diff options
| author | Sadeep Madurange <sadeep@asciimx.com> | 2026-08-29 15:40:49 +0800 |
|---|---|---|
| committer | Sadeep Madurange <sadeep@asciimx.com> | 2026-08-29 15:40:49 +0800 |
| commit | 47925d204c7eb4e9679514d3d5c943aada6a2b3a (patch) | |
| tree | 9f92c2cec022e904912c02a1ae71dd23cc724efe /mailer.pl | |
| parent | adaefc77ec359bec5b42711d23e7036835592e93 (diff) | |
| download | lex-47925d204c7eb4e9679514d3d5c943aada6a2b3a.tar.gz | |
SR: cap interval, fair scheduling correction.
Diffstat (limited to 'mailer.pl')
| -rw-r--r-- | mailer.pl | 49 |
1 files changed, 42 insertions, 7 deletions
@@ -16,6 +16,8 @@ my $subject = 'Daily word'; my $sendmail = '/usr/sbin/sendmail'; my $syslog_tag = 'lex-mail'; +my $max_interval = 365; # Upper bound limit (1 year) + # Connect to /dev/log before pledge/unveil so no late 'unix' # socket creation occurs openlog($syslog_tag, 'ndelay,pid', LOG_MAIL); @@ -98,12 +100,34 @@ foreach my $file (@current_files) { } } -# Select candidate file (spaced repetition) +# Select candidate file (spaced repetition + fair scheduling) my @due_files = grep { $state{$_}{next_due} le $today } @current_files; my $selected_file; -if (@due_files) { - $selected_file = $due_files[rand @due_files]; +if (@due_files == 1) { + $selected_file = $due_files[0]; +} elsif (@due_files > 1) { + # Weighted random choice: files with fewer reviews get higher probability weights + my %weights; + my $total_weight = 0; + + foreach my $file (@due_files) { + # Inversely proportional weight (1 / (reviews + 1)) + my $w = 1.0 / ($state{$file}{reviews} + 1); + $weights{$file} = $w; + $total_weight += $w; + } + + my $rand_point = rand($total_weight); + my $accum = 0; + foreach my $file (@due_files) { + $accum += $weights{$file}; + if ($rand_point <= $accum) { + $selected_file = $file; + last; + } + } + $selected_file //= $due_files[-1]; # Fallback } else { @current_files = sort { $state{$a}{next_due} cmp $state{$b}{next_due} } @current_files; $selected_file = $current_files[0]; @@ -150,6 +174,11 @@ if ($item->{reviews} == 0) { $interval = int($interval * $factor); } +# Cap interval +if ($interval > $max_interval) { + $interval = $max_interval; +} + $item->{interval} = $interval; $item->{reviews} += 1; @@ -161,15 +190,21 @@ foreach my $file (keys %state) { delete $state{$file} unless $active_files{$file}; } +# Atomic write using a temporary state file +my $tmp_file = "$state_file.tmp"; my $out_fh; -if (!open($out_fh, '>', $state_file)) { - log_die("Cannot write to $state_file: $!"); +if (!open($out_fh, '>', $tmp_file)) { + log_die("Cannot write to $tmp_file: $!"); } foreach my $file (keys %state) { my $i = $state{$file}; print $out_fh join("\t", $file, $i->{next_due}, $i->{interval}, $i->{reviews}, $i->{factor}), "\n"; } -close($out_fh); +close($out_fh) or log_die("Error closing $tmp_file: $!"); + +if (!rename($tmp_file, $state_file)) { + log_die("Cannot atomic swap $tmp_file to $state_file: $!"); +} syslog(LOG_INFO, "Mailed %s to %s (next due: %s)", $selected_file, $to, $item->{next_due}); -closelog(); +closelog();
\ No newline at end of file |
