svn commit: r1936613 - spamassassin/trunk/lib/Mail/SpamAssassin/Plugin

[email protected] Mon, 27 Jul 2026 07:20:20 -0000
Newsgroups gmane.mail.spam.spamassassin.cvs
Message-ID <178513682076.2894552.14818992064045189878@svn03-he-fi>
Author: gbechis
Date: Mon Jul 27 07:20:20 2026
New Revision: 1936613

Log:
Replace meta-rule full-rescan retry with a dependency-driven work queue

do_meta_tests() restarted its entire foreach over meta_check_ready (or
meta_pending when finishing) via \"goto RULE\" every time any meta
resolved, re-checking the dependency list of every still-pending meta
each time, even ones unaffected by what just resolved. Replace it with
an explicit work queue seeded the same way, where resolving a meta
pushes only the specific metas listed in its meta_deprules reverse-map
(the same map PerMsgStatus::rule_ready() already uses) instead of
rescanning everything.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
initialize queue

Submitted by: Giovanni <[email protected]>

Github: closes #32

Modified:
   spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Check.pm

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Check.pm
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Check.pm	Mon Jul 27 06:32:31 2026	(r1936612)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Check.pm	Mon Jul 27 07:20:20 2026	(r1936613)
@@ -298,9 +298,9 @@ sub do_meta_tests {
   my $mr = $pms->{meta_check_ready};
   my $mp = $pms->{meta_pending};
   my $md = $pms->{conf}->{meta_dependencies};
+  my $mdr = $pms->{conf}->{meta_deprules};
   my $mt = $pms->{conf}->{meta_tests};
   my $h = $pms->{tests_already_hit};
-  my $retry;
 
   # When finishing, first mark all unrun non-meta rules as finished,
   # it will enable the next loop to finish everything properly
@@ -314,14 +314,31 @@ sub do_meta_tests {
     }
   }
 
-RULE:
-  foreach my $rulename ($finish ? keys %$mp : keys %$mr) {
+  # Work queue of metas worth (re)checking, seeded from meta_check_ready
+  # (or all pending metas when finishing). Running a meta can make other
+  # metas newly ready (a meta depending on it); rather than rescanning
+  # every pending/ready meta again on each such change (as a "goto RULE"
+  # restart would), push only the specific metas that depend on whatever
+  # just resolved, via the same meta_deprules reverse-dependency map
+  # PerMsgStatus::rule_ready() uses.
+  my @queue = $finish ? keys %$mp : keys %$mr;
+  my %queued; @queued{@queue} = (1) x @queue;
+
+  while (@queue) {
+    my $rulename = shift @queue;
+    delete $queued{$rulename};
+    next if exists $h->{$rulename};  # already resolved
+
     # Meta is not ready if some dependency has not run yet
+    my $ready = 1;
     foreach my $deprule (@{$md->{$rulename}||[]}) {
       if (!exists $h->{$deprule}) {
-        next RULE;
+        $ready = 0;
+        last;
       }
     }
+    next if !$ready;
+
     # Metasubs look like ($_[1]->{$rulename}||0) ...
     my $result = $mt->{$rulename}->($pms, $h);
     if ($result) {
@@ -333,11 +350,15 @@ RULE:
     }
     delete $mr->{$rulename};
     delete $mp->{$rulename};
-    # Reiterate all metas again, in case some meta depended on us
-    $retry = 1;
-  }
 
-  goto RULE if $retry--;
+    # $rulename just resolved (in %$h); queue only the metas that
+    # specifically depend on it, instead of every pending/ready meta.
+    foreach my $deprule (keys %{$mdr->{$rulename} || {}}) {
+      next if $queued{$deprule};
+      push @queue, $deprule;
+      $queued{$deprule} = 1;
+    }
+  }
 
   delete $pms->{meta_check_ready};
 }