summaryrefslogtreecommitdiff
path: root/lib/IO/Async/Timer
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2015-06-01 14:15:30 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2015-06-01 14:15:30 +0000
commit1425eea04dd872dc6313f5315f317b2de288037c (patch)
treef81c74f75429e829714029850f89ee4c7f13aa39 /lib/IO/Async/Timer
downloadIO-Async-tarball-master.tar.gz
Diffstat (limited to 'lib/IO/Async/Timer')
-rw-r--r--lib/IO/Async/Timer/Absolute.pm142
-rw-r--r--lib/IO/Async/Timer/Countdown.pm274
-rw-r--r--lib/IO/Async/Timer/Periodic.pm249
3 files changed, 665 insertions, 0 deletions
diff --git a/lib/IO/Async/Timer/Absolute.pm b/lib/IO/Async/Timer/Absolute.pm
new file mode 100644
index 0000000..a925415
--- /dev/null
+++ b/lib/IO/Async/Timer/Absolute.pm
@@ -0,0 +1,142 @@
+# You may distribute under the terms of either the GNU General Public License
+# or the Artistic License (the same terms as Perl itself)
+#
+# (C) Paul Evans, 2010-2015 -- leonerd@leonerd.org.uk
+
+package IO::Async::Timer::Absolute;
+
+use strict;
+use warnings;
+use base qw( IO::Async::Timer );
+
+our $VERSION = '0.67';
+
+use Carp;
+
+=head1 NAME
+
+C<IO::Async::Timer::Absolute> - event callback at a fixed future time
+
+=head1 SYNOPSIS
+
+ use IO::Async::Timer::Absolute;
+
+ use POSIX qw( mktime );
+
+ use IO::Async::Loop;
+ my $loop = IO::Async::Loop->new;
+
+ my @time = gmtime;
+
+ my $timer = IO::Async::Timer::Absolute->new(
+ time => mktime( 0, 0, 0, $time[3]+1, $time[4], $time[5] ),
+
+ on_expire => sub {
+ print "It's midnight\n";
+ $loop->stop;
+ },
+ );
+
+ $loop->add( $timer );
+
+ $loop->run;
+
+=head1 DESCRIPTION
+
+This subclass of L<IO::Async::Timer> implements one-shot events at a fixed
+time in the future. The object waits for a given timestamp, and invokes its
+callback at that point in the future.
+
+For a C<Timer> object that waits for a delay relative to the time it is
+started, see instead L<IO::Async::Timer::Countdown>.
+
+=cut
+
+=head1 EVENTS
+
+The following events are invoked, either using subclass methods or CODE
+references in parameters:
+
+=head2 on_expire
+
+Invoked when the timer expires.
+
+=cut
+
+=head1 PARAMETERS
+
+The following named parameters may be passed to C<new> or C<configure>:
+
+=head2 on_expire => CODE
+
+CODE reference for the C<on_expire> event.
+
+=head2 time => NUM
+
+The epoch time at which the timer will expire.
+
+Once constructed, the timer object will need to be added to the C<Loop> before
+it will work.
+
+Unlike other timers, it does not make sense to C<start> this object, because
+its expiry time is absolute, and not relative to the time it is started.
+
+=cut
+
+sub configure
+{
+ my $self = shift;
+ my %params = @_;
+
+ if( exists $params{on_expire} ) {
+ my $on_expire = delete $params{on_expire};
+ ref $on_expire or croak "Expected 'on_expire' as a reference";
+
+ $self->{on_expire} = $on_expire;
+ undef $self->{cb}; # Will be lazily constructed when needed
+ }
+
+ if( exists $params{time} ) {
+ my $time = delete $params{time};
+
+ $self->stop if $self->is_running;
+
+ $self->{time} = $time;
+
+ $self->start if !$self->is_running;
+ }
+
+ unless( $self->can_event( 'on_expire' ) ) {
+ croak 'Expected either a on_expire callback or an ->on_expire method';
+ }
+
+ $self->SUPER::configure( %params );
+}
+
+sub _make_cb
+{
+ my $self = shift;
+
+ return $self->_capture_weakself( sub {
+ my $self = shift or return;
+
+ undef $self->{id};
+
+ $self->invoke_event( "on_expire" );
+ } );
+}
+
+sub _make_enqueueargs
+{
+ my $self = shift;
+
+ return at => $self->{time};
+}
+
+=head1 AUTHOR
+
+Paul Evans <leonerd@leonerd.org.uk>
+
+=cut
+
+0x55AA;
diff --git a/lib/IO/Async/Timer/Countdown.pm b/lib/IO/Async/Timer/Countdown.pm
new file mode 100644
index 0000000..201ba42
--- /dev/null
+++ b/lib/IO/Async/Timer/Countdown.pm
@@ -0,0 +1,274 @@
+# You may distribute under the terms of either the GNU General Public License
+# or the Artistic License (the same terms as Perl itself)
+#
+# (C) Paul Evans, 2009-2012 -- leonerd@leonerd.org.uk
+
+package IO::Async::Timer::Countdown;
+
+use strict;
+use warnings;
+use base qw( IO::Async::Timer );
+
+our $VERSION = '0.67';
+
+use Carp;
+
+=head1 NAME
+
+C<IO::Async::Timer::Countdown> - event callback after a fixed delay
+
+=head1 SYNOPSIS
+
+ use IO::Async::Timer::Countdown;
+
+ use IO::Async::Loop;
+ my $loop = IO::Async::Loop->new;
+
+ my $timer = IO::Async::Timer::Countdown->new(
+ delay => 10,
+
+ on_expire => sub {
+ print "Sorry, your time's up\n";
+ $loop->stop;
+ },
+ );
+
+ $timer->start;
+
+ $loop->add( $timer );
+
+ $loop->run;
+
+=head1 DESCRIPTION
+
+This subclass of L<IO::Async::Timer> implements one-shot fixed delays.
+The object implements a countdown timer, which invokes its callback after the
+given period from when it was started. After it has expired the Timer may be
+started again, when it will wait the same period then invoke the callback
+again. A timer that is currently running may be stopped or reset.
+
+For a C<Timer> object that repeatedly runs a callback at regular intervals,
+see instead L<IO::Async::Timer::Periodic>. For a C<Timer> that invokes its
+callback at a fixed time in the future, see L<IO::Async::Timer::Absolute>.
+
+=cut
+
+=head1 EVENTS
+
+The following events are invoked, either using subclass methods or CODE
+references in parameters:
+
+=head2 on_expire
+
+Invoked when the timer expires.
+
+=cut
+
+=head1 PARAMETERS
+
+The following named parameters may be passed to C<new> or C<configure>:
+
+=head2 on_expire => CODE
+
+CODE reference for the C<on_expire> event.
+
+=head2 delay => NUM
+
+The delay in seconds after starting the timer until it expires. Cannot be
+changed if the timer is running. A timer with a zero delay expires
+"immediately".
+
+=head2 remove_on_expire => BOOL
+
+Optional. If true, remove this timer object from its parent notifier or
+containing loop when it expires. Defaults to false.
+
+Once constructed, the timer object will need to be added to the C<Loop> before
+it will work. It will also need to be started by the C<start> method.
+
+=cut
+
+sub configure
+{
+ my $self = shift;
+ my %params = @_;
+
+ foreach (qw( remove_on_expire )) {
+ $self->{$_} = delete $params{$_} if exists $params{$_};
+ }
+
+ if( exists $params{on_expire} ) {
+ my $on_expire = delete $params{on_expire};
+ ref $on_expire or croak "Expected 'on_expire' as a reference";
+
+ $self->{on_expire} = $on_expire;
+ undef $self->{cb}; # Will be lazily constructed when needed
+ }
+
+ if( exists $params{delay} ) {
+ $self->is_running and croak "Cannot configure 'delay' of a running timer\n";
+
+ my $delay = delete $params{delay};
+ $delay >= 0 or croak "Expected a 'delay' as a non-negative number";
+
+ $self->{delay} = $delay;
+ }
+
+ unless( $self->can_event( 'on_expire' ) ) {
+ croak 'Expected either a on_expire callback or an ->on_expire method';
+ }
+
+ $self->SUPER::configure( %params );
+}
+
+=head1 METHODS
+
+=cut
+
+=head2 $expired = $timer->is_expired
+
+Returns true if the Timer has already expired.
+
+=cut
+
+sub is_expired
+{
+ my $self = shift;
+ return $self->{expired};
+}
+
+sub _make_cb
+{
+ my $self = shift;
+
+ return $self->_capture_weakself( sub {
+ my $self = shift or return;
+
+ undef $self->{id};
+ $self->{expired} = 1;
+
+ $self->remove_from_parent if $self->{remove_on_expire};
+
+ $self->invoke_event( "on_expire" );
+ } );
+}
+
+sub _make_enqueueargs
+{
+ my $self = shift;
+
+ undef $self->{expired};
+ return after => $self->{delay};
+}
+
+=head2 $timer->reset
+
+If the timer is running, restart the countdown period from now. If the timer
+is not running, this method has no effect.
+
+=cut
+
+sub reset
+{
+ my $self = shift;
+
+ my $loop = $self->loop or croak "Cannot reset a Timer that is not in a Loop";
+
+ return if !$self->is_running;
+
+ $self->stop;
+ $self->start;
+}
+
+=head1 EXAMPLES
+
+=head2 Watchdog Timer
+
+Because the C<reset> method restarts a running countdown timer back to its
+full period, it can be used to implement a watchdog timer. This is a timer
+which will not expire provided the method is called at least as often as it
+is configured. If the method fails to be called, the timer will eventually
+expire and run its callback.
+
+For example, to expire an accepted connection after 30 seconds of inactivity:
+
+ ...
+
+ on_accept => sub {
+ my ( $newclient ) = @_;
+
+ my $watchdog = IO::Async::Timer::Countdown->new(
+ delay => 30,
+
+ on_expire => sub {
+ my $self = shift;
+
+ my $stream = $self->parent;
+ $stream->close;
+ },
+ );
+
+ my $stream = IO::Async::Stream->new(
+ handle => $newclient,
+
+ on_read => sub {
+ my ( $self, $buffref, $eof ) = @_;
+ $watchdog->reset;
+
+ ...
+ },
+
+ on_closed => sub {
+ $watchdog->stop;
+ },
+ ) );
+
+ $stream->add_child( $watchdog );
+ $watchdog->start;
+
+ $loop->add( $watchdog );
+ }
+
+Rather than setting up a lexical variable to store the Stream so that the
+Timer's C<on_expire> closure can call C<close> on it, the parent/child
+relationship between the two Notifier objects is used. At the time the Timer
+C<on_expire> closure is invoked, it will have been added as a child notifier
+of the Stream; this means the Timer's C<parent> method will return the Stream
+Notifier. This enables it to call C<close> without needing to capture a
+lexical variable, which would create a cyclic reference.
+
+=head2 Fixed-Delay Repeating Timer
+
+The C<on_expire> event fires a fixed delay after the C<start> method has begun
+the countdown. The C<start> method can be invoked again at some point during
+the C<on_expire> handling code, to create a timer that invokes its code
+regularly a fixed delay after the previous invocation has finished. This
+creates an arrangement similar to an L<IO::Async::Timer::Periodic>, except
+that it will wait until the previous invocation has indicated it is finished,
+before starting the countdown for the next call.
+
+ my $timer = IO::Async::Timer::Countdown->new(
+ delay => 60,
+
+ on_expire => sub {
+ my $self = shift;
+
+ start_some_operation(
+ on_complete => sub { $self->start },
+ );
+ },
+ );
+
+ $timer->start;
+ $loop->add( $timer );
+
+This example invokes the C<start_some_operation> function 60 seconds after the
+previous iteration has indicated it has finished.
+
+=head1 AUTHOR
+
+Paul Evans <leonerd@leonerd.org.uk>
+
+=cut
+
+0x55AA;
diff --git a/lib/IO/Async/Timer/Periodic.pm b/lib/IO/Async/Timer/Periodic.pm
new file mode 100644
index 0000000..f99a43c
--- /dev/null
+++ b/lib/IO/Async/Timer/Periodic.pm
@@ -0,0 +1,249 @@
+# You may distribute under the terms of either the GNU General Public License
+# or the Artistic License (the same terms as Perl itself)
+#
+# (C) Paul Evans, 2009-2015 -- leonerd@leonerd.org.uk
+
+package IO::Async::Timer::Periodic;
+
+use strict;
+use warnings;
+use base qw( IO::Async::Timer );
+
+our $VERSION = '0.67';
+
+use Carp;
+
+=head1 NAME
+
+C<IO::Async::Timer::Periodic> - event callback at regular intervals
+
+=head1 SYNOPSIS
+
+ use IO::Async::Timer::Periodic;
+
+ use IO::Async::Loop;
+ my $loop = IO::Async::Loop->new;
+
+ my $timer = IO::Async::Timer::Periodic->new(
+ interval => 60,
+
+ on_tick => sub {
+ print "You've had a minute\n";
+ },
+ );
+
+ $timer->start;
+
+ $loop->add( $timer );
+
+ $loop->run;
+
+=head1 DESCRIPTION
+
+This subclass of L<IO::Async::Timer> implements repeating events at regular
+clock intervals. The timing may or may not be subject to how long it takes the
+callback to execute. Iterations may be rescheduled runs at fixed regular
+intervals beginning at the time the timer was started, or by a fixed delay
+after the previous code has finished executing.
+
+For a C<Timer> object that only runs a callback once, after a given delay, see
+instead L<IO::Async::Timer::Countdown>. A Countdown timer can also be used to
+create repeating events that fire at a fixed delay after the previous event
+has finished processing. See als the examples in
+C<IO::Async::Timer::Countdown>.
+
+=cut
+
+=head1 EVENTS
+
+The following events are invoked, either using subclass methods or CODE
+references in parameters:
+
+=head2 on_tick
+
+Invoked on each interval of the timer.
+
+=cut
+
+=head1 PARAMETERS
+
+The following named parameters may be passed to C<new> or C<configure>:
+
+=head2 on_tick => CODE
+
+CODE reference for the C<on_tick> event.
+
+=head2 interval => NUM
+
+The interval in seconds between invocations of the callback or method. Cannot
+be changed if the timer is running.
+
+=head2 first_interval => NUM
+
+Optional. If defined, the interval in seconds after calling the C<start>
+method before the first invocation of the callback or method. Thereafter, the
+regular C<interval> will be used. If not supplied, the first interval will be
+the same as the others.
+
+Even if this value is zero, the first invocation will be made asynchronously,
+by the containing C<Loop> object, and not synchronously by the C<start> method
+itself.
+
+=head2 reschedule => STRING
+
+Optional. Must be one of C<hard>, C<skip> or C<drift>. Defines the algorithm
+used to reschedule the next invocation.
+
+C<hard> schedules each iteration at the fixed interval from the previous
+iteration's schedule time, ensuring a regular repeating event.
+
+C<skip> schedules similarly to C<hard>, but skips over times that have already
+passed. This matters if the duration is particularly short and there's a
+possibility that times may be missed, or if the entire process is stopped and
+resumed by C<SIGSTOP> or similar.
+
+C<drift> schedules each iteration at the fixed interval from the time that the
+previous iteration's event handler returns. This allows it to slowly drift over
+time and become desynchronised with other events of the same interval or
+multiples/fractions of it.
+
+Once constructed, the timer object will need to be added to the C<Loop> before
+it will work. It will also need to be started by the C<start> method.
+
+=cut
+
+sub _init
+{
+ my $self = shift;
+ $self->SUPER::_init( @_ );
+
+ $self->{reschedule} = "hard";
+}
+
+sub configure
+{
+ my $self = shift;
+ my %params = @_;
+
+ if( exists $params{on_tick} ) {
+ my $on_tick = delete $params{on_tick};
+ ref $on_tick or croak "Expected 'on_tick' as a reference";
+
+ $self->{on_tick} = $on_tick;
+ undef $self->{cb}; # Will be lazily constructed when needed
+ }
+
+ if( exists $params{interval} ) {
+ $self->is_running and croak "Cannot configure 'interval' of a running timer\n";
+
+ my $interval = delete $params{interval};
+ $interval > 0 or croak "Expected a 'interval' as a positive number";
+
+ $self->{interval} = $interval;
+ }
+
+ if( exists $params{first_interval} ) {
+ $self->is_running and croak "Cannot configure 'first_interval' of a running timer\n";
+
+ my $first_interval = delete $params{first_interval};
+ $first_interval >= 0 or croak "Expected a 'first_interval' as a non-negative number";
+
+ $self->{first_interval} = $first_interval;
+ }
+
+ if( exists $params{reschedule} ) {
+ my $resched = delete $params{reschedule} || "hard";
+ grep { $_ eq $resched } qw( hard skip drift ) or
+ croak "Expected 'reschedule' to be one of hard, skip, drift";
+
+ $self->{reschedule} = $resched;
+ }
+
+ unless( $self->can_event( 'on_tick' ) ) {
+ croak 'Expected either a on_tick callback or an ->on_tick method';
+ }
+
+ $self->SUPER::configure( %params );
+}
+
+sub _next_interval
+{
+ my $self = shift;
+ return $self->{first_interval} if defined $self->{first_interval};
+ return $self->{interval};
+}
+
+sub start
+{
+ my $self = shift;
+
+ # Only actually define a time if we've got a loop; otherwise it'll just
+ # become start-pending. We'll calculate it properly when it gets added to
+ # the Loop
+ if( my $loop = $self->loop ) {
+ my $now = $loop->time;
+ my $resched = $self->{reschedule};
+
+ if( !defined $self->{next_time} ) {
+ $self->{next_time} = $now + $self->_next_interval;
+ }
+ elsif( $resched eq "hard" ) {
+ $self->{next_time} += $self->_next_interval;
+ }
+ elsif( $resched eq "skip" ) {
+ # How many ticks are needed?
+ my $ticks = POSIX::ceil( $now - $self->{next_time} );
+ # $self->{last_ticks} = $ticks;
+ $self->{next_time} += $self->_next_interval * $ticks;
+ }
+ elsif( $resched eq "drift" ) {
+ $self->{next_time} = $now + $self->_next_interval;
+ }
+ }
+
+ $self->SUPER::start;
+}
+
+sub stop
+{
+ my $self = shift;
+ $self->SUPER::stop;
+
+ undef $self->{next_time};
+}
+
+sub _make_cb
+{
+ my $self = shift;
+
+ return $self->_capture_weakself( sub {
+ my $self = shift or return;
+
+ undef $self->{first_interval};
+
+ undef $self->{id};
+
+ my $ok = eval { $self->invoke_event( on_tick => ); 1 } or
+ my $e = $@;
+
+ # detect ->stop
+ $self->start if defined $self->{next_time};
+
+ die $e if !$ok;
+ } );
+}
+
+sub _make_enqueueargs
+{
+ my $self = shift;
+
+ return at => $self->{next_time};
+}
+
+=head1 AUTHOR
+
+Paul Evans <leonerd@leonerd.org.uk>
+
+=cut
+
+0x55AA;