The IRC wilderness is hostile. Netsplits crash in like Dementors, draining the botโs strength. Antiflood guards misfire on edge cases. Cursors leak into the void. This release draws its wand and answers: five netsplit fixes, three bug corrections, three hardening improvements, and every guard double-checked.
โLumos Maxima! The darkness of netsplits retreats. The bot stands firm.โ ๐ฐโก๐
During a netsplit, the server sends dozens of QUIT messages with the pattern
"server1.net server2.net". Each one was previously triggering:
logBotAction โ DB writeupdateUserSeen โ DB writeauth->logout โ session clearOn a 100-nick channel that meant 200+ synchronous DB queries in seconds, plus valid Claude conversation histories being silently destroyed for users who hadnโt actually left.
# NS1: detect netsplit QUIT
my $is_netsplit = ($text =~ /^\S+\.\S+\s+\S+\.\S+$/);
if ($is_netsplit) {
# Skip DB ops entirely โ just remove from in-memory nicklist
$mediabot->channelNicksRemove($sChannel, $sNick);
return;
}
NS5: Claude history is now only purged on genuine QUITs โ users who survive the split keep their conversation context intact.
A mediabot_netsplit_quits_total Prometheus counter tracks how many
netsplit QUITs are absorbed per session.
reconnect() now explicitly wipes all in-memory flood state before
rebuilding the IRC object:
$mediabot->{_af} = {}; # AF1: output flood state
$mediabot->{_af_params} = {}; # AF1: params cache
$mediabot->{_chan_flood} = {}; # AF4: input flood state
$mediabot->{_nick_flood} = {}; # AF3: per-nick sliding window
$mediabot->{_nick_mute} = {}; # CC3: auto-mutes
$mediabot->{_cmd_cooldown} = {}; # CC1: command cooldowns
Without this, a channel silenced by AF4 during the split would remain
silenced indefinitely after reconnect โ the bot would ignore all commands
on that channel until a manual .floodstatus inspection and restart.
The old joinChannels() sent all JOINs in a tight synchronous loop.
On large channel lists after a split, this could trigger server-side
flood protection and get the bot disconnected immediately after reconnecting.
The new implementation staggers each JOIN 1.5 seconds apart using
IO::Async::Timer::Countdown:
JOIN #boulets t=0s
JOIN #testchan t=1.5s
JOIN #quebec t=3.0s
JOIN #epiknet t=4.5s
The Partyline and DB listener remain alive throughout โ only the IRC joins are deferred.
During a netsplit, nicks on the remote side of the split quit without sending QUIT messages. The local nicklist accumulates ghosts until the next periodic refresh (default: 300s).
After each throttled JOIN, a WHO #channel is now automatically sent
3 seconds later:
# NS4: schedule WHO after join to sync nicklist
my $who_t = IO::Async::Timer::Countdown->new(
delay => 3,
on_expire => sub {
eval { $irc->send_message('WHO', undef, $chan_name) }
if $irc && $irc->is_connected;
},
);
This ensures the nicklist is accurate within seconds of rejoining, not minutes.
.netsplit โ Live Diagnostics.netsplit
--- Netsplit state ---
Netsplit QUITs since last reconnect: 47
AF1 channels in state: 3
AF4 channels in state: 0
--- Channel nicklist status ---
#boulets 18 nicks
#quebec 34 nicks
#testchan 5 nicks
A single command to assess the damage after a split.
mp3_ctx Cursor Leakmp3_ctx (search sub-command) uses two statement handles: $sth_count
for counting results and $sth for fetching the first match. On the
happy path (match found, results displayed, function returns), $sth
was never closed:
# B-68-1/fix: ensure sth_first cursor is closed
$sth->finish if $sth;
return;
!triviastop Left Scores Behind!triviastop (Master) stopped the active game but did not clear the
scores hash or reset hint_given. Immediately starting a new game
with !trivia start N would:
hint_given was still 1)# B-68-2/fix
delete $trivia->{scores};
$trivia->{hint_given} = 0;
.floodset window=0 Accepted Without Clamp.floodset #chan 0 4 30 stored window=0 in _chan_flood_conf.
Because Perlโs // operator tests for undef (not falsiness), the
value 0 was not replaced by the default โ it propagated into
checkChanFlood, making the sliding window infinitely narrow and the
grep always return an empty array.
# B-68-3/fix: clamp CC2 overrides to sane minimums
my $window = do {
my $v = $conf_ov->{window};
defined($v) ? (int($v) >= 1 ? int($v) : 1)
: _af_conf_int($self, 'CHANFLOOD_WINDOW', 10, 1, 3600)
};
Same clamp applied to max_cmds and silence. The Partyline
.floodset command now also warns when values are below 1 and reports
the effective clamped values.
checkCmdCooldown Prometheus Countermediabot_cmdcooldown_blocks_total is now incremented at the moment
the cooldown is enforced โ previously it was only declared in
Metrics.pm but never actually incremented.
| File | Changes |
|---|---|
mediabot.pl |
NS1โNS5: netsplit QUIT filter, reconnect state reset, throttled JOINs, WHO resync |
Helpers.pm |
B-68-1: mp3_ctx cursor fix; B-68-3: checkChanFlood window clamp; A-68-3: cmdcooldown metric |
UserCommands.pm |
B-68-2: triviastop scores/hint reset |
Partyline.pm |
.netsplit command; A-68-1: .floodset clamp+warn; A-68-2: .cmdcooldown range comment |
Metrics.pm |
mediabot_netsplit_quits_total, mediabot_netsplit_rejoins_total |
Small Lumos Maxima erratum: the live suite was already green, but the static guards exposed a few integration leftovers. This patch aligns the internal help table, removes duplicate dispatch/export noise, verifies the real throttled JOIN + WHO netsplit behavior, and keeps the full static + live test suite green.
You must be logged in to reply.