File Coverage

blib/lib/POE/Component/Schedule.pm
Criterion Covered Total %
statement 37 75 49.3
branch 10 20 50.0
condition 3 9 33.3
subroutine 11 17 64.7
pod 3 3 100.0
total 64 124 51.6


line stmt bran cond sub pod time code
1             package POE::Component::Schedule;
2              
3 13     13   3162091 use 5.008;
  13         74  
  13         992  
4              
5 13     13   88 use strict;
  13         26  
  13         690  
6 13     13   82 use warnings;
  13         33  
  13         464  
7 13     13   80 use Carp;
  13         22  
  13         1746  
8              
9             our $VERSION = '0.95';
10              
11 13     13   280649 use POE;
  13         814231  
  13         146  
12              
13              
14             BEGIN {
15 13 100   13   745479 defined &DEBUG or *DEBUG = sub () { 0 };
16             }
17              
18             # Private properties of a schedule ticket
19             sub PCS_TIMER () { 0 } # The POE timer
20             sub PCS_ITERATOR () { 1 } # DateTime::Set iterator
21             sub PCS_SESSION () { 2 } # POE session ID
22             sub PCS_EVENT () { 3 } # Event name
23             sub PCS_ARGS () { 4 } # Event args array
24              
25             # Private constant:
26             # The name of the counter attached to each session
27             # We use only one counter for all timers of one session
28             # All instances of P::C::S will use the same counter for a given session
29             sub REFCOUNT_COUNTER_NAME () { __PACKAGE__ }
30              
31             # Scheduling session ID
32             # This session is a singleton
33             my $BackEndSession;
34              
35             # Maps tickets IDs to tickets
36             my %Tickets = ();
37             my $LastTicketID = 'a'; # 'b' ... 'z', 'aa' ...
38              
39             #
40             # crank up the schedule session
41             #
42             sub spawn { ## no critic (Subroutines::RequireArgUnpacking)
43 3 50   3 1 2834 if ( !defined $BackEndSession ) {
44 3         9 my ($class, %arg) = @_;
45 3   66     23 my $alias = $arg{Alias} || ref $class || $class;
46              
47             $BackEndSession = POE::Session->create(
48             inline_states => {
49             _start => sub {
50 3     3   13544 print "# $alias _start\n" if DEBUG;
51 3         858 my ($k) = $_[KERNEL];
52              
53 3         17 $k->detach_myself;
54 3         82 $k->alias_set( $alias );
55 3         129 $k->sig( 'SHUTDOWN', 'shutdown' );
56             },
57              
58             schedule => \&_schedule,
59             client_event => \&_client_event,
60             cancel => \&_cancel,
61              
62             shutdown => sub {
63 0     0   0 print "# $alias shutdown\n" if DEBUG;
64 0         0 my $k = $_[KERNEL];
65              
66             # Remove all timers of our session
67             # and decrement session references
68 0         0 foreach my $alarm ($k->alarm_remove_all()) {
69 0         0 my ($name, $time, $t) = @$alarm;
70 0         0 $t->[PCS_TIMER] = undef;
71 0         0 $k->refcount_decrement($t->[PCS_SESSION], REFCOUNT_COUNTER_NAME);
72             }
73 0         0 %Tickets = ();
74              
75 0         0 $k->sig_handled();
76             },
77             _stop => sub {
78 3     3   3758 print "# $alias _stop\n" if DEBUG;
79 3         404 $BackEndSession = undef;
80             },
81             },
82 3         73 )->ID;
83             }
84 3         552 return $BackEndSession;
85             }
86              
87             #
88             # schedule the next event
89             # ARG0 is the schedule ticket
90             #
91             sub _schedule {
92 0     0   0 my ( $k, $t ) = @_[ KERNEL, ARG0];
93              
94             #
95             # deal with DateTime::Sets that are finite
96             #
97 0         0 my $n = $t->[PCS_ITERATOR]->next;
98 0 0       0 unless ($n) {
99             # No more events, so release the session
100 0         0 $k->refcount_decrement($t->[PCS_SESSION], REFCOUNT_COUNTER_NAME);
101 0         0 $t->[PCS_TIMER] = undef;
102 0         0 return;
103             }
104              
105 0         0 $t->[PCS_TIMER] = $k->alarm_set( client_event => $n->epoch, $t );
106 0         0 return $t;
107             }
108              
109             #
110             # handle a client event and schedule the next one
111             # ARG0 is the schedule ticket
112             #
113             sub _client_event { ## no critic (Subroutines::RequireArgUnpacking)
114 0     0   0 my ( $k, $t ) = @_[ KERNEL, ARG0 ];
115              
116 0         0 $k->post( @{$t}[PCS_SESSION, PCS_EVENT], @{$t->[PCS_ARGS]} );
  0         0  
  0         0  
117              
118 0         0 return _schedule(@_);
119             }
120              
121             #
122             # cancel an alarm
123             #
124             sub _cancel {
125 0     0   0 my ( $k, $t ) = @_[ KERNEL, ARG0 ];
126              
127 0 50       0 if (defined($t->[PCS_TIMER])) {
128 0         0 $k->alarm_remove($t->[PCS_TIMER]);
129 0         0 $k->refcount_decrement($t->[PCS_SESSION], REFCOUNT_COUNTER_NAME);
130 0         0 $t->[PCS_TIMER] = undef;
131             }
132 0         0 return;
133             }
134              
135             #
136             # Takes a POE::Session, an event name and a DateTime::Set
137             # Returns a ticket object
138             #
139             sub add {
140              
141 3     3 1 2005 my ( $class, $session, $event, $iterator, @args ) = @_;
142              
143             # Remember only the session ID
144 3 100       19 $session = $poe_kernel->alias_resolve($session) unless ref $session;
145 3 100       254 defined($session) or croak __PACKAGE__ . "->add: first arg must be an existing POE session ID or alias.";
146 2         9 $session = $session->ID;
147              
148             # We don't want to loose the session until the event has been handled
149 2 50       15 $poe_kernel->refcount_increment($session, REFCOUNT_COUNTER_NAME) > 0
150             or croak __PACKAGE__ . "->add: first arg must be an existing POE session ID or alias: $!";
151              
152 2 50 33     273 ref $iterator && $iterator->isa('DateTime::Set')
153             or croak __PACKAGE__ . "->add: third arg must be a DateTime::Set";
154              
155 0 0         $class->spawn unless $BackEndSession;
156              
157 0           my $id = $LastTicketID++;
158 0           my $ticket = $Tickets{$id} = [
159             undef, # Current alarm id
160             $iterator,
161             $session,
162             $event,
163             \@args,
164             ];
165              
166 0           $poe_kernel->post( $BackEndSession, schedule => $ticket);
167              
168             # We return a kind of smart pointer, so the schedule
169             # can be simply destroyed by releasing its object reference
170 0   0       return bless \$id, ref($class) || $class;
171             }
172              
173             sub delete {
174 0     0 1   my $id = ${$_[0]};
  0            
175 0 0         return unless exists $Tickets{$id};
176 0           $poe_kernel->post($BackEndSession, cancel => delete $Tickets{$id});
177 0           return;
178             }
179              
180             # Releasing the ticket object will delete the ressource
181             sub DESTROY {
182 0     0     return $_[0]->delete;
183             }
184              
185             {
186 13     13   117 no warnings;
  13         29  
  13         1652  
187             *new = \&add;
188             }
189              
190             1;
191             __END__