Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

symcache_impl.cxx 39KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341
  1. /*
  2. * Copyright 2024 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 "lua/lua_common.h"
  17. #include "symcache_internal.hxx"
  18. #include "symcache_item.hxx"
  19. #include "symcache_runtime.hxx"
  20. #include "unix-std.h"
  21. #include "libutil/cxx/file_util.hxx"
  22. #include "libutil/cxx/util.hxx"
  23. #include "fmt/core.h"
  24. #include "contrib/t1ha/t1ha.h"
  25. #ifdef __has_include
  26. #if __has_include(<version>)
  27. #include <version>
  28. #endif
  29. #endif
  30. #include <cmath>
  31. namespace rspamd::symcache {
  32. INIT_LOG_MODULE_PUBLIC(symcache)
  33. auto symcache::init() -> bool
  34. {
  35. auto res = true;
  36. reload_time = cfg->cache_reload_time;
  37. if (cfg->cache_filename != nullptr) {
  38. msg_debug_cache("loading symcache saved data from %s", cfg->cache_filename);
  39. load_items();
  40. }
  41. ankerl::unordered_dense::set<int> disabled_ids;
  42. /* Process enabled/disabled symbols */
  43. for (const auto &[id, it]: items_by_id) {
  44. if (disabled_symbols) {
  45. /*
  46. * Due to the ability to add patterns, this is now O(N^2), but it is done
  47. * once on configuration and the amount of static patterns is usually low
  48. * The possible optimization is to store non patterns in a different set to check it
  49. * quickly. However, it is unlikely that this would be used to something really heavy.
  50. */
  51. for (const auto &disable_pat: *disabled_symbols) {
  52. if (disable_pat.matches(it->get_name())) {
  53. msg_debug_cache("symbol %s matches %*s disable pattern", it->get_name().c_str(),
  54. (int) disable_pat.to_string_view().size(), disable_pat.to_string_view().data());
  55. auto need_disable = true;
  56. if (enabled_symbols) {
  57. for (const auto &enable_pat: *enabled_symbols) {
  58. if (enable_pat.matches(it->get_name())) {
  59. msg_debug_cache("symbol %s matches %*s enable pattern; skip disabling", it->get_name().c_str(),
  60. (int) enable_pat.to_string_view().size(), enable_pat.to_string_view().data());
  61. need_disable = false;
  62. break;
  63. }
  64. }
  65. }
  66. if (need_disable) {
  67. disabled_ids.insert(it->id);
  68. if (it->is_virtual()) {
  69. auto real_elt = it->get_parent(*this);
  70. if (real_elt) {
  71. disabled_ids.insert(real_elt->id);
  72. const auto *children = real_elt->get_children();
  73. if (children != nullptr) {
  74. for (const auto &cld: *children) {
  75. msg_debug_cache("symbol %s is a virtual sibling of the disabled symbol %s",
  76. cld->get_name().c_str(), it->get_name().c_str());
  77. disabled_ids.insert(cld->id);
  78. }
  79. }
  80. }
  81. }
  82. else {
  83. /* Also disable all virtual children of this element */
  84. const auto *children = it->get_children();
  85. if (children != nullptr) {
  86. for (const auto &cld: *children) {
  87. msg_debug_cache("symbol %s is a virtual child of the disabled symbol %s",
  88. cld->get_name().c_str(), it->get_name().c_str());
  89. disabled_ids.insert(cld->id);
  90. }
  91. }
  92. }
  93. }
  94. }
  95. }
  96. }
  97. }
  98. /* Deal with the delayed dependencies */
  99. msg_debug_cache("resolving delayed dependencies: %d in list", (int) delayed_deps->size());
  100. for (const auto &delayed_dep: *delayed_deps) {
  101. auto virt_source = get_item_by_name(delayed_dep.from, false);
  102. auto real_source = get_item_by_name(delayed_dep.from, true);
  103. auto real_destination = get_item_by_name(delayed_dep.to, true);
  104. if (virt_source == nullptr || real_source == nullptr || real_destination == nullptr) {
  105. if (real_destination != nullptr) {
  106. msg_err_cache("cannot register delayed dependency %s -> %s: "
  107. "source %s is missing",
  108. delayed_dep.from.data(),
  109. delayed_dep.to.data(), delayed_dep.from.data());
  110. }
  111. else {
  112. msg_err_cache("cannot register delayed dependency %s -> %s: "
  113. "destionation %s is missing",
  114. delayed_dep.from.data(),
  115. delayed_dep.to.data(), delayed_dep.to.data());
  116. }
  117. }
  118. else {
  119. if (!disabled_ids.contains(real_source->id)) {
  120. msg_debug_cache("delayed between %s(%d:%d) -> %s",
  121. delayed_dep.from.data(),
  122. real_source->id, virt_source->id,
  123. delayed_dep.to.data());
  124. add_dependency(real_source->id, delayed_dep.to, real_destination->id,
  125. virt_source != real_source ? virt_source->id : -1);
  126. }
  127. else {
  128. msg_debug_cache("no delayed between %s(%d:%d) -> %s; %s is disabled",
  129. delayed_dep.from.data(),
  130. real_source->id, virt_source->id,
  131. delayed_dep.to.data(),
  132. delayed_dep.from.data());
  133. }
  134. }
  135. }
  136. /* Remove delayed dependencies, as they are no longer needed at this point */
  137. delayed_deps.reset();
  138. /* Physically remove ids that are disabled statically */
  139. for (auto id_to_disable: disabled_ids) {
  140. /*
  141. * This erasure is inefficient, we can swap the last element with the removed id
  142. * But in this way, our ids are still sorted by addition
  143. */
  144. /* Preserve refcount here */
  145. auto deleted_element_refcount = items_by_id[id_to_disable];
  146. items_by_id.erase(id_to_disable);
  147. items_by_symbol.erase(deleted_element_refcount->get_name());
  148. auto &additional_vec = get_item_specific_vector(*deleted_element_refcount);
  149. #if defined(__cpp_lib_erase_if)
  150. std::erase_if(additional_vec, [id_to_disable](cache_item *elt) {
  151. return elt->id == id_to_disable;
  152. });
  153. #else
  154. auto it = std::remove_if(additional_vec.begin(),
  155. additional_vec.end(), [id_to_disable](cache_item *elt) {
  156. return elt->id == id_to_disable;
  157. });
  158. additional_vec.erase(it, additional_vec.end());
  159. #endif
  160. /* Refcount is dropped, so the symbol should be freed, ensure that nothing else owns this symbol */
  161. g_assert(deleted_element_refcount.use_count() == 1);
  162. }
  163. /* Remove no longer used stuff */
  164. enabled_symbols.reset();
  165. disabled_symbols.reset();
  166. /* Deal with the delayed conditions */
  167. msg_debug_cache("resolving delayed conditions: %d in list", (int) delayed_conditions->size());
  168. for (const auto &delayed_cond: *delayed_conditions) {
  169. auto it = get_item_by_name_mut(delayed_cond.sym, true);
  170. if (it == nullptr) {
  171. msg_err_cache(
  172. "cannot register delayed condition for %s",
  173. delayed_cond.sym.c_str());
  174. luaL_unref(delayed_cond.L, LUA_REGISTRYINDEX, delayed_cond.cbref);
  175. }
  176. else {
  177. if (!it->add_condition(delayed_cond.L, delayed_cond.cbref)) {
  178. msg_err_cache(
  179. "cannot register delayed condition for %s: virtual parent; qed",
  180. delayed_cond.sym.c_str());
  181. g_abort();
  182. }
  183. msg_debug_cache("added a condition to the symbol %s", it->symbol.c_str());
  184. }
  185. }
  186. delayed_conditions.reset();
  187. msg_debug_cache("process dependencies");
  188. for (const auto &[_id, it]: items_by_id) {
  189. it->process_deps(*this);
  190. }
  191. /* Sorting stuff */
  192. constexpr auto postfilters_cmp = [](const auto &it1, const auto &it2) -> bool {
  193. return it1->priority < it2->priority;
  194. };
  195. constexpr auto prefilters_cmp = [](const auto &it1, const auto &it2) -> bool {
  196. return it1->priority > it2->priority;
  197. };
  198. msg_debug_cache("sorting stuff");
  199. std::stable_sort(std::begin(connfilters), std::end(connfilters), prefilters_cmp);
  200. std::stable_sort(std::begin(prefilters), std::end(prefilters), prefilters_cmp);
  201. std::stable_sort(std::begin(postfilters), std::end(postfilters), postfilters_cmp);
  202. std::stable_sort(std::begin(idempotent), std::end(idempotent), postfilters_cmp);
  203. resort();
  204. /* Connect metric symbols with symcache symbols */
  205. if (cfg->symbols) {
  206. msg_debug_cache("connect metrics");
  207. g_hash_table_foreach(cfg->symbols,
  208. symcache::metric_connect_cb,
  209. (void *) this);
  210. }
  211. return res;
  212. }
  213. auto symcache::load_items() -> bool
  214. {
  215. auto cached_map = util::raii_mmaped_file::mmap_shared(cfg->cache_filename,
  216. O_RDONLY, PROT_READ);
  217. if (!cached_map.has_value()) {
  218. if (cached_map.error().category == util::error_category::CRITICAL) {
  219. msg_err_cache("%s", cached_map.error().error_message.data());
  220. }
  221. else {
  222. msg_info_cache("%s", cached_map.error().error_message.data());
  223. }
  224. return false;
  225. }
  226. if (cached_map->get_size() < (int) sizeof(symcache_header)) {
  227. msg_info_cache("cannot use file %s, truncated: %z", cfg->cache_filename,
  228. errno, strerror(errno));
  229. return false;
  230. }
  231. const auto *hdr = (struct symcache_header *) cached_map->get_map();
  232. if (memcmp(hdr->magic, symcache_magic,
  233. sizeof(symcache_magic)) != 0) {
  234. msg_info_cache("cannot use file %s, bad magic", cfg->cache_filename);
  235. return false;
  236. }
  237. auto *parser = ucl_parser_new(0);
  238. const auto *p = (const std::uint8_t *) (hdr + 1);
  239. if (!ucl_parser_add_chunk(parser, p, cached_map->get_size() - sizeof(*hdr))) {
  240. msg_info_cache("cannot use file %s, cannot parse: %s", cfg->cache_filename,
  241. ucl_parser_get_error(parser));
  242. ucl_parser_free(parser);
  243. return false;
  244. }
  245. auto *top = ucl_parser_get_object(parser);
  246. ucl_parser_free(parser);
  247. if (top == nullptr || ucl_object_type(top) != UCL_OBJECT) {
  248. msg_info_cache("cannot use file %s, bad object", cfg->cache_filename);
  249. ucl_object_unref(top);
  250. return false;
  251. }
  252. auto it = ucl_object_iterate_new(top);
  253. const ucl_object_t *cur;
  254. while ((cur = ucl_object_iterate_safe(it, true)) != nullptr) {
  255. auto item_it = items_by_symbol.find(ucl_object_key(cur));
  256. if (item_it != items_by_symbol.end()) {
  257. auto item = item_it->second;
  258. /* Copy saved info */
  259. /*
  260. * XXX: don't save or load weight, it should be obtained from the
  261. * metric
  262. */
  263. #if 0
  264. elt = ucl_object_lookup (cur, "weight");
  265. if (elt) {
  266. w = ucl_object_todouble (elt);
  267. if (w != 0) {
  268. item->weight = w;
  269. }
  270. }
  271. #endif
  272. const auto *elt = ucl_object_lookup(cur, "time");
  273. if (elt) {
  274. item->st->avg_time = ucl_object_todouble(elt);
  275. }
  276. elt = ucl_object_lookup(cur, "count");
  277. if (elt) {
  278. item->st->total_hits = ucl_object_toint(elt);
  279. item->last_count = item->st->total_hits;
  280. }
  281. elt = ucl_object_lookup(cur, "frequency");
  282. if (elt && ucl_object_type(elt) == UCL_OBJECT) {
  283. const ucl_object_t *freq_elt;
  284. freq_elt = ucl_object_lookup(elt, "avg");
  285. if (freq_elt) {
  286. item->st->avg_frequency = ucl_object_todouble(freq_elt);
  287. }
  288. freq_elt = ucl_object_lookup(elt, "stddev");
  289. if (freq_elt) {
  290. item->st->stddev_frequency = ucl_object_todouble(freq_elt);
  291. }
  292. }
  293. if (item->is_virtual() && !item->is_ghost()) {
  294. const auto &parent = item->get_parent(*this);
  295. if (parent) {
  296. if (parent->st->weight < item->st->weight) {
  297. parent->st->weight = item->st->weight;
  298. }
  299. }
  300. /*
  301. * We maintain avg_time for virtual symbols equal to the
  302. * parent item avg_time
  303. */
  304. item->st->avg_time = parent->st->avg_time;
  305. }
  306. total_weight += fabs(item->st->weight);
  307. total_hits += item->st->total_hits;
  308. }
  309. }
  310. ucl_object_iterate_free(it);
  311. ucl_object_unref(top);
  312. return true;
  313. }
  314. template<typename T>
  315. static constexpr auto round_to_hundreds(T x)
  316. {
  317. return (::floor(x) * 100.0) / 100.0;
  318. }
  319. bool symcache::save_items() const
  320. {
  321. if (cfg->cache_filename == nullptr) {
  322. return false;
  323. }
  324. auto file_sink = util::raii_file_sink::create(cfg->cache_filename,
  325. O_WRONLY | O_TRUNC, 00644);
  326. if (!file_sink.has_value()) {
  327. if (errno == EEXIST) {
  328. /* Some other process is already writing data, give up silently */
  329. return false;
  330. }
  331. msg_err_cache("%s", file_sink.error().error_message.data());
  332. return false;
  333. }
  334. struct symcache_header hdr;
  335. memset(&hdr, 0, sizeof(hdr));
  336. memcpy(hdr.magic, symcache_magic, sizeof(symcache_magic));
  337. if (write(file_sink->get_fd(), &hdr, sizeof(hdr)) == -1) {
  338. msg_err_cache("cannot write to file %s, error %d, %s", cfg->cache_filename,
  339. errno, strerror(errno));
  340. return false;
  341. }
  342. auto *top = ucl_object_typed_new(UCL_OBJECT);
  343. for (const auto &it: items_by_symbol) {
  344. auto item = it.second;
  345. auto elt = ucl_object_typed_new(UCL_OBJECT);
  346. ucl_object_insert_key(elt,
  347. ucl_object_fromdouble(round_to_hundreds(item->st->weight)),
  348. "weight", 0, false);
  349. ucl_object_insert_key(elt,
  350. ucl_object_fromdouble(round_to_hundreds(item->st->time_counter.mean)),
  351. "time", 0, false);
  352. ucl_object_insert_key(elt, ucl_object_fromint(item->st->total_hits),
  353. "count", 0, false);
  354. auto *freq = ucl_object_typed_new(UCL_OBJECT);
  355. ucl_object_insert_key(freq,
  356. ucl_object_fromdouble(round_to_hundreds(item->st->frequency_counter.mean)),
  357. "avg", 0, false);
  358. ucl_object_insert_key(freq,
  359. ucl_object_fromdouble(round_to_hundreds(item->st->frequency_counter.stddev)),
  360. "stddev", 0, false);
  361. ucl_object_insert_key(elt, freq, "frequency", 0, false);
  362. ucl_object_insert_key(top, elt, it.first.data(), 0, true);
  363. }
  364. auto fp = fdopen(file_sink->get_fd(), "a");
  365. auto *efunc = ucl_object_emit_file_funcs(fp);
  366. auto ret = ucl_object_emit_full(top, UCL_EMIT_JSON_COMPACT, efunc, nullptr);
  367. ucl_object_emit_funcs_free(efunc);
  368. ucl_object_unref(top);
  369. fclose(fp);
  370. return ret;
  371. }
  372. auto symcache::metric_connect_cb(void *k, void *v, void *ud) -> void
  373. {
  374. auto *cache = (symcache *) ud;
  375. const auto *sym = (const char *) k;
  376. auto *s = (struct rspamd_symbol *) v;
  377. auto weight = *s->weight_ptr;
  378. auto *item = cache->get_item_by_name_mut(sym, false);
  379. if (item) {
  380. item->st->weight = weight;
  381. s->cache_item = (void *) item;
  382. }
  383. }
  384. auto symcache::get_item_by_id(int id, bool resolve_parent) const -> const cache_item *
  385. {
  386. if (id < 0 || id >= items_by_id.size()) {
  387. msg_err_cache("internal error: requested item with id %d, when we have just %d items in the cache",
  388. id, (int) items_by_id.size());
  389. return nullptr;
  390. }
  391. const auto &maybe_item = rspamd::find_map(items_by_id, id);
  392. if (!maybe_item.has_value()) {
  393. msg_err_cache("internal error: requested item with id %d but it is empty; qed",
  394. id);
  395. return nullptr;
  396. }
  397. const auto &item = maybe_item.value().get();
  398. if (resolve_parent && item->is_virtual()) {
  399. return item->get_parent(*this);
  400. }
  401. return item.get();
  402. }
  403. auto symcache::get_item_by_id_mut(int id, bool resolve_parent) const -> cache_item *
  404. {
  405. if (id < 0 || id >= items_by_id.size()) {
  406. msg_err_cache("internal error: requested item with id %d, when we have just %d items in the cache",
  407. id, (int) items_by_id.size());
  408. return nullptr;
  409. }
  410. const auto &maybe_item = rspamd::find_map(items_by_id, id);
  411. if (!maybe_item.has_value()) {
  412. msg_err_cache("internal error: requested item with id %d but it is empty; qed",
  413. id);
  414. return nullptr;
  415. }
  416. const auto &item = maybe_item.value().get();
  417. if (resolve_parent && item->is_virtual()) {
  418. return const_cast<cache_item *>(item->get_parent(*this));
  419. }
  420. return item.get();
  421. }
  422. auto symcache::get_item_by_name(std::string_view name, bool resolve_parent) const -> const cache_item *
  423. {
  424. auto it = items_by_symbol.find(name);
  425. if (it == items_by_symbol.end()) {
  426. return nullptr;
  427. }
  428. if (resolve_parent && it->second->is_virtual()) {
  429. it->second->resolve_parent(*this);
  430. return it->second->get_parent(*this);
  431. }
  432. return it->second;
  433. }
  434. auto symcache::get_item_by_name_mut(std::string_view name, bool resolve_parent) const -> cache_item *
  435. {
  436. auto it = items_by_symbol.find(name);
  437. if (it == items_by_symbol.end()) {
  438. return nullptr;
  439. }
  440. if (resolve_parent && it->second->is_virtual()) {
  441. return (cache_item *) it->second->get_parent(*this);
  442. }
  443. return it->second;
  444. }
  445. auto symcache::add_dependency(int id_from, std::string_view to, int id_to, int virtual_id_from) -> void
  446. {
  447. g_assert(id_from >= 0 && id_from < (int) items_by_id.size());
  448. g_assert(id_to >= 0 && id_to < (int) items_by_id.size());
  449. const auto &source = items_by_id[id_from];
  450. const auto &dest = items_by_id[id_to];
  451. g_assert(source.get() != nullptr);
  452. g_assert(dest.get() != nullptr);
  453. if (!source->deps.contains(id_to)) {
  454. msg_debug_cache("add dependency %s(%d) -> %s(%d)",
  455. source->symbol.c_str(), source->id, to.data(), dest->id);
  456. source->deps.emplace(id_to, cache_dependency{dest.get(),
  457. std::string(to),
  458. -1});
  459. }
  460. else {
  461. msg_debug_cache("duplicate dependency %s -> %s",
  462. source->symbol.c_str(), to.data());
  463. return;
  464. }
  465. if (virtual_id_from >= 0) {
  466. g_assert(virtual_id_from < (int) items_by_id.size());
  467. /* We need that for settings id propagation */
  468. const auto &vsource = items_by_id[virtual_id_from];
  469. g_assert(vsource.get() != nullptr);
  470. if (!vsource->deps.contains(id_to)) {
  471. msg_debug_cache("add virtual dependency %s -> %s",
  472. vsource->symbol.c_str(), to.data());
  473. vsource->deps.emplace(id_to, cache_dependency{dest.get(),
  474. std::string(to),
  475. virtual_id_from});
  476. }
  477. else {
  478. msg_debug_cache("duplicate virtual dependency %s -> %s",
  479. vsource->symbol.c_str(), to.data());
  480. }
  481. }
  482. }
  483. auto symcache::resort() -> void
  484. {
  485. auto log_func = RSPAMD_LOG_FUNC;
  486. auto ord = std::make_shared<order_generation>(filters.size() +
  487. prefilters.size() +
  488. composites.size() +
  489. postfilters.size() +
  490. idempotent.size() +
  491. connfilters.size() +
  492. classifiers.size(),
  493. cur_order_gen);
  494. for (auto &it: filters) {
  495. if (it) {
  496. total_hits += it->st->total_hits;
  497. /* Unmask topological order */
  498. it->order = 0;
  499. ord->d.emplace_back(it->getptr());
  500. }
  501. }
  502. enum class tsort_mask {
  503. PERM,
  504. TEMP
  505. };
  506. constexpr auto tsort_unmask = [](cache_item *it) -> auto {
  507. return (it->order & ~((1u << 31) | (1u << 30)));
  508. };
  509. /* Recursive topological sort helper */
  510. const auto tsort_visit = [&](cache_item *it, unsigned cur_order, auto &&rec) {
  511. constexpr auto tsort_mark = [](cache_item *it, tsort_mask how) {
  512. switch (how) {
  513. case tsort_mask::PERM:
  514. it->order |= (1u << 31);
  515. break;
  516. case tsort_mask::TEMP:
  517. it->order |= (1u << 30);
  518. break;
  519. }
  520. };
  521. constexpr auto tsort_is_marked = [](cache_item *it, tsort_mask how) {
  522. switch (how) {
  523. case tsort_mask::PERM:
  524. return (it->order & (1u << 31));
  525. case tsort_mask::TEMP:
  526. return (it->order & (1u << 30));
  527. }
  528. return 100500u; /* Because fuck compilers, that's why */
  529. };
  530. if (tsort_is_marked(it, tsort_mask::PERM)) {
  531. if (cur_order > tsort_unmask(it)) {
  532. /* Need to recalculate the whole chain */
  533. it->order = cur_order; /* That also removes all masking */
  534. }
  535. else {
  536. /* We are fine, stop DFS */
  537. return;
  538. }
  539. }
  540. else if (tsort_is_marked(it, tsort_mask::TEMP)) {
  541. msg_err_cache_lambda("cyclic dependencies found when checking '%s'!",
  542. it->symbol.c_str());
  543. return;
  544. }
  545. tsort_mark(it, tsort_mask::TEMP);
  546. msg_debug_cache_lambda("visiting node: %s (%d)", it->symbol.c_str(), cur_order);
  547. for (const auto &[id, dep]: it->deps) {
  548. msg_debug_cache_lambda("visiting dep: %s (%d)", dep.item->symbol.c_str(), cur_order + 1);
  549. rec(dep.item, cur_order + 1, rec);
  550. }
  551. it->order = cur_order;
  552. tsort_mark(it, tsort_mask::PERM);
  553. };
  554. /*
  555. * Topological sort
  556. */
  557. total_hits = 0;
  558. auto used_items = ord->d.size();
  559. msg_debug_cache("topologically sort %d filters", used_items);
  560. for (const auto &it: ord->d) {
  561. if (it->order == 0) {
  562. tsort_visit(it.get(), 0, tsort_visit);
  563. }
  564. }
  565. /* Main sorting comparator */
  566. constexpr auto score_functor = [](auto w, auto f, auto t) -> auto {
  567. auto time_alpha = 1.0, weight_alpha = 0.1, freq_alpha = 0.01;
  568. return ((w > 0.0 ? w : weight_alpha) * (f > 0.0 ? f : freq_alpha) /
  569. (t > time_alpha ? t : time_alpha));
  570. };
  571. auto cache_order_cmp = [&](const auto &it1, const auto &it2) -> auto {
  572. constexpr const auto topology_mult = 1e7,
  573. priority_mult = 1e6,
  574. augmentations1_mult = 1e5;
  575. auto w1 = tsort_unmask(it1.get()) * topology_mult,
  576. w2 = tsort_unmask(it2.get()) * topology_mult;
  577. w1 += it1->priority * priority_mult;
  578. w2 += it2->priority * priority_mult;
  579. w1 += it1->get_augmentation_weight() * augmentations1_mult;
  580. w2 += it2->get_augmentation_weight() * augmentations1_mult;
  581. auto avg_freq = ((double) total_hits / used_items);
  582. auto avg_weight = (total_weight / used_items);
  583. auto f1 = (double) it1->st->total_hits / avg_freq;
  584. auto f2 = (double) it2->st->total_hits / avg_freq;
  585. auto weight1 = std::fabs(it1->st->weight) / avg_weight;
  586. auto weight2 = std::fabs(it2->st->weight) / avg_weight;
  587. auto t1 = it1->st->avg_time;
  588. auto t2 = it2->st->avg_time;
  589. w1 += score_functor(weight1, f1, t1);
  590. w2 += score_functor(weight2, f2, t2);
  591. return w1 > w2;
  592. };
  593. std::stable_sort(std::begin(ord->d), std::end(ord->d), cache_order_cmp);
  594. /*
  595. * Here lives some ugly legacy!
  596. * We have several filters classes, connfilters, prefilters, filters... etc
  597. *
  598. * Our order is meaningful merely for filters, but we have to add other classes
  599. * to understand if those symbols are checked or disabled.
  600. * We can disable symbols for almost everything but not for virtual symbols.
  601. * The rule of thumb is that if a symbol has explicit parent, then it is a
  602. * virtual symbol that follows it's special rules
  603. */
  604. /*
  605. * We enrich ord with all other symbol types without any sorting,
  606. * as it is done in another place
  607. */
  608. const auto append_items_vec = [&](const auto &vec, auto &out, const char *what) {
  609. msg_debug_cache_lambda("append %d items; type = %s", (int) vec.size(), what);
  610. for (const auto &it: vec) {
  611. if (it) {
  612. out.emplace_back(it->getptr());
  613. }
  614. }
  615. };
  616. append_items_vec(connfilters, ord->d, "connection filters");
  617. append_items_vec(prefilters, ord->d, "prefilters");
  618. append_items_vec(postfilters, ord->d, "postfilters");
  619. append_items_vec(idempotent, ord->d, "idempotent filters");
  620. append_items_vec(composites, ord->d, "composites");
  621. append_items_vec(classifiers, ord->d, "classifiers");
  622. /* After sorting is done, we can assign all elements in the by_symbol hash */
  623. for (const auto [i, it]: rspamd::enumerate(ord->d)) {
  624. ord->by_symbol.emplace(it->get_name(), i);
  625. ord->by_cache_id[it->id] = i;
  626. }
  627. /* Finally set the current order */
  628. std::swap(ord, items_by_order);
  629. }
  630. auto symcache::add_symbol_with_callback(std::string_view name,
  631. int priority,
  632. symbol_func_t func,
  633. void *user_data,
  634. int flags_and_type) -> int
  635. {
  636. auto real_type_pair_maybe = item_type_from_c(flags_and_type);
  637. if (!real_type_pair_maybe.has_value()) {
  638. msg_err_cache("incompatible flags when adding %s: %s", name.data(),
  639. real_type_pair_maybe.error().c_str());
  640. return -1;
  641. }
  642. auto real_type_pair = real_type_pair_maybe.value();
  643. if (real_type_pair.first != symcache_item_type::FILTER) {
  644. real_type_pair.second |= SYMBOL_TYPE_NOSTAT;
  645. }
  646. if (real_type_pair.second & (SYMBOL_TYPE_GHOST | SYMBOL_TYPE_CALLBACK)) {
  647. real_type_pair.second |= SYMBOL_TYPE_NOSTAT;
  648. }
  649. if (real_type_pair.first == symcache_item_type::VIRTUAL) {
  650. msg_err_cache("trying to add virtual symbol %s as real (no parent)", name.data());
  651. return -1;
  652. }
  653. std::string static_string_name;
  654. if (name.empty()) {
  655. static_string_name = fmt::format("AUTO_{}_{}", (void *) func, user_data);
  656. msg_warn_cache("trying to add an empty symbol name, convert it to %s",
  657. static_string_name.c_str());
  658. }
  659. else {
  660. static_string_name = name;
  661. }
  662. if (real_type_pair.first == symcache_item_type::IDEMPOTENT && priority != 0) {
  663. msg_warn_cache("priority has been set for idempotent symbol %s: %d",
  664. static_string_name.c_str(), priority);
  665. }
  666. if ((real_type_pair.second & SYMBOL_TYPE_FINE) && priority == 0) {
  667. /* Adjust priority for negative weighted symbols */
  668. priority = 1;
  669. }
  670. if (items_by_symbol.contains(static_string_name)) {
  671. msg_err_cache("duplicate symbol name: %s", static_string_name.data());
  672. return -1;
  673. }
  674. auto id = items_by_id.size();
  675. auto item = cache_item::create_with_function(static_pool, id,
  676. std::move(static_string_name),
  677. priority, func, user_data,
  678. real_type_pair.first, real_type_pair.second);
  679. items_by_symbol.emplace(item->get_name(), item.get());
  680. get_item_specific_vector(*item).push_back(item.get());
  681. items_by_id.emplace(id, std::move(item));// Takes ownership
  682. if (!(real_type_pair.second & SYMBOL_TYPE_NOSTAT)) {
  683. cksum = t1ha(name.data(), name.size(), cksum);
  684. stats_symbols_count++;
  685. }
  686. return id;
  687. }
  688. auto symcache::add_virtual_symbol(std::string_view name, int parent_id, int flags_and_type) -> int
  689. {
  690. if (name.empty()) {
  691. msg_err_cache("cannot register a virtual symbol with no name; qed");
  692. return -1;
  693. }
  694. auto real_type_pair_maybe = item_type_from_c(flags_and_type);
  695. if (!real_type_pair_maybe.has_value()) {
  696. msg_err_cache("incompatible flags when adding %s: %s", name.data(),
  697. real_type_pair_maybe.error().c_str());
  698. return -1;
  699. }
  700. auto real_type_pair = real_type_pair_maybe.value();
  701. if (items_by_symbol.contains(name)) {
  702. msg_err_cache("duplicate symbol name: %s", name.data());
  703. return -1;
  704. }
  705. if (items_by_id.size() < parent_id) {
  706. msg_err_cache("parent id %d is out of bounds for virtual symbol %s", parent_id, name.data());
  707. return -1;
  708. }
  709. auto id = items_by_id.size();
  710. auto item = cache_item::create_with_virtual(static_pool,
  711. id,
  712. std::string{name},
  713. parent_id, real_type_pair.first, real_type_pair.second);
  714. const auto &parent = items_by_id[parent_id].get();
  715. parent->add_child(item.get());
  716. items_by_symbol.emplace(item->get_name(), item.get());
  717. get_item_specific_vector(*item).push_back(item.get());
  718. items_by_id.emplace(id, std::move(item));// Takes ownership
  719. return id;
  720. }
  721. auto symcache::set_peak_cb(int cbref) -> void
  722. {
  723. if (peak_cb != -1) {
  724. luaL_unref(L, LUA_REGISTRYINDEX, peak_cb);
  725. }
  726. peak_cb = cbref;
  727. msg_info_cache("registered peak callback");
  728. }
  729. auto symcache::add_delayed_condition(std::string_view sym, int cbref) -> void
  730. {
  731. delayed_conditions->emplace_back(sym, cbref, (lua_State *) cfg->lua_state);
  732. }
  733. auto symcache::validate(bool strict) -> bool
  734. {
  735. total_weight = 1.0;
  736. for (auto &pair: items_by_symbol) {
  737. auto &item = pair.second;
  738. auto skipped = item->st->weight != 0;
  739. if (item->is_scoreable() && g_hash_table_lookup(cfg->symbols, item->symbol.c_str()) == nullptr) {
  740. if (!std::isnan(cfg->unknown_weight)) {
  741. item->st->weight = cfg->unknown_weight;
  742. auto *s = rspamd_mempool_alloc0_type(static_pool,
  743. struct rspamd_symbol);
  744. /* Legit as we actually never modify this data */
  745. s->name = (char *) item->symbol.c_str();
  746. s->weight_ptr = &item->st->weight;
  747. g_hash_table_insert(cfg->symbols, (void *) s->name, (void *) s);
  748. msg_info_cache("adding unknown symbol %s with weight: %.2f",
  749. item->symbol.c_str(), cfg->unknown_weight);
  750. skipped = false;
  751. }
  752. else {
  753. /* No `unknown weight`, no static score, and no dynamic score */
  754. skipped = true;
  755. }
  756. }
  757. else {
  758. /* We have a score, so we are not skipped */
  759. skipped = false;
  760. }
  761. if (skipped) {
  762. if (!(item->flags & SYMBOL_TYPE_SKIPPED)) {
  763. item->flags |= SYMBOL_TYPE_SKIPPED;
  764. msg_warn_cache("symbol %s has no score registered, skip its check",
  765. item->symbol.c_str());
  766. }
  767. }
  768. if (item->st->weight < 0 && item->priority == 0) {
  769. item->priority++;
  770. }
  771. if (item->is_virtual()) {
  772. if (!(item->flags & SYMBOL_TYPE_GHOST)) {
  773. auto *parent = const_cast<cache_item *>(item->get_parent(*this));
  774. if (parent == nullptr) {
  775. item->resolve_parent(*this);
  776. parent = const_cast<cache_item *>(item->get_parent(*this));
  777. }
  778. if (::fabs(parent->st->weight) < ::fabs(item->st->weight)) {
  779. parent->st->weight = item->st->weight;
  780. }
  781. auto p1 = ::abs(item->priority);
  782. auto p2 = ::abs(parent->priority);
  783. if (p1 != p2) {
  784. parent->priority = MAX(p1, p2);
  785. item->priority = parent->priority;
  786. }
  787. }
  788. }
  789. total_weight += fabs(item->st->weight);
  790. }
  791. /* Now check each metric item and find corresponding symbol in a cache */
  792. auto ret = true;
  793. GHashTableIter it;
  794. void *k, *v;
  795. g_hash_table_iter_init(&it, cfg->symbols);
  796. while (g_hash_table_iter_next(&it, &k, &v)) {
  797. auto ignore_symbol = false;
  798. auto sym_def = (struct rspamd_symbol *) v;
  799. if (sym_def && (sym_def->flags &
  800. (RSPAMD_SYMBOL_FLAG_IGNORE_METRIC | RSPAMD_SYMBOL_FLAG_DISABLED))) {
  801. ignore_symbol = true;
  802. }
  803. if (!ignore_symbol) {
  804. if (!items_by_symbol.contains((const char *) k)) {
  805. msg_debug_cache(
  806. "symbol '%s' has its score defined but there is no "
  807. "corresponding rule registered",
  808. k);
  809. }
  810. }
  811. else if (sym_def->flags & RSPAMD_SYMBOL_FLAG_DISABLED) {
  812. auto item = get_item_by_name_mut((const char *) k, false);
  813. if (item) {
  814. item->internal_flags &= ~cache_item::bit_enabled;
  815. }
  816. }
  817. }
  818. return ret;
  819. }
  820. auto symcache::counters() const -> ucl_object_t *
  821. {
  822. auto *top = ucl_object_typed_new(UCL_ARRAY);
  823. constexpr const auto round_float = [](const auto x, const int digits) -> auto {
  824. const auto power10 = ::pow(10, digits);
  825. return (::floor(x * power10) / power10);
  826. };
  827. for (auto &pair: items_by_symbol) {
  828. auto &item = pair.second;
  829. auto symbol = pair.first;
  830. auto *obj = ucl_object_typed_new(UCL_OBJECT);
  831. ucl_object_insert_key(obj, ucl_object_fromlstring(symbol.data(), symbol.size()),
  832. "symbol", 0, false);
  833. if (item->is_virtual()) {
  834. if (!(item->flags & SYMBOL_TYPE_GHOST)) {
  835. const auto *parent = item->get_parent(*this);
  836. ucl_object_insert_key(obj,
  837. ucl_object_fromdouble(round_float(item->st->weight, 3)),
  838. "weight", 0, false);
  839. ucl_object_insert_key(obj,
  840. ucl_object_fromdouble(round_float(parent->st->avg_frequency, 3)),
  841. "frequency", 0, false);
  842. ucl_object_insert_key(obj,
  843. ucl_object_fromint(parent->st->total_hits),
  844. "hits", 0, false);
  845. ucl_object_insert_key(obj,
  846. ucl_object_fromdouble(round_float(parent->st->avg_time, 3)),
  847. "time", 0, false);
  848. }
  849. else {
  850. ucl_object_insert_key(obj,
  851. ucl_object_fromdouble(round_float(item->st->weight, 3)),
  852. "weight", 0, false);
  853. ucl_object_insert_key(obj,
  854. ucl_object_fromdouble(0.0),
  855. "frequency", 0, false);
  856. ucl_object_insert_key(obj,
  857. ucl_object_fromdouble(0.0),
  858. "hits", 0, false);
  859. ucl_object_insert_key(obj,
  860. ucl_object_fromdouble(0.0),
  861. "time", 0, false);
  862. }
  863. }
  864. else {
  865. ucl_object_insert_key(obj,
  866. ucl_object_fromdouble(round_float(item->st->weight, 3)),
  867. "weight", 0, false);
  868. ucl_object_insert_key(obj,
  869. ucl_object_fromdouble(round_float(item->st->avg_frequency, 3)),
  870. "frequency", 0, false);
  871. ucl_object_insert_key(obj,
  872. ucl_object_fromint(item->st->total_hits),
  873. "hits", 0, false);
  874. ucl_object_insert_key(obj,
  875. ucl_object_fromdouble(round_float(item->st->avg_time, 3)),
  876. "time", 0, false);
  877. }
  878. ucl_array_append(top, obj);
  879. }
  880. return top;
  881. }
  882. auto symcache::periodic_resort(struct ev_loop *ev_loop, double cur_time, double last_resort) -> void
  883. {
  884. for (const auto &item: filters) {
  885. if (item->update_counters_check_peak(L, ev_loop, cur_time, last_resort)) {
  886. auto cur_value = (item->st->total_hits - item->last_count) /
  887. (cur_time - last_resort);
  888. auto cur_err = (item->st->avg_frequency - cur_value);
  889. cur_err *= cur_err;
  890. msg_debug_cache("peak found for %s is %.2f, avg: %.2f, "
  891. "stddev: %.2f, error: %.2f, peaks: %d",
  892. item->symbol.c_str(), cur_value,
  893. item->st->avg_frequency,
  894. item->st->stddev_frequency,
  895. cur_err,
  896. item->frequency_peaks);
  897. if (peak_cb != -1) {
  898. struct ev_loop **pbase;
  899. lua_rawgeti(L, LUA_REGISTRYINDEX, peak_cb);
  900. pbase = (struct ev_loop **) lua_newuserdata(L, sizeof(*pbase));
  901. *pbase = ev_loop;
  902. rspamd_lua_setclass(L, rspamd_ev_base_classname, -1);
  903. lua_pushlstring(L, item->symbol.c_str(), item->symbol.size());
  904. lua_pushnumber(L, item->st->avg_frequency);
  905. lua_pushnumber(L, ::sqrt(item->st->stddev_frequency));
  906. lua_pushnumber(L, cur_value);
  907. lua_pushnumber(L, cur_err);
  908. if (lua_pcall(L, 6, 0, 0) != 0) {
  909. msg_info_cache("call to peak function for %s failed: %s",
  910. item->symbol.c_str(), lua_tostring(L, -1));
  911. lua_pop(L, 1);
  912. }
  913. }
  914. }
  915. }
  916. }
  917. symcache::~symcache()
  918. {
  919. if (peak_cb != -1) {
  920. luaL_unref(L, LUA_REGISTRYINDEX, peak_cb);
  921. }
  922. }
  923. auto symcache::maybe_resort() -> bool
  924. {
  925. if (items_by_order->generation_id != cur_order_gen) {
  926. /*
  927. * Cache has been modified, need to resort it
  928. */
  929. msg_info_cache("symbols cache has been modified since last check:"
  930. " old id: %ud, new id: %ud",
  931. items_by_order->generation_id, cur_order_gen);
  932. resort();
  933. return true;
  934. }
  935. return false;
  936. }
  937. auto symcache::get_item_specific_vector(const cache_item &it) -> symcache::items_ptr_vec &
  938. {
  939. switch (it.get_type()) {
  940. case symcache_item_type::CONNFILTER:
  941. return connfilters;
  942. case symcache_item_type::FILTER:
  943. return filters;
  944. case symcache_item_type::IDEMPOTENT:
  945. return idempotent;
  946. case symcache_item_type::PREFILTER:
  947. return prefilters;
  948. case symcache_item_type::POSTFILTER:
  949. return postfilters;
  950. case symcache_item_type::COMPOSITE:
  951. return composites;
  952. case symcache_item_type::CLASSIFIER:
  953. return classifiers;
  954. case symcache_item_type::VIRTUAL:
  955. return virtual_symbols;
  956. }
  957. RSPAMD_UNREACHABLE;
  958. }
  959. auto symcache::process_settings_elt(struct rspamd_config_settings_elt *elt) -> void
  960. {
  961. auto id = elt->id;
  962. if (elt->symbols_disabled) {
  963. /* Process denied symbols */
  964. ucl_object_iter_t iter = nullptr;
  965. const ucl_object_t *cur;
  966. while ((cur = ucl_object_iterate(elt->symbols_disabled, &iter, true)) != NULL) {
  967. const auto *sym = ucl_object_key(cur);
  968. auto *item = get_item_by_name_mut(sym, false);
  969. if (item != nullptr) {
  970. if (item->is_virtual()) {
  971. /*
  972. * Virtual symbols are special:
  973. * we ignore them in symcache but prevent them from being
  974. * inserted.
  975. */
  976. item->forbidden_ids.add_id(id);
  977. msg_debug_cache("deny virtual symbol %s for settings %ud (%s); "
  978. "parent can still be executed",
  979. sym, id, elt->name);
  980. }
  981. else {
  982. /* Normal symbol, disable it */
  983. item->forbidden_ids.add_id(id);
  984. msg_debug_cache("deny symbol %s for settings %ud (%s)",
  985. sym, id, elt->name);
  986. }
  987. }
  988. else {
  989. msg_warn_cache("cannot find a symbol to disable %s "
  990. "when processing settings %ud (%s)",
  991. sym, id, elt->name);
  992. }
  993. }
  994. }
  995. if (elt->symbols_enabled) {
  996. ucl_object_iter_t iter = nullptr;
  997. const ucl_object_t *cur;
  998. while ((cur = ucl_object_iterate(elt->symbols_enabled, &iter, true)) != nullptr) {
  999. /* Here, we resolve parent and explicitly allow it */
  1000. const auto *sym = ucl_object_key(cur);
  1001. auto *item = get_item_by_name_mut(sym, false);
  1002. if (item != nullptr) {
  1003. if (item->is_virtual()) {
  1004. auto *parent = get_item_by_name_mut(sym, true);
  1005. if (parent) {
  1006. if (elt->symbols_disabled &&
  1007. ucl_object_lookup(elt->symbols_disabled, parent->symbol.data())) {
  1008. msg_err_cache("conflict in %s: cannot enable disabled symbol %s, "
  1009. "wanted to enable symbol %s",
  1010. elt->name, parent->symbol.data(), sym);
  1011. continue;
  1012. }
  1013. parent->exec_only_ids.add_id(id);
  1014. msg_debug_cache("allow just execution of symbol %s for settings %ud (%s)",
  1015. parent->symbol.data(), id, elt->name);
  1016. }
  1017. }
  1018. item->allowed_ids.add_id(id);
  1019. msg_debug_cache("allow execution of symbol %s for settings %ud (%s)",
  1020. sym, id, elt->name);
  1021. }
  1022. else {
  1023. msg_warn_cache("cannot find a symbol to enable %s "
  1024. "when processing settings %ud (%s)",
  1025. sym, id, elt->name);
  1026. }
  1027. }
  1028. }
  1029. }
  1030. auto symcache::get_max_timeout(std::vector<std::pair<double, const cache_item *>> &elts) const -> double
  1031. {
  1032. auto accumulated_timeout = 0.0;
  1033. auto log_func = RSPAMD_LOG_FUNC;
  1034. ankerl::unordered_dense::set<const cache_item *> seen_items;
  1035. auto get_item_timeout = [](cache_item *it) {
  1036. return it->get_numeric_augmentation("timeout").value_or(0.0);
  1037. };
  1038. /* This function returns the timeout for an item and all it's dependencies */
  1039. auto get_filter_timeout = [&](cache_item *it, auto self) -> double {
  1040. auto own_timeout = get_item_timeout(it);
  1041. auto max_child_timeout = 0.0;
  1042. for (const auto &[id, dep]: it->deps) {
  1043. auto cld_timeout = self(dep.item, self);
  1044. if (cld_timeout > max_child_timeout) {
  1045. max_child_timeout = cld_timeout;
  1046. }
  1047. }
  1048. return own_timeout + max_child_timeout;
  1049. };
  1050. /* For prefilters and postfilters, we just care about priorities */
  1051. auto pre_postfilter_iter = [&](const items_ptr_vec &vec) -> double {
  1052. auto saved_priority = -1;
  1053. auto max_timeout = 0.0, added_timeout = 0.0;
  1054. const cache_item *max_elt = nullptr;
  1055. for (const auto &it: vec) {
  1056. if (it->priority != saved_priority && max_elt != nullptr && max_timeout > 0) {
  1057. if (!seen_items.contains(max_elt)) {
  1058. accumulated_timeout += max_timeout;
  1059. added_timeout += max_timeout;
  1060. msg_debug_cache_lambda("added %.2f to the timeout (%.2f) as the priority has changed (%d -> %d); "
  1061. "symbol: %s",
  1062. max_timeout, accumulated_timeout, saved_priority, it->priority,
  1063. max_elt->symbol.c_str());
  1064. elts.emplace_back(max_timeout, max_elt);
  1065. seen_items.insert(max_elt);
  1066. }
  1067. max_timeout = 0;
  1068. saved_priority = it->priority;
  1069. max_elt = nullptr;
  1070. }
  1071. auto timeout = get_item_timeout(it);
  1072. if (timeout > max_timeout) {
  1073. max_timeout = timeout;
  1074. max_elt = it;
  1075. }
  1076. }
  1077. if (max_elt != nullptr && max_timeout > 0) {
  1078. if (!seen_items.contains(max_elt)) {
  1079. accumulated_timeout += max_timeout;
  1080. added_timeout += max_timeout;
  1081. msg_debug_cache_lambda("added %.2f to the timeout (%.2f) end of processing; "
  1082. "symbol: %s",
  1083. max_timeout, accumulated_timeout,
  1084. max_elt->symbol.c_str());
  1085. elts.emplace_back(max_timeout, max_elt);
  1086. seen_items.insert(max_elt);
  1087. }
  1088. }
  1089. return added_timeout;
  1090. };
  1091. auto prefilters_timeout = pre_postfilter_iter(this->prefilters);
  1092. /* For normal filters, we check the maximum chain of the dependencies
  1093. * This function might have O(N^2) complexity if all symbols are in a single
  1094. * dependencies chain. But it is not the case in practice
  1095. */
  1096. double max_filters_timeout = 0;
  1097. for (const auto &it: this->filters) {
  1098. auto timeout = get_filter_timeout(it, get_filter_timeout);
  1099. if (timeout > max_filters_timeout) {
  1100. max_filters_timeout = timeout;
  1101. if (!seen_items.contains(it)) {
  1102. elts.emplace_back(timeout, it);
  1103. seen_items.insert(it);
  1104. }
  1105. }
  1106. }
  1107. accumulated_timeout += max_filters_timeout;
  1108. auto postfilters_timeout = pre_postfilter_iter(this->postfilters);
  1109. auto idempotent_timeout = pre_postfilter_iter(this->idempotent);
  1110. /* Sort in decreasing order by timeout */
  1111. std::stable_sort(std::begin(elts), std::end(elts),
  1112. [](const auto &p1, const auto &p2) {
  1113. return p1.first > p2.first;
  1114. });
  1115. msg_debug_cache("overall cache timeout: %.2f, %.2f from prefilters,"
  1116. " %.2f from postfilters, %.2f from idempotent filters,"
  1117. " %.2f from normal filters",
  1118. accumulated_timeout, prefilters_timeout, postfilters_timeout,
  1119. idempotent_timeout, max_filters_timeout);
  1120. return accumulated_timeout;
  1121. }
  1122. }// namespace rspamd::symcache