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.

symcache_impl.cxx 37KB

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