File Coverage

blib/lib/Net/SIP/Dispatcher/Eventloop.pm
Criterion Covered Total %
statement 148 155 95.4
branch 41 64 64.0
condition 22 32 68.7
subroutine 23 23 100.0
pod 6 6 100.0
total 240 280 85.7


line stmt bran cond sub pod time code
1              
2             ###########################################################################
3             # package Net::SIP::Dispatcher::Eventloop
4             # simple event loop for Net::SIP
5             ###########################################################################
6              
7 41     41   290 use strict;
  41         110  
  41         1283  
8 41     41   246 use warnings;
  41         78  
  41         1892  
9              
10             package Net::SIP::Dispatcher::Eventloop;
11 41     41   214 use fields qw( fd vec just_dropped timer now );
  41         73  
  41         212  
12 41     41   3480 use Time::HiRes qw(gettimeofday);
  41         76  
  41         408  
13 41     41   3946 use Socket;
  41         81  
  41         20073  
14 41     41   323 use List::Util qw(first);
  41         77  
  41         4684  
15 41     41   311 use Net::SIP::Util ':all';
  41         70  
  41         7578  
16 41     41   310 use Net::SIP::Debug;
  41         158  
  41         254  
17 41     41   312 use Carp 'confess';
  41         83  
  41         1919  
18 41     41   258 use Errno 'EINTR';
  41         73  
  41         2031  
19              
20              
21             # constants for read/write events
22 41     41   244 use Exporter 'import';
  41         77  
  41         2382  
23             our @EXPORT = qw(EV_READ EV_WRITE);
24 41     41   329 use constant EV_READ => 0;
  41         138  
  41         3352  
25 41     41   285 use constant EV_WRITE => 1;
  41         96  
  41         56050  
26              
27             ###########################################################################
28             # creates new event loop
29             # Args: $class
30             # Returns: $self
31             ###########################################################################
32             sub new {
33 55     55 1 370 my $class = shift;
34 55         247 my $self = fields::new($class);
35 55         7841 %$self = (
36             fd => [], # {fd}[fn][rw] -> [fd,callback,name]
37             vec => [ '','' ], # read|write vec(..) for select
38             just_dropped => undef, # dropped fn inside current select
39             timer => [], # list of TimerEvent objects
40             now => scalar(gettimeofday()), # time after select
41             );
42 55         352 return $self;
43             }
44              
45             ###########################################################################
46             # adds callback for the event, that FD is readable
47             # Args: ($self,$fd,$rw,$callback,?$name)
48             # $fd: file descriptor
49             # $rw: if the callback is for read(0) or write(1)
50             # $callback: callback to be called, when fd is readable, will be called
51             # with fd as argument
52             # $name: optional name for callback, used for debugging
53             # Returns: NONE
54             ###########################################################################
55             sub addFD {
56 161     161 1 486 my Net::SIP::Dispatcher::Eventloop $self = shift;
57 161         790 my ($fd,$rw,$callback,$name) = @_;
58 161 50       620 ref($callback) or confess("wrong usage");
59 161 50       710 defined( my $fn = fileno($fd)) || return;
60             $DEBUG && DEBUG(99, "$self added fn=$fn rw($rw) sock="
61 161 50       587 . eval { ip_sockaddr2string(getsockname($fd)) });
  0         0  
62 161   100     1985 $self->{fd}[$fn][$rw] = [ $fd,$callback,$name || '' ];
63 161         1500 vec($self->{vec}[$rw],$fn,1) = 1;
64 161 50       1071 $DEBUG && DEBUG(100, "maxfd=%d",0+@{$self->{fd}});
  0         0  
65             }
66              
67             ###########################################################################
68             # removes callback for readable for FD
69             # Args: ($self,$fd,?$rw)
70             # $fd: file descriptor
71             # $rw: if disable for read(0) or write(1). Disables both if not given
72             # Returns: NONE
73             ###########################################################################
74             sub delFD {
75 155     155 1 362 my Net::SIP::Dispatcher::Eventloop $self = shift;
76 155         297 my $fd = shift;
77 155 50 33     1695 defined( my $fn = $fd && fileno($fd)) || return;
78 155 100       1032 if (!@_) {
79             $DEBUG && DEBUG(99, "$self delete fn=$fn sock="
80 120 50       327 . eval { ip_sockaddr2string(getsockname($fd)) });
  0         0  
81 120         1268 delete $self->{fd}[$fn];
82 120         537 vec($self->{vec}[0],$fn,1) = 0;
83 120         406 vec($self->{vec}[1],$fn,1) = 0;
84             # mark both read and write as dropped so we don't process events for the
85             # fd inside the same loop
86 120 50       549 $self->{just_dropped}[$fn] = [1,1] if $self->{just_dropped};
87              
88             } else {
89 35         230 for my $rw (@_) {
90             $DEBUG && DEBUG(99, "$self disable rw($rw) fn=$fn sock="
91 35 50       138 . eval { ip_sockaddr2string(getsockname($fd)) });
  0         0  
92 35         91 delete $self->{fd}[$fn][$rw];
93 35         178 vec($self->{vec}[$rw],$fn,1) = 0;
94             # mark $rw handler as dropped so we don't process events for the fd
95             # inside the same loop
96 35 50       199 $self->{just_dropped}[$fn][$rw] = 1 if $self->{just_dropped};
97             }
98             }
99 155 50       636 $DEBUG && DEBUG(100, "maxfd=%d",0+@{$self->{fd}});
  0         0  
100             }
101              
102             ###########################################################################
103             # add timer
104             # Args: ($self,$when,$callback;$repeat,$name)
105             # $when: absolute time_t or relative (smaller than a year), can be
106             # subsecond resolution
107             # $callback: callback to be called, gets timer object as argument
108             # $repeat: interval for repeated callbacks, optional
109             # $name: optional name for debugging
110             # Returns: $timer object
111             ###########################################################################
112             sub add_timer {
113 141     141 1 407 my Net::SIP::Dispatcher::Eventloop $self = shift;
114 141         780 my ($when,$callback,$repeat,$name ) = @_;
115 141 50       971 $when += $self->{now} if $when < 3600*24*365;
116              
117 141         1936 my $timer = Net::SIP::Dispatcher::Eventloop::TimerEvent->new(
118             $when, $repeat, $callback,$name );
119 141         299 push @{ $self->{timer}}, $timer;
  141         437  
120 141         1459 return $timer;
121             }
122              
123             ###########################################################################
124             # return time of currentloop, e.g. when select(2) returned
125             # Args: ()
126             # Returns: time
127             ###########################################################################
128             sub looptime {
129 406     406 1 1037 my Net::SIP::Dispatcher::Eventloop $self = shift;
130             return $self->{now}
131 406         3473 }
132              
133              
134             ###########################################################################
135             # simple mainloop
136             # Args: ($self;$timeout,@stop)
137             # $timeout: if 0 just poll once, if undef never return, otherwise return
138             # after $timeout seconds
139             # @stop: \@array of Scalar-REF, if one gets true the eventloop will be stopped
140             # Returns: NONE
141             ###########################################################################
142             sub loop {
143 138     138 1 486 my Net::SIP::Dispatcher::Eventloop $self = shift;
144 138         532 my ($timeout,@stop) = @_;
145              
146             # looptime for this run
147 138         734 my $looptime = $self->{now} = gettimeofday();
148              
149             # if timeout defined and != 0 set $end to now+timeout
150             # otherwise set end to undef|0 depending on timeout
151 138 100       595 my $end = $timeout ? $looptime + $timeout : $timeout;
152 138         369 my $to = $timeout;
153              
154 138   100     1276 while ( !$to || $to>0 ) {
155              
156 8062 100       94934 DEBUG( 100, "timeout = ".( defined($to) ? $to: '' ));
157             # handle timers
158 8062         26623 my $timer = $self->{timer};
159              
160 8062         16090 my $do_timer = 1;
161 8062   66     41256 while ( @$timer && $do_timer ) {
162 11710         19729 $do_timer = 0;
163 11710         59151 @$timer = sort { $a->{expire} <=> $b->{expire} } @$timer;
  37494         110424  
164              
165             # delete canceled timers
166 11710   66     55499 shift(@$timer) while ( @$timer && !$timer->[0]{expire} );
167              
168             # run expired timers
169 11710   66     67575 while ( @$timer && $timer->[0]{expire} <= $looptime ) {
170 3701         9712 my $t = shift(@$timer);
171             DEBUG( 50, "trigger timer(%s) %s repeat=%s",
172 3701   50     18274 $t->name,$t->{expire} || '', $t->{repeat} || '' );
      50        
173 3701         21761 invoke_callback( $t->{callback},$t );
174 3701 50 66     34454 if ( $t->{expire} && $t->{repeat} ) {
175 3689         10615 $t->{expire} += $t->{repeat};
176 3689         14195 DEBUG( 100, "timer(%s) gets repeated at %d",$t->name,$t->{expire} );
177 3689         10234 push @$timer,$t;
178 3689         29699 $do_timer = 1; # rerun loop
179             }
180             }
181             }
182              
183             # adjust timeout for select based on when next timer expires
184 8062 50       20614 if ( @$timer ) {
185 8062         20369 my $next_timer = $timer->[0]{expire} - $looptime;
186 8062 100 100     35266 $to = $next_timer if !defined($to) || $to>$next_timer;
187             }
188 8062 50       76850 DEBUG( 100, "timeout = ".( defined($to) ? $to: '' ));
189              
190 8062 100       20695 if ( grep { ${$_} } @stop ) {
  8013         14074  
  8013         30304  
191 114         419 DEBUG( 50, "stopvar triggered" );
192 114         791 return;
193             }
194              
195             # wait for selected fds
196 7948         19755 my $fds = $self->{fd};
197 7948         12952 my @vec = @{$self->{vec}};
  7948         27011  
198 7948 50       19942 $DEBUG && DEBUG(100,"BEFORE read=%s write=%s",
199             unpack("b*",$vec[0]), unpack("b*",$vec[1]));
200 7948         139694271 my $nfound = select($vec[0],$vec[1], undef, $to);
201 7948 50       70943 $DEBUG && DEBUG(100,"AFTER read=%s write=%s nfound=%d",
202             unpack("b*",$vec[0]), unpack("b*",$vec[1]), $nfound);
203 7948 50       27500 if ($nfound<0) {
204 0 0       0 next if $! == EINTR;
205 0         0 die $!
206             };
207              
208 7948         63338 $looptime = $self->{now} = gettimeofday();
209 7948         41016 $self->{just_dropped} = [];
210              
211 7948   66     50054 for(my $i=0; $nfound>0 && $i<@$fds; $i++) {
212 31019 100       106194 next if !$fds->[$i];
213 10281         23441 for my $rw (0,1) {
214 20562 100       79571 vec($vec[$rw],$i,1) or next;
215 4728         9286 $nfound--;
216 4728 50       21912 next if $self->{just_dropped}[$i][$rw];
217 4728 50       13383 $DEBUG && DEBUG(50,"call cb on fn=$i rw=$rw ".$fds->[$i][$rw][2]);
218 4728         10021 invoke_callback(@{ $fds->[$i][$rw] }[1,0]);
  4728         35069  
219             }
220             }
221              
222 7948 100       26956 if ( defined($timeout)) {
223 5480 50       17015 last if !$timeout;
224 5480         50396 $to = $end - $looptime;
225             } else {
226 2468         16150 $to = undef
227             }
228             }
229             }
230              
231              
232             ##########################################################################
233             # Timer object which gets returned from add_timer and has method for
234             # canceling the timer (by setting expire to 0)
235             ##########################################################################
236             package Net::SIP::Dispatcher::Eventloop::TimerEvent;
237 41     41   388 use fields qw( expire repeat callback name );
  41         113  
  41         212  
238              
239             ##########################################################################
240             # create new timer object, see add_timer for description of Args
241             # Args: ($class,$expire,$repeat,$callback)
242             # Returns: $self
243             ##########################################################################
244             sub new {
245 141     141   541 my ($class,$expire,$repeat,$callback,$name) = @_;
246 141         557 my $self = fields::new( $class );
247 141 100       12045 unless ( $name ) {
248             # check with caller until I find a function which is not
249             # named 'add_timer'
250 3         25 for( my $i=1;1;$i++ ) {
251 9 50       433 my (undef,undef,undef,$sub) = caller($i) or last;
252 9 100       81 next if $sub =~m{::add_timer$};
253 3         17 my $line = (caller($i-1))[2];
254 3         16 $name = "${sub}[$line]";
255 3         7 last;
256             }
257             }
258 141         784 %$self = (
259             expire => $expire,
260             repeat => $repeat,
261             callback => $callback,
262             name => $name
263             );
264 141         533 return $self;
265             }
266              
267             ##########################################################################
268             # cancel timer by setting expire to 0, it will be deleted next time
269             # the timer queue is scanned in loop
270             # Args: $self
271             # Returns: NONE
272             ##########################################################################
273             sub cancel {
274 98     98   243 my Net::SIP::Dispatcher::Eventloop::TimerEvent $self = shift;
275 98         240 $self->{expire} = 0;
276 98         702 $self->{callback} = undef;
277             }
278              
279             ##########################################################################
280             # returns name for debugging
281             # Args: $self
282             # Returns: $name
283             ##########################################################################
284             sub name {
285 7390     7390   16432 my Net::SIP::Dispatcher::Eventloop::TimerEvent $self = shift;
286 7390   50     58781 return $self->{name} || 'NONAME'
287             }
288              
289             1;