You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

async_session.c 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. /*
  2. * Copyright 2023 Vsevolod Stakhov
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #include "config.h"
  17. #include "rspamd.h"
  18. #include "contrib/uthash/utlist.h"
  19. #include "contrib/libucl/khash.h"
  20. #include "async_session.h"
  21. #include "cryptobox.h"
  22. #define RSPAMD_SESSION_FLAG_DESTROYING (1 << 1)
  23. #define RSPAMD_SESSION_FLAG_CLEANUP (1 << 2)
  24. #define RSPAMD_SESSION_CAN_ADD_EVENT(s) (!((s)->flags & (RSPAMD_SESSION_FLAG_DESTROYING | RSPAMD_SESSION_FLAG_CLEANUP)))
  25. #define msg_err_session(...) rspamd_default_log_function(G_LOG_LEVEL_CRITICAL, \
  26. "events", session->pool->tag.uid, \
  27. G_STRFUNC, \
  28. __VA_ARGS__)
  29. #define msg_warn_session(...) rspamd_default_log_function(G_LOG_LEVEL_WARNING, \
  30. "events", session->pool->tag.uid, \
  31. G_STRFUNC, \
  32. __VA_ARGS__)
  33. #define msg_info_session(...) rspamd_default_log_function(G_LOG_LEVEL_INFO, \
  34. "events", session->pool->tag.uid, \
  35. G_STRFUNC, \
  36. __VA_ARGS__)
  37. #define msg_debug_session(...) rspamd_conditional_debug_fast(NULL, NULL, \
  38. rspamd_events_log_id, "events", session->pool->tag.uid, \
  39. G_STRFUNC, \
  40. __VA_ARGS__)
  41. INIT_LOG_MODULE(events)
  42. /* Average symbols count to optimize hash allocation */
  43. static struct rspamd_counter_data events_count;
  44. struct rspamd_async_event {
  45. const gchar *subsystem;
  46. const gchar *event_source;
  47. event_finalizer_t fin;
  48. void *user_data;
  49. };
  50. static inline bool
  51. rspamd_event_equal(const struct rspamd_async_event *ev1, const struct rspamd_async_event *ev2)
  52. {
  53. return ev1->fin == ev2->fin && ev1->user_data == ev2->user_data;
  54. }
  55. static inline uint64_t
  56. rspamd_event_hash(const struct rspamd_async_event *ev)
  57. {
  58. union _pointer_fp_thunk {
  59. event_finalizer_t f;
  60. gpointer p;
  61. };
  62. struct ev_storage {
  63. union _pointer_fp_thunk p;
  64. gpointer ud;
  65. } st;
  66. st.p.f = ev->fin;
  67. st.ud = ev->user_data;
  68. return rspamd_cryptobox_fast_hash(&st, sizeof(st), rspamd_hash_seed());
  69. }
  70. /* Define **SET** of events */
  71. KHASH_INIT(rspamd_events_hash,
  72. struct rspamd_async_event *,
  73. char,
  74. false,
  75. rspamd_event_hash,
  76. rspamd_event_equal);
  77. struct rspamd_async_session {
  78. session_finalizer_t fin;
  79. event_finalizer_t restore;
  80. event_finalizer_t cleanup;
  81. khash_t(rspamd_events_hash) * events;
  82. void *user_data;
  83. rspamd_mempool_t *pool;
  84. guint flags;
  85. };
  86. static void
  87. rspamd_session_dtor(gpointer d)
  88. {
  89. struct rspamd_async_session *s = (struct rspamd_async_session *) d;
  90. /* Events are usually empty at this point */
  91. rspamd_set_counter_ema(&events_count, s->events->n_buckets, 0.5);
  92. kh_destroy(rspamd_events_hash, s->events);
  93. }
  94. struct rspamd_async_session *
  95. rspamd_session_create(rspamd_mempool_t *pool,
  96. session_finalizer_t fin,
  97. event_finalizer_t restore,
  98. event_finalizer_t cleanup,
  99. void *user_data)
  100. {
  101. struct rspamd_async_session *s;
  102. s = rspamd_mempool_alloc0(pool, sizeof(struct rspamd_async_session));
  103. s->pool = pool;
  104. s->fin = fin;
  105. s->restore = restore;
  106. s->cleanup = cleanup;
  107. s->user_data = user_data;
  108. s->events = kh_init(rspamd_events_hash);
  109. kh_resize(rspamd_events_hash, s->events, MAX(4, events_count.mean));
  110. rspamd_mempool_add_destructor(pool, rspamd_session_dtor, s);
  111. return s;
  112. }
  113. struct rspamd_async_event *
  114. rspamd_session_add_event_full(struct rspamd_async_session *session,
  115. event_finalizer_t fin,
  116. gpointer user_data,
  117. const gchar *subsystem,
  118. const gchar *event_source)
  119. {
  120. struct rspamd_async_event *new_event;
  121. gint ret;
  122. if (session == NULL) {
  123. msg_err("session is NULL");
  124. g_assert_not_reached();
  125. }
  126. if (!RSPAMD_SESSION_CAN_ADD_EVENT(session)) {
  127. msg_debug_session("skip adding event subsystem: %s: "
  128. "session is destroying/cleaning",
  129. subsystem);
  130. return NULL;
  131. }
  132. new_event = rspamd_mempool_alloc(session->pool,
  133. sizeof(struct rspamd_async_event));
  134. new_event->fin = fin;
  135. new_event->user_data = user_data;
  136. new_event->subsystem = subsystem;
  137. new_event->event_source = event_source;
  138. msg_debug_session("added event: %p, pending %d (+1) events, "
  139. "subsystem: %s (%s)",
  140. user_data,
  141. kh_size(session->events),
  142. subsystem,
  143. event_source);
  144. kh_put(rspamd_events_hash, session->events, new_event, &ret);
  145. g_assert(ret > 0);
  146. return new_event;
  147. }
  148. void rspamd_session_remove_event_full(struct rspamd_async_session *session,
  149. event_finalizer_t fin,
  150. void *ud,
  151. const gchar *event_source)
  152. {
  153. struct rspamd_async_event search_ev, *found_ev;
  154. khiter_t k;
  155. if (session == NULL) {
  156. msg_err("session is NULL");
  157. return;
  158. }
  159. if (!RSPAMD_SESSION_CAN_ADD_EVENT(session)) {
  160. /* Session is already cleaned up, ignore this */
  161. return;
  162. }
  163. /* Search for event */
  164. search_ev.fin = fin;
  165. search_ev.user_data = ud;
  166. k = kh_get(rspamd_events_hash, session->events, &search_ev);
  167. if (k == kh_end(session->events)) {
  168. msg_err_session("cannot find event: %p(%p) from %s (%d total events)", fin, ud,
  169. event_source, (int) kh_size(session->events));
  170. kh_foreach_key(session->events, found_ev, {
  171. msg_err_session("existing event %s (%s): %p(%p)",
  172. found_ev->subsystem,
  173. found_ev->event_source,
  174. found_ev->fin,
  175. found_ev->user_data);
  176. });
  177. g_assert_not_reached();
  178. }
  179. found_ev = kh_key(session->events, k);
  180. msg_debug_session("removed event: %p, pending %d (-1) events, "
  181. "subsystem: %s (%s), added at %s",
  182. ud,
  183. kh_size(session->events),
  184. found_ev->subsystem,
  185. event_source,
  186. found_ev->event_source);
  187. kh_del(rspamd_events_hash, session->events, k);
  188. /* Remove event */
  189. if (fin) {
  190. fin(ud);
  191. }
  192. rspamd_session_pending(session);
  193. }
  194. gboolean
  195. rspamd_session_destroy(struct rspamd_async_session *session)
  196. {
  197. if (session == NULL) {
  198. msg_err("session is NULL");
  199. return FALSE;
  200. }
  201. if (!rspamd_session_blocked(session)) {
  202. session->flags |= RSPAMD_SESSION_FLAG_DESTROYING;
  203. rspamd_session_cleanup(session, false);
  204. if (session->cleanup != NULL) {
  205. session->cleanup(session->user_data);
  206. }
  207. }
  208. return TRUE;
  209. }
  210. void rspamd_session_cleanup(struct rspamd_async_session *session, bool forced_cleanup)
  211. {
  212. struct rspamd_async_event *ev;
  213. if (session == NULL) {
  214. msg_err("session is NULL");
  215. return;
  216. }
  217. session->flags |= RSPAMD_SESSION_FLAG_CLEANUP;
  218. khash_t(rspamd_events_hash) *uncancellable_events = kh_init(rspamd_events_hash);
  219. kh_foreach_key(session->events, ev, {
  220. /* Call event's finalizer */
  221. int ret;
  222. if (ev->fin != NULL) {
  223. if (forced_cleanup) {
  224. msg_info_session("forced removed event on destroy: %p, subsystem: %s, scheduled from: %s",
  225. ev->user_data,
  226. ev->subsystem,
  227. ev->event_source);
  228. }
  229. else {
  230. msg_debug_session("removed event on destroy: %p, subsystem: %s",
  231. ev->user_data,
  232. ev->subsystem);
  233. }
  234. ev->fin(ev->user_data);
  235. }
  236. else {
  237. if (forced_cleanup) {
  238. msg_info_session("NOT forced removed event on destroy - uncancellable: "
  239. "%p, subsystem: %s, scheduled from: %s",
  240. ev->user_data,
  241. ev->subsystem,
  242. ev->event_source);
  243. }
  244. else {
  245. msg_debug_session("NOT removed event on destroy - uncancellable: %p, subsystem: %s",
  246. ev->user_data,
  247. ev->subsystem);
  248. }
  249. /* Assume an event is uncancellable, move it to a new hash table */
  250. kh_put(rspamd_events_hash, uncancellable_events, ev, &ret);
  251. }
  252. });
  253. kh_destroy(rspamd_events_hash, session->events);
  254. session->events = uncancellable_events;
  255. if (forced_cleanup) {
  256. msg_info_session("pending %d uncancellable events", kh_size(uncancellable_events));
  257. }
  258. else {
  259. msg_debug_session("pending %d uncancellable events", kh_size(uncancellable_events));
  260. }
  261. session->flags &= ~RSPAMD_SESSION_FLAG_CLEANUP;
  262. }
  263. gboolean
  264. rspamd_session_pending(struct rspamd_async_session *session)
  265. {
  266. gboolean ret = TRUE;
  267. if (kh_size(session->events) == 0) {
  268. if (session->fin != NULL) {
  269. msg_debug_session("call fin handler, as no events are pending");
  270. if (!session->fin(session->user_data)) {
  271. /* Session finished incompletely, perform restoration */
  272. msg_debug_session("restore incomplete session");
  273. if (session->restore != NULL) {
  274. session->restore(session->user_data);
  275. }
  276. }
  277. else {
  278. ret = FALSE;
  279. }
  280. }
  281. ret = FALSE;
  282. }
  283. return ret;
  284. }
  285. guint rspamd_session_events_pending(struct rspamd_async_session *session)
  286. {
  287. guint npending;
  288. g_assert(session != NULL);
  289. npending = kh_size(session->events);
  290. msg_debug_session("pending %d events", npending);
  291. return npending;
  292. }
  293. rspamd_mempool_t *
  294. rspamd_session_mempool(struct rspamd_async_session *session)
  295. {
  296. g_assert(session != NULL);
  297. return session->pool;
  298. }
  299. gboolean
  300. rspamd_session_blocked(struct rspamd_async_session *session)
  301. {
  302. g_assert(session != NULL);
  303. return !RSPAMD_SESSION_CAN_ADD_EVENT(session);
  304. }