Refactor PBot::Timer into PBot::EventQueue

Make better use of PBot::IRC's select loop
Remove use of SIGALRM
This commit is contained in:
Pragmatic Software 2021-06-21 17:26:24 -07:00
parent dcd07d9a68
commit 1a41f9aebb
24 changed files with 206 additions and 199 deletions

View File

@ -36,7 +36,7 @@ sub initialize {
$self->{'ban-exemptions'} = PBot::DualIndexHashObject->new(name => 'Ban exemptions', filename => $filename, pbot => $self->{pbot});
$self->{'ban-exemptions'}->load;
$self->{pbot}->{timer}->register(sub { $self->adjust_offenses }, 60 * 60 * 1, 'AntiFlood Adjust Offenses');
$self->{pbot}->{event_queue}->enqueue(sub { $self->adjust_offenses }, 60 * 60 * 1, 'Adjust anti-flood offenses');
$self->{pbot}->{registry}->add_default('text', 'antiflood', 'enforce', $conf{enforce_antiflood} // 1);

View File

@ -59,7 +59,7 @@ sub initialize {
$self->{ban_queue} = {};
$self->{unban_queue} = {};
$self->{pbot}->{timer}->register(sub { $self->flush_unban_queue }, 30, 'Unban Queue');
$self->{pbot}->{event_queue}->enqueue(sub { $self->flush_unban_queue }, 30, 'Flush unban queue');
}
sub cmd_banlist {
@ -203,7 +203,7 @@ sub compare_banlist {
$self->{pbot}->{logger}->log("BanList: Saved ban +b $mask no longer exists in $channel.\n");
# TODO option to restore ban
$self->{banlist}->remove($channel, $mask, undef, 1);
$self->{pbot}->{timer}->dequeue_event("unban $channel $mask");
$self->{pbot}->{event_queue}->dequeue_event("unban $channel $mask");
}
}
@ -248,7 +248,7 @@ sub compare_quietlist {
$self->{pbot}->{logger}->log("BanList: Saved quiet +q $mask no longer exists in $channel.\n");
# TODO option to restore quiet
$self->{quietlist}->remove($channel, $mask, undef, 1);
$self->{pbot}->{timer}->dequeue_event("unmute $channel $mask");
$self->{pbot}->{event_queue}->dequeue_event("unmute $channel $mask");
}
}
@ -294,15 +294,15 @@ sub track_mode {
if ($mode eq "-b") {
$self->{banlist}->remove($channel, $mask);
$self->{pbot}->{timer}->dequeue_event("unban $channel $mask");
$self->{pbot}->{event_queue}->dequeue_event("unban $channel $mask");
# freenode strips channel forwards from unban result if no ban exists with a channel forward
my $join_flood_channel = $self->{pbot}->{registry}->get_value('antiflood', 'join_flood_channel') // '#stop-join-flood';
$self->{banlist}->remove($channel, "$mask\$$join_flood_channel");
$self->{pbot}->{timer}->dequeue_event(lc "unban $channel $mask\$$join_flood_channel");
$self->{pbot}->{event_queue}->dequeue_event(lc "unban $channel $mask\$$join_flood_channel");
} elsif ($mode eq "-$mute_char") {
$self->{quietlist}->remove($channel, $mask);
$self->{pbot}->{timer}->dequeue_event("unmute $channel $mask");
$self->{pbot}->{event_queue}->dequeue_event("unmute $channel $mask");
}
}
@ -312,7 +312,7 @@ sub track_mode {
if ($nick eq "ChanServ" or $mask =~ m/##fix_your_connection$/i) {
if ($self->{banlist}->exists($channel, $mask)) {
$self->{banlist}->set($channel, $mask, 'timeout', gettimeofday + $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
$self->{pbot}->{timer}->update_interval("unban $channel $mask", $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
$self->{pbot}->{event_queue}->update_interval("unban $channel $mask", $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
} else {
my $data = {
reason => 'Temp ban for banned-by-ChanServ or mask is *!*@*##fix_your_connection',
@ -349,7 +349,7 @@ sub track_mode {
$self->{pbot}->{logger}->log("WEIRD MUTE THING $nick...\n");
if ($self->{quietlist}->exists($channel, $mask)) {
$self->{quietlist}->set($channel, $mask, 'timeout', gettimeofday + $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
$self->{pbot}->{timer}->update_interval("unmute $channel $mask", $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
$self->{pbot}->{event_queue}->update_interval("unmute $channel $mask", $self->{pbot}->{registry}->get_value('banlist', 'chanserv_ban_timeout'));
} else {
my $data = {
reason => 'Temp mute',
@ -557,7 +557,7 @@ sub ban_user_timed {
}
my $method = $mode eq 'b' ? 'unban' : 'unmute';
$self->{pbot}->{timer}->dequeue_event("$method $channel $mask");
$self->{pbot}->{event_queue}->dequeue_event("$method $channel $mask");
if ($length > 0) {
$self->enqueue_unban($channel, $mode, $mask, $length);
@ -704,9 +704,9 @@ sub enqueue_unban {
my $method = $mode eq 'b' ? 'unban' : 'unmute';
$self->{pbot}->{timer}->enqueue_event(
$self->{pbot}->{event_queue}->enqueue_event(
sub {
$self->{pbot}->{timer}->update_interval("$method $channel $hostmask", 60 * 15, 1); # try again in 15 minutes
$self->{pbot}->{event_queue}->update_interval("$method $channel $hostmask", 60 * 15, 1); # try again in 15 minutes
return if not $self->{pbot}->{joined_channels};
$self->unban_user($channel, $mode, $hostmask);
}, $interval, "$method $channel $hostmask", 1

View File

@ -27,7 +27,8 @@ sub initialize {
$self->{pbot}->{registry}->add_default('text', 'general', 'deop_timeout', 300);
$self->{pbot}->{timer}->register(sub { $self->check_opped_timeouts }, 10, 'Check Opped Timeouts');
# TODO: enqueue OP events as needed instead of naively checking every 10 seconds
$self->{pbot}->{event_queue}->enqueue(sub { $self->check_opped_timeouts }, 10, 'Check opped timeouts');
}
sub track_mode {

View File

@ -82,8 +82,8 @@ sub cmd_remove {
# clear banlists
$self->{pbot}->{banlist}->{banlist}->remove($context->{arguments});
$self->{pbot}->{banlist}->{quietlist}->remove($context->{arguments});
$self->{pbot}->{timer}->dequeue_event("unban $context->{arguments} .*");
$self->{pbot}->{timer}->dequeue_event("unmute $context->{arguments} .*");
$self->{pbot}->{event_queue}->dequeue_event("unban $context->{arguments} .*");
$self->{pbot}->{event_queue}->dequeue_event("unmute $context->{arguments} .*");
# TODO: ignores, etc?
return $self->{channels}->remove($context->{arguments});

View File

@ -125,7 +125,7 @@ sub save {
if ($self->{save_queue_timeout}) {
# enqueue the save to prevent save-thrashing
$self->{pbot}->{timer}->replace_subref_or_enqueue_event(
$self->{pbot}->{enqueue_event}->replace_subref_or_enqueue_event(
$subref,
$self->{save_queue_timeout},
"save $self->{name}",

View File

@ -39,7 +39,7 @@ sub initialize {
$self->{pbot}->{registry}->add_trigger('dualindexsqliteobject', "debug_$self->{name}", sub { $self->sqlite_debug_trigger(@_) });
$self->{pbot}->{atexit}->register(sub { $self->end; return; });
$self->{pbot}->{timer}->register(sub {$self->trim_cache }, 60, "DualIndexSQLiteObject $self->{name} Timer");
$self->{pbot}->{event_queue}->enqueue(sub {$self->trim_cache }, 60, "Trim $self->{name} cache");
$self->begin;
}
@ -80,7 +80,7 @@ sub end {
$self->{dbh} = undef;
}
$self->{pbot}->{timer}->unregister("DualIndexSQLiteObject $self->{name} Timer");
$self->{pbot}->{event_queue}->dequeue("Trim $self->{name} cache");
}
sub load {

View File

@ -1,6 +1,8 @@
# File: EventDispatcher.pm
#
# Purpose: Registers event handlers and dispatches events to them.
#
# Note: PBot::EventDispatcher has no relation to PBot::EventQueue.
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -1,50 +1,33 @@
# File: Timer.pm
# File: EventQueue.pm
#
# Purpose: Provides functionality to register subroutines/events to be invoked
# Purpose: Provides functionality to manage event subroutines which are invoked
# at a future time, optionally recurring.
#
# If no subroutines/events are registered/enqueued, the default on_tick()
# method, which can be overridden, is invoked.
#
# Uses own internal seconds counter and relative-intervals to avoid
# timeout desyncs due to system clock changes.
#
# Note: Uses ALARM signal.
# Note: PBot::EventQueue has no relation to PBot::EventDispatcher.
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package PBot::Timer;
package PBot::EventQueue;
use parent 'PBot::Class';
use PBot::Imports;
use Time::Duration qw/concise duration/;
our $seconds //= 0;
our $waitfor //= 1;
our @timer_funcs;
# alarm signal handler (poor-man's timer)
$SIG{ALRM} = sub {
$seconds += $waitfor;
foreach my $func (@timer_funcs) { &$func; }
};
use Time::HiRes qw/time/;
use Time::Duration;
sub initialize {
my ($self, %conf) = @_;
my $timeout = $conf{timeout} // 10;
$self->{name} = $conf{name} // "Unnamed ${timeout}s Timer";
$self->{enabled} = 0;
# array of pending events
$self->{event_queue} = [];
$self->{last} = $seconds;
$self->{timeout} = $timeout;
$self->{pbot}->{commands}->register(sub { $self->cmd_eventqueue(@_) }, 'eventqueue', 1);
# register `eventqueue` bot command
$self->{pbot}->{commands}->register(sub { $self->cmd_eventqueue(@_) }, 'eventqueue', 1);
# add `can-eventqueue` capability to admin group
$self->{pbot}->{capabilities}->add('admin', 'can-eventqueue', 1);
$self->{timer_func} = sub { $self->on_tick_handler(@_) };
}
sub cmd_eventqueue {
@ -63,6 +46,7 @@ sub cmd_eventqueue {
my $result = eval {
my $text = "Queued events:\n";
my ($regex) = $self->{pbot}->{interpreter}->shift_arg($context->{arglist});
my $i = 0;
@ -76,19 +60,29 @@ sub cmd_eventqueue {
$events++;
my $duration = concise duration $event->{timeout} - $seconds;
$text .= " $i) in $duration: $event->{id}";
my $duration = $event->{timeout} - time;
if ($duration < 0) {
# current time has passed an event's time but the
# event hasn't left the queue yet. we'll show these
# as, e.g., "pending 5s ago"
$duration = 'pending ' . concise ago -$duration;
} else {
$duration = 'in ' . concise duration $duration;
}
$text .= " $i) $duration: $event->{id}";
$text .= ' [R]' if $event->{repeating};
$text .= ";\n";
}
return "No events found." if $events == 0;
return $text;
return $text . "$events events.\n";
};
if ($@) {
my $error = $@;
if (my $error = $@) {
# strip source information to prettify error for non-developer consumption
$error =~ s/ at PBot.*//;
return "Bad regex: $error";
}
@ -98,22 +92,28 @@ sub cmd_eventqueue {
if ($command eq 'add') {
my ($duration, $command) = $self->{pbot}->{interpreter}->split_args($context->{arglist}, 2);
return "Usage: eventqueue add <relative time> <command> [-repeat]" if not defined $duration or not defined $command;
my ($delay, $error) = $self->{pbot}->{parsedate}->parsedate($duration);
if (not defined $duration or not defined $command) {
return "Usage: eventqueue add <relative time> <command> [-repeat]";
}
# convert text like "5 minutes" or "1 week" or "next tuesday" to seconds
my ($seconds, $error) = $self->{pbot}->{parsedate}->parsedate($duration);
return $error if defined $error;
my $repeating = 0;
$repeating = 1 if $command =~ s/^-repeat\s+|\s+-repeat$//g;
# check for `-repeating` at front or end of command
my $repeating = $command =~ s/^-repeat\s+|\s+-repeat$//g;
my $cmd = {
nick => $context->{nick},
user => $context->{user},
host => $context->{host},
command => $command,
nick => $context->{nick},
user => $context->{user},
host => $context->{host},
hostmask => $context->{hostmask},
command => $command,
};
$self->{pbot}->{interpreter}->add_to_command_queue($context->{from}, $cmd, $delay, $repeating);
$self->{pbot}->{interpreter}->add_to_command_queue($context->{from}, $cmd, $seconds, $repeating);
return "Command added to event queue.";
}
@ -127,44 +127,39 @@ sub cmd_eventqueue {
return "Unknown command '$command'. $usage";
}
sub start {
my $self = shift;
$self->{enabled} = 1;
push @timer_funcs, $self->{timer_func};
alarm 1;
}
sub stop {
my $self = shift;
$self->{enabled} = 0;
@timer_funcs = grep { $_ != $self->{timer_func} } @timer_funcs;
}
sub find_enqueue_position {
my ($self, $value) = @_;
my ($self, $time) = @_;
# no events in queue yet, early-return first position
return 0 if not @{$self->{event_queue}};
if ($value < $self->{event_queue}->[0]->{timeout}) {
# early-return first position if event's time is less
# than first position's
if ($time < $self->{event_queue}->[0]->{timeout}) {
return 0;
}
if ($value > $self->{event_queue}->[@{$self->{event_queue}} - 1]->{timeout}) {
# early-return last position if event's time is greater
if ($time > $self->{event_queue}->[@{$self->{event_queue}} - 1]->{timeout}) {
return scalar @{$self->{event_queue}};
}
# binary search to find enqueue position
my $lo = 0;
my $hi = scalar @{$self->{event_queue}} - 1;
while ($lo <= $hi) {
my $mid = int (($hi + $lo) / 2);
if ($value < $self->{event_queue}->[$mid]->{timeout}) {
if ($time < $self->{event_queue}->[$mid]->{timeout}) {
$hi = $mid - 1;
} elsif ($value > $self->{event_queue}->[$mid]->{timeout}) {
} elsif ($time > $self->{event_queue}->[$mid]->{timeout}) {
$lo = $mid + 1;
} else {
while ($mid < @{$self->{event_queue}} and $self->{event_queue}->[$mid]->{timeout} == $value) {
while ($mid < @{$self->{event_queue}} and $self->{event_queue}->[$mid]->{timeout} == $time) {
# found a slot with the same time. we "slide" down the array
# to append this event to the end of this region of same-times.
$mid++;
}
return $mid;
@ -175,57 +170,64 @@ sub find_enqueue_position {
}
sub replace_subref_or_enqueue_event {
my ($self, $ref, $interval, $id, $repeating) = @_;
my ($self, $subref, $interval, $id, $repeating) = @_;
# find events matching id
my @events = grep { $_->{id} eq $id } @{$self->{event_queue}};
# no events found, enqueue new event
if (not @events) {
$self->enqueue_event($ref, $interval, $id, $repeating);
$self->enqueue_event($subref, $interval, $id, $repeating);
return;
}
# otherwise update existing events
foreach my $event (@events) {
$event->{subref} = $ref;
$event->{subref} = $subref;
}
}
sub replace_or_enqueue_event {
my ($self, $ref, $interval, $id, $repeating) = @_;
my ($self, $subref, $interval, $id, $repeating) = @_;
# remove event if it exists
$self->dequeue_event($id) if $self->exists($id);
$self->enqueue_event($ref, $interval, $id, $repeating);
# enqueue new event
$self->enqueue_event($subref, $interval, $id, $repeating);
}
sub enqueue_event_unless_exists {
my ($self, $ref, $interval, $id, $repeating) = @_;
my ($self, $subref, $interval, $id, $repeating) = @_;
# event already exists, bail out
return if $self->exists($id);
$self->enqueue_event($ref, $interval, $id, $repeating);
# enqueue new event
$self->enqueue_event($subref, $interval, $id, $repeating);
}
sub enqueue_event {
my ($self, $ref, $interval, $id, $repeating) = @_;
my ($self, $subref, $interval, $id, $repeating) = @_;
$id //= 'anonymous event';
$id //= 'an event';
$repeating //= 0;
$interval //= 0;
my $event = {
id => $id,
subref => $ref,
subref => $subref,
interval => $interval,
timeout => $seconds + $interval,
timeout => time + $interval,
repeating => $repeating,
};
my $i = $self->find_enqueue_position($event->{timeout});
splice @{$self->{event_queue}}, $i, 0, $event;
if ($interval < $waitfor) {
$self->waitfor($interval);
}
my $debug = $self->{pbot}->{registry}->get_value('timer', 'debug') // 0;
my $debug = $self->{pbot}->{registry}->get_value('eventqueue', 'debug') // 0;
if ($debug > 1) {
$self->{pbot}->{logger}->log("Enqueued new timer event $id at position $i: timeout=$event->{timeout} interval=$interval repeating=$repeating\n");
$self->{pbot}->{logger}->log("Enqueued new event $id at position $i: timeout=$event->{timeout} interval=$interval repeating=$repeating\n");
}
}
@ -267,12 +269,14 @@ sub execute_and_dequeue_event {
return $self->dequeue_event($id, 1);
}
sub register {
my ($self, $ref, $interval, $id) = @_;
$self->enqueue_event($ref, $interval, $id, 1);
# adds event with repeating defaulting to enabled
sub enqueue {
my ($self, $subref, $interval, $id, $repeating) = @_;
$repeating //= 1;
$self->enqueue_event($subref, $interval, $id, $repeating);
}
sub unregister {
sub dequeue {
my ($self, $id) = @_;
$self->dequeue_event($id);
}
@ -299,8 +303,11 @@ sub update_interval {
for (my $i = 0; $i < @{$self->{event_queue}}; $i++) {
if ($self->{event_queue}->[$i]->{id} eq $id) {
if ($dont_enqueue) {
# update interval in-place without moving event to new place in queue
# (allows event to fire at expected time, then updates to new timeout afterwards)
$self->{event_queue}->[$i]->{interval} = $interval;
} else {
# remove and add event in new position in queue
my $event = splice(@{$self->{event_queue}}, $i, 1);
$self->enqueue_event($event->{subref}, $interval, $id, $event->{repeating});
}
@ -309,60 +316,57 @@ sub update_interval {
}
}
sub waitfor {
my ($self, $duration) = @_;
$duration = 1 if $duration < 1;
alarm $duration;
$waitfor = $duration;
sub duration_until_next_event {
my ($self) = @_;
return 0 if not @{$self->{event_queue}};
return $self->{event_queue}->[0]->{timeout} - time;
}
sub on_tick_handler {
# invokes the next event, if ready, and then returns seconds until next event
sub do_next_event {
my ($self) = @_;
return if not $self->{enabled};
my $debug = $self->{pbot}->{registry}->get_value('timer', 'debug') // 0;
$self->{pbot}->{logger}->log("$self->{name} tick $seconds\n") if $debug;
# early-return if no events available
return 0 if not @{$self->{event_queue}};
if (@{$self->{event_queue}}) {
my $next_tick = 1;
my @enqueue = ();
for (my $i = 0; $i < @{$self->{event_queue}}; $i++) {
if ($seconds >= $self->{event_queue}->[$i]->{timeout}) {
my $event = $self->{event_queue}->[$i];
$self->{pbot}->{logger}->log("Processing timer event $i: $event->{id}\n") if $debug > 1;
$event->{subref}->($event);
splice @{$self->{event_queue}}, $i--, 1;
push @enqueue, $event if $event->{repeating};
} else {
if ($debug > 2) {
$self->{pbot}->{logger}->log("Event not ready yet: $self->{event_queue}->[$i]->{id} (timeout=$self->{event_queue}->[$i]->{timeout})\n");
}
my $debug = $self->{pbot}->{registry}->get_value('eventqueue', 'debug') // 0;
$next_tick = $self->{event_queue}->[$i]->{timeout} - $seconds;
last;
# repeating events to re-enqueue
my @enqueue;
for (my $i = 0; $i < @{$self->{event_queue}}; $i++) {
# we call time for a fresh time, instead of using a stale $now that
# could be well in the past depending on a previous event's duration
if (time >= $self->{event_queue}->[$i]->{timeout}) {
my $event = $self->{event_queue}->[$i];
$self->{pbot}->{logger}->log("Processing event $i: $event->{id}\n") if $debug > 1;
# call event's subref, passing event as argument
$event->{subref}->($event);
# remove event from queue
splice @{$self->{event_queue}}, $i--, 1;
# add event to re-enqueue queue if repeating
push @enqueue, $event if $event->{repeating};
} else {
# no more events ready at this time
if ($debug > 2) {
$self->{pbot}->{logger}->log("Event not ready yet: $self->{event_queue}->[$i]->{id} (timeout=$self->{event_queue}->[$i]->{timeout})\n");
}
}
$self->waitfor($next_tick);
foreach my $event (@enqueue) {
$self->enqueue_event($event->{subref}, $event->{interval}, $event->{id}, 1);
last;
}
} else {
# no queued events, call default overridable on_tick() method if timeout has elapsed
if ($seconds - $self->{last} >= $self->{timeout}) {
$self->{last} = $seconds;
$self->on_tick;
}
$self->waitfor($self->{timeout} - $seconds - $self->{last});
}
}
# default overridable handler, executed whenever timeout is triggered
sub on_tick {
my ($self) = @_;
$self->{pbot}->{logger}->log("Tick! $self->{name} $self->{timeout} $self->{last} $seconds\n");
# re-enqueue repeating events
foreach my $event (@enqueue) {
$self->enqueue_event($event->{subref}, $event->{interval}, $event->{id}, 1);
}
return $self->duration_until_next_event;
}
1;

View File

@ -64,7 +64,8 @@ sub initialize {
$self->{pbot}->{event_dispatcher}->register_handler('pbot.join', sub { $self->on_self_join(@_) });
$self->{pbot}->{event_dispatcher}->register_handler('pbot.part', sub { $self->on_self_part(@_) });
$self->{pbot}->{timer}->register(sub { $self->check_pending_whos }, 10, 'Check Pending Whos');
# TODO: enqueue these events as needed instead of naively checking every 10 seconds
$self->{pbot}->{event_queue}->enqueue(sub { $self->check_pending_whos }, 10, 'Check pending WHOs');
}
sub default_handler {

View File

@ -98,7 +98,7 @@ sub enqueue_ignores {
my $interval = $timeout - $now;
$interval = 0 if $interval < 0;
$self->{pbot}->{timer}->enqueue_event(sub {
$self->{pbot}->{event_queue}->enqueue_event(sub {
$self->remove($channel, $hostmask);
}, $interval, "ignore_timeout $channel $hostmask"
);
@ -136,9 +136,9 @@ sub add {
$self->{ignorelist}->add($channel, $hostmask, $data);
if ($length > 0) {
$self->{pbot}->{timer}->dequeue_event("ignore_timeout $channel $hostmask");
$self->{pbot}->{event_queue}->dequeue_event("ignore_timeout $channel $hostmask");
$self->{pbot}->{timer}->enqueue_event(sub {
$self->{pbot}->{event_queue}->enqueue_event(sub {
$self->remove($channel, $hostmask);
}, $length, "ignore_timeout $channel $hostmask"
);
@ -159,7 +159,7 @@ sub remove {
$channel = '.*' if $channel !~ /^#/;
$self->{pbot}->{timer}->dequeue_event("ignore_timeout $channel $hostmask");
$self->{pbot}->{event_queue}->dequeue_event("ignore_timeout $channel $hostmask");
return $self->{ignorelist}->remove($channel, $hostmask);
}

View File

@ -887,7 +887,7 @@ sub output_result {
sub add_message_to_output_queue {
my ($self, $channel, $message, $delay) = @_;
$self->{pbot}->{timer}->enqueue_event(
$self->{pbot}->{event_queue}->enqueue_event(
sub {
my $context = {
from => $channel,
@ -909,7 +909,7 @@ sub add_message_to_output_queue {
sub add_to_command_queue {
my ($self, $channel, $command, $delay, $repeating) = @_;
$self->{pbot}->{timer}->enqueue_event(
$self->{pbot}->{event_queue}->enqueue_event(
sub {
my $context = {
from => $channel,

View File

@ -45,8 +45,8 @@ sub initialize {
# registry trigger for lag_history_interval changes
$self->{pbot}->{registry}->add_trigger('lagchecker', 'lag_history_interval', sub { $self->trigger_lag_history_interval(@_) });
# timer to send PINGs
$self->{pbot}->{timer}->register(
# enqueue repeating event to send PINGs
$self->{pbot}->{event_queue}->enqueue(
sub { $self->send_ping },
$self->{pbot}->{registry}->get_value('lagchecker', 'lag_history_interval'),
'lag check'
@ -62,7 +62,7 @@ sub initialize {
# registry trigger fires when value changes
sub trigger_lag_history_interval {
my ($self, $section, $item, $newvalue) = @_;
$self->{pbot}->{timer}->update_interval('lag check', $newvalue);
$self->{pbot}->{event_queue}->update_interval('lag check', $newvalue);
}
# lagcheck bot command

View File

@ -35,7 +35,7 @@ sub initialize {
$self->{pbot}->{registry}->add_trigger('messagehistory', 'sqlite_commit_interval', sub { $self->sqlite_commit_interval_trigger(@_) });
$self->{pbot}->{registry}->add_trigger('messagehistory', 'sqlite_debug', sub { $self->sqlite_debug_trigger(@_) });
$self->{pbot}->{timer}->register(
$self->{pbot}->{event_queue}->enqueue(
sub { $self->commit_message_history },
$self->{pbot}->{registry}->get_value('messagehistory', 'sqlite_commit_interval'),
'messagehistory commit'
@ -47,7 +47,7 @@ sub initialize {
sub sqlite_commit_interval_trigger {
my ($self, $section, $item, $newvalue) = @_;
$self->{pbot}->{timer}->update_interval('messagehistory commit', $newvalue);
$self->{pbot}->{event_queue}->update_interval('messagehistory commit', $newvalue);
}
sub sqlite_debug_trigger {

View File

@ -191,8 +191,8 @@ sub cmd_reload {
},
'banlist' => sub {
$self->{pbot}->{timer}->dequeue_event('unban #.*');
$self->{pbot}->{timer}->dequeue_event('unmute #.*');
$self->{pbot}->{event_queue}->dequeue_event('unban #.*');
$self->{pbot}->{event_queue}->dequeue_event('unmute #.*');
$self->{pbot}->{banlist}->{banlist}->load;
$self->{pbot}->{banlist}->{quietlist}->load;
$self->{pbot}->{banlist}->enqueue_timeouts($self->{pbot}->{banlist}->{banlist}, 'b');

View File

@ -37,6 +37,7 @@ use PBot::ChanOps;
use PBot::DualIndexHashObject;
use PBot::DualIndexSQLiteObject;
use PBot::EventDispatcher;
use PBot::EventQueue;
use PBot::Factoids;
use PBot::Functions;
use PBot::HashObject;
@ -55,7 +56,6 @@ use PBot::Registry;
use PBot::Refresher;
use PBot::SelectHandler;
use PBot::StdinReader;
use PBot::Timer;
use PBot::Updater;
use PBot::Users;
use PBot::Utils::ParseDate;
@ -178,7 +178,7 @@ sub initialize {
$self->{irc} = PBot::IRC->new;
# prepare remaining core PBot modules -- do not change this order
$self->{timer} = PBot::Timer->new(pbot => $self, timeout => 10, name => 'PBot Timer', %conf);
$self->{event_queue} = PBot::EventQueue->new(pbot => $self, name => 'PBot event queue', %conf);
$self->{event_dispatcher} = PBot::EventDispatcher->new(pbot => $self, %conf);
$self->{users} = PBot::Users->new(pbot => $self, filename => "$conf{data_dir}/users", %conf);
$self->{antiflood} = PBot::AntiFlood->new(pbot => $self, %conf);
@ -213,9 +213,9 @@ sub initialize {
# -- this must happen last after all modules have registered their capabilities --
$self->{capabilities}->rebuild_botowner_capabilities;
# flush all pending save events to disk at exit
# fire all pending save events at exit
$self->{atexit}->register(sub {
$self->{timer}->execute_and_dequeue_event('save *');
$self->{event_queue}->execute_and_dequeue_event('save .*');
return;
}
);
@ -267,9 +267,6 @@ sub connect {
$self->{connected} = 1;
# start timer once connected
$self->{timer}->start;
# set up handlers for the IRC engine
$self->{conn}->add_default_handler(sub { $self->{irchandlers}->default_handler(@_) }, 1);
$self->{conn}->add_handler([251, 252, 253, 254, 255, 302], sub { $self->{irchandlers}->on_init(@_) });
@ -308,7 +305,6 @@ sub register_signal_handlers {
sub atexit {
my $self = shift;
$self->{atexit}->execute_all;
alarm 0;
if (exists $self->{logger}) {
$self->{logger}->log("Good-bye.\n");
} else {
@ -335,7 +331,16 @@ sub exit {
# main loop
sub do_one_loop {
my ($self) = @_;
$self->{irc}->do_one_loop();
# do an irc engine loop (select, eventqueues, etc)
$self->{irc}->do_one_loop;
# check for an available PBot event (returns seconds until next event)
my $waitfor = $self->{event_queue}->do_next_event;
# tell irc select loop to sleep for this many seconds
# (or until its own internal eventqueue has an event)
$self->{irc}->timeout($waitfor);
}
# main entry point

View File

@ -243,13 +243,12 @@ sub execute_process {
# remove atexit handlers
$self->{pbot}->{atexit}->unregister_all;
# FIXME: close databases and files too? Or just set everything to check for $self->{pbot}->{child} == 1 or $context->{pid} == 0?
# execute the provided subroutine, results are stored in $context
eval {
local $SIG{ALRM} = sub { die "Process `$context->{commands}->[0]` timed-out" };
alarm $timeout;
$subref->($context);
alarm 0;
};
# check for errors
@ -265,9 +264,6 @@ sub execute_process {
$context->{result} =~ s/\s+...propagated at .*$//ms;
}
# turn alarm back on for PBot::Timer
alarm 1;
# print $context to pipe
my $json = encode_json $context;
print $writer "$json\n";

View File

@ -82,8 +82,6 @@ sub paste {
$result =~ s/^\s+|\s+$//g;
alarm 1; # LWP::UserAgent::Paranoid kills alarm
return $result;
}

View File

@ -26,7 +26,7 @@ sub initialize {
sub unload {
my $self = shift;
$self->{pbot}->{timer}->dequeue_event('antirepeat .*');
$self->{pbot}->{event_queue}->dequeue_event('antirepeat .*');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.caction');
}
@ -112,7 +112,7 @@ sub on_public {
$self->{offenses}->{$account}->{$channel}->{last_offense} = gettimeofday;
$self->{offenses}->{$account}->{$channel}->{offenses}++;
$self->{pbot}->{timer}->enqueue_event(sub {
$self->{pbot}->{event_queue}->enqueue_event(sub {
my ($event) = @_;
$self->{offenses}->{$account}->{$channel}->{offenses}--;

View File

@ -24,7 +24,7 @@ sub initialize {
sub unload {
my ($self) = @_;
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
$self->{pbot}->{timer}->dequeue_event('antitwitter .*');
$self->{pbot}->{event_queue}->dequeue_event('antitwitter .*');
}
sub on_public {
@ -68,7 +68,7 @@ sub on_public {
);
$self->{pbot}->{chanops}->gain_ops($channel);
$self->{pbot}->{timer}->enqueue_event(sub {
$self->{pbot}->{event_queue}->enqueue_event(sub {
my ($event) = @_;
if (--$self->{offenses}->{$channel}->{$nick}->{offenses} <= 0) {
delete $self->{offenses}->{$channel}->{$nick};

View File

@ -34,7 +34,7 @@ sub initialize {
sub unload {
my $self = shift;
$self->{pbot}->{commands}->unregister('battleship');
$self->{pbot}->{timer}->dequeue_event('battleship loop');
$self->{pbot}->{event_queue}->dequeue_event('battleship loop');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.part');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.quit');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.kick');
@ -124,7 +124,7 @@ sub cmd_battleship {
$player = {id => -1, name => undef, missedinputs => 0};
push @{$self->{state_data}->{players}}, $player;
$self->{pbot}->{timer}->enqueue_event(sub {
$self->{pbot}->{event_queue}->enqueue_event(sub {
$self->run_one_state;
}, 1, 'battleship loop', 1
);
@ -147,7 +147,7 @@ sub cmd_battleship {
$player = {id => $id, name => $challengee, missedinputs => 0};
push @{$self->{state_data}->{players}}, $player;
$self->{pbot}->{timer}->enqueue_event(sub {
$self->{pbot}->{event_queue}->enqueue_event(sub {
$self->battleship_loop;
}, 1, 'battleship loop', 1
);
@ -965,7 +965,7 @@ sub show_battlefield {
sub nogame {
my ($self, $state) = @_;
$state->{result} = 'nogame';
$self->{pbot}->{timer}->update_repeating('battleship loop', 0);
$self->{pbot}->{event_queue}->update_repeating('battleship loop', 0);
return $state;
}

View File

@ -32,7 +32,7 @@ sub unload {
$self->{pbot}->{event_dispatcher}->remove_handler('irc.part');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.quit');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.kick');
$self->{pbot}->{timer}->dequeue_event('connect4 loop');
$self->{pbot}->{event_queue}->dequeue_event('connect4 loop');
}
sub on_kick {
@ -156,7 +156,7 @@ sub cmd_connect4 {
$self->{current_state} = 'accept';
$self->{state_data} = {players => [], counter => 0};
$self->{pbot}->{timer}->enqueue_event(
$self->{pbot}->{event_queue}->enqueue_event(
sub {
$self->run_one_state;
}, 1, 'connect4 loop', 1
@ -185,7 +185,7 @@ sub cmd_connect4 {
$self->{current_state} = 'accept';
$self->{state_data} = {players => [], counter => 0};
$self->{pbot}->{timer}->enqueue_event(
$self->{pbot}->{event_queue}->enqueue_event(
sub {
$self->run_one_state;
}, 1, 'connect4 loop', 1
@ -693,7 +693,7 @@ sub show_board {
sub nogame {
my ($self, $state) = @_;
$state->{result} = 'nogame';
$self->{pbot}->{timer}->update_repeating('connect4 loop', 0);
$self->{pbot}->{event_queue}->update_repeating('connect4 loop', 0);
return $state;
}

View File

@ -14,12 +14,12 @@ sub initialize {
$self->{pbot}->{event_dispatcher}->register_handler('irc.public', sub { $self->on_public(@_) });
$self->{queue} = [];
$self->{notified} = {};
$self->{pbot}->{timer}->register(sub { $self->check_queue }, 1, 'RelayUnreg');
$self->{pbot}->{event_queue}->enqueue(sub { $self->check_queue }, 1, 'RelayUnreg');
}
sub unload {
my $self = shift;
$self->{pbot}->{timer}->unregister('RelayUnreg');
$self->{pbot}->{event_queue}->dequeue('RelayUnreg');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.public');
}

View File

@ -25,7 +25,7 @@ sub unload {
my $self = shift;
$self->dbi_end;
$self->{pbot}->{commands}->unregister('remindme');
$self->{pbot}->{timer}->dequeue_event('reminder .*');
$self->{pbot}->{event_queue}->dequeue_event('reminder .*');
}
sub enqueue_reminders {
@ -56,7 +56,7 @@ sub enqueue_reminders {
$timeout = 10 if $timeout < 10;
my $repeating = $reminder->{repeat};
$self->{pbot}->{timer}->enqueue_event(
$self->{pbot}->{event_queue}->enqueue_event(
sub {
my ($event) = @_;
$self->do_reminder($reminder->{id}, $event);
@ -126,7 +126,7 @@ sub add_reminder {
return 0;
}
$self->{pbot}->{timer}->enqueue_event(
$self->{pbot}->{event_queue}->enqueue_event(
sub {
my ($event) = @_;
$self->do_reminder($id, $event);
@ -151,7 +151,7 @@ sub delete_reminder {
return 0;
}
$self->{pbot}->{timer}->dequeue_event("reminder $id");
$self->{pbot}->{event_queue}->dequeue_event("reminder $id");
return 1;
}

View File

@ -67,7 +67,7 @@ sub initialize {
sub unload {
my $self = shift;
$self->{pbot}->{commands}->unregister('spinach');
$self->{pbot}->{timer}->dequeue_event('spinach loop');
$self->{pbot}->{event_queue}->dequeue_event('spinach loop');
$self->{stats}->end if $self->{stats_running};
$self->{pbot}->{event_dispatcher}->remove_handler('irc.part');
$self->{pbot}->{event_dispatcher}->remove_handler('irc.quit');
@ -336,7 +336,7 @@ sub cmd_spinach {
$self->{state_data} = { players => [], counter => 0 };
$self->{current_state} = 'getplayers';
$self->{pbot}->{timer}->enqueue_event(sub {
$self->{pbot}->{event_queue}->enqueue_event(sub {
$self->run_one_state;
}, 1, 'spinach loop', 1
);
@ -409,7 +409,7 @@ sub cmd_spinach {
if (not @{$self->{state_data}->{players}}) {
$self->{current_state} = 'nogame';
$self->{pbot}->{timer}->update_repeating('spinach loop', 0);
$self->{pbot}->{event_queue}->update_repeating('spinach loop', 0);
return "/msg $self->{channel} $context->{nick} has left the game! All players have left. The game has been stopped.";
} else {
return "/msg $self->{channel} $context->{nick} has left the game!";
@ -935,7 +935,7 @@ sub run_one_state {
if (not @{$self->{state_data}->{players}}) {
$self->send_message($self->{channel}, "All players have left the game!");
$self->{current_state} = 'nogame';
$self->{pbot}->{timer}->update_repeating('spinach loop', 0);
$self->{pbot}->{event_queue}->update_repeating('spinach loop', 0);
}
}
@ -945,7 +945,7 @@ sub run_one_state {
if (not defined $self->{current_state}) {
$self->{pbot}->{logger}->log("Spinach state broke.\n");
$self->{current_state} = 'nogame';
$self->{pbot}->{timer}->update_repeating('spinach loop', 0);
$self->{pbot}->{event_queue}->update_repeating('spinach loop', 0);
return;
}
@ -2152,7 +2152,7 @@ sub nogame {
delete $self->{stats_running};
}
$state->{result} = 'nogame';
$self->{pbot}->{timer}->update_repeating('spinach loop', 0);
$self->{pbot}->{event_queue}->update_repeating('spinach loop', 0);
return $state;
}
@ -2205,7 +2205,7 @@ sub getplayers {
$self->send_message($self->{channel}, "All players have left the queue. The game has been stopped.");
$self->{current_state} = 'nogame';
$self->{result} = 'nogame';
$self->{pbot}->{timer}->update_repeating('spinach loop', 0);
$self->{pbot}->{event_queue}->update_repeating('spinach loop', 0);
return $state;
}
}