File Coverage

blib/lib/Mojo/Reactor/Poll.pm
Criterion Covered Total %
statement 100 100 100.0
branch 43 46 93.4
condition 14 23 60.8
subroutine 22 22 100.0
pod 12 12 100.0
total 191 203 94.0


line stmt bran cond sub pod time code
1             package Mojo::Reactor::Poll;
2 67     67   242742 use Mojo::Base 'Mojo::Reactor';
  67         169  
  67         498  
3              
4 67     67   508 use Carp qw(croak);
  67         432  
  67         4571  
5 67     67   543 use IO::Poll qw(POLLERR POLLHUP POLLIN POLLNVAL POLLOUT POLLPRI);
  67         221  
  67         4959  
6 67     67   444 use List::Util qw(min);
  67         154  
  67         5279  
7 67     67   407 use Mojo::Util qw(md5_sum steady_time);
  67         142  
  67         4038  
8 67     67   504 use Time::HiRes qw(usleep);
  67         129  
  67         1206  
9              
10             sub again {
11 11582     11582 1 28922 my ($self, $id, $after) = @_;
12 11582 100       44198 croak 'Timer not active' unless my $timer = $self->{timers}{$id};
13 11581 100       27712 $timer->{after} = $after if defined $after;
14 11581         40808 $timer->{time} = steady_time + $timer->{after};
15             }
16              
17             sub io {
18 1140     1140 1 4991 my ($self, $handle, $cb) = @_;
19 1140   33     7507 $self->{io}{fileno($handle) // croak 'Handle is closed'} = {cb => $cb};
20 1140         3590 return $self->watch($handle, 1, 1);
21             }
22              
23 3534     3534 1 22390 sub is_running { !!shift->{running} }
24              
25             sub next_tick {
26 2142     2142 1 8437 my ($self, $cb) = @_;
27 2142         3660 push @{$self->{next_tick}}, $cb;
  2142         5665  
28 2142   66     12535 $self->{next_timer} //= $self->timer(0 => \&_next);
29 2142         11512 return undef;
30             }
31              
32             sub one_tick {
33 41996     41996 1 69076 my $self = shift;
34              
35             # Just one tick
36 41996 100       103374 local $self->{running} = 1 unless $self->{running};
37              
38             # Wait for one event
39 41996         63157 my $i;
40 41996   66     162103 until ($i || !$self->{running}) {
41              
42             # Stop automatically if there is nothing to watch
43 41996 100 100     60403 return $self->stop unless keys %{$self->{timers}} || keys %{$self->{io}};
  41996         100713  
  11         66  
44              
45             # Calculate ideal timeout based on timers and round up to next millisecond
46 41986         68646 my $min = min map { $_->{time} } values %{$self->{timers}};
  85298         207606  
  41986         98477  
47 41986 100       127889 my $timeout = defined $min ? $min - steady_time : 0.5;
48 41986 100       211695 $timeout = $timeout <= 0 ? 0 : int($timeout * 1000) + 1;
49              
50             # I/O
51 41986 100       62211 if (keys %{$self->{io}}) {
  41986 100       108401  
52 20838         34168 my @poll = map { $_ => $self->{io}{$_}{mode} } keys %{$self->{io}};
  55538         144965  
  20838         56375  
53              
54             # This may break in the future, but is worth it for performance
55 20838 100       7709200 if (IO::Poll::_poll($timeout, @poll) > 0) {
56 19684         76060 while (my ($fd, $mode) = splice @poll, 0, 2) {
57              
58 50358 100       114750 if ($mode & (POLLIN | POLLPRI | POLLNVAL | POLLHUP | POLLERR)) {
59 16234 100       54662 next unless my $io = $self->{io}{$fd};
60 16233 50       63640 ++$i and $self->_try('I/O watcher', $io->{cb}, 0);
61             }
62 50357 100 100     225920 next unless $mode & POLLOUT && (my $io = $self->{io}{$fd});
63 8352 50       39681 ++$i and $self->_try('I/O watcher', $io->{cb}, 1);
64             }
65             }
66             }
67              
68             # Wait for timeout if poll can't be used
69 17         871752 elsif ($timeout) { usleep($timeout * 1000) }
70              
71             # Timers (time should not change in between timers)
72 41986         110714 my $now = steady_time;
73 41986         169694 for my $id (keys %{$self->{timers}}) {
  41986         117000  
74 85356 100       218290 next unless my $t = $self->{timers}{$id};
75 85351 100       303000 next unless $t->{time} <= $now;
76              
77             # Recurring timer
78 23329 100       44498 if ($t->{recurring}) { $t->{time} = $now + $t->{after} }
  21887         42470  
79              
80             # Normal timer
81 1442         6746 else { $self->remove($id) }
82              
83 23329 50 33     90452 ++$i and $self->_try('Timer', $t->{cb}) if $t->{cb};
84             }
85             }
86             }
87              
88 14     14 1 1087 sub recurring { shift->_timer(1, @_) }
89              
90             sub remove {
91 3725     3725 1 15691 my ($self, $remove) = @_;
92 3725 100       23075 return !!delete $self->{timers}{$remove} unless ref $remove;
93 1038   33     23140 return !!delete $self->{io}{fileno($remove) // croak 'Handle is closed'};
94             }
95              
96 7     7 1 1779 sub reset { delete @{shift()}{qw(events io next_tick next_timer running timers)} }
  7         124  
97              
98             sub start {
99 1097     1097 1 2370 my $self = shift;
100 1097   50     7466 local $self->{running} = ($self->{running} || 0) + 1;
101 1097         4774 $self->one_tick while $self->{running};
102             }
103              
104 1117     1117 1 4946 sub stop { delete shift->{running} }
105              
106 2179     2179 1 22463 sub timer { shift->_timer(0, @_) }
107              
108             sub watch {
109 10617     10617 1 28444 my ($self, $handle, $read, $write) = @_;
110              
111 10617 100       65199 croak 'I/O watcher not active' unless my $io = $self->{io}{fileno $handle};
112 10616         26750 $io->{mode} = 0;
113 10616 100       31563 $io->{mode} |= POLLIN | POLLPRI if $read;
114 10616 100       25237 $io->{mode} |= POLLOUT if $write;
115              
116 10616         31714 return $self;
117             }
118              
119             sub _id {
120 2193     2193   4095 my $self = shift;
121 2193         3653 my $id;
122 2193         6619 do { $id = md5_sum 't' . steady_time . rand } while $self->{timers}{$id};
  2193         12124  
123 2193         47893 return $id;
124             }
125              
126             sub _next {
127 1381     1381   2658 my $self = shift;
128 1381         4055 delete $self->{next_timer};
129 1381         5820 while (my $cb = shift @{$self->{next_tick}}) { $self->$cb }
  2141         7574  
  3522         11887  
130             }
131              
132             sub _timer {
133 2193     2193   6996 my ($self, $recurring, $after, $cb) = @_;
134 2193         6491 my $id = $self->_id;
135 2193         7055 $self->{timers}{$id} = {cb => $cb, after => $after, recurring => $recurring, time => steady_time + $after};
136 2193         26469 return $id;
137             }
138              
139             sub _try {
140 47914     47914   111638 my ($self, $what, $cb) = (shift, shift, shift);
141 47914 100       79368 eval { $self->$cb(@_); 1 } or $self->emit(error => "$what failed: $@");
  47914         143490  
  47911         337819  
142             }
143              
144             1;
145              
146             =encoding utf8
147              
148             =head1 NAME
149              
150             Mojo::Reactor::Poll - Low-level event reactor with poll support
151              
152             =head1 SYNOPSIS
153              
154             use Mojo::Reactor::Poll;
155              
156             # Watch if handle becomes readable or writable
157             my $reactor = Mojo::Reactor::Poll->new;
158             $reactor->io($first => sub ($reactor, $writable) {
159             say $writable ? 'First handle is writable' : 'First handle is readable';
160             });
161              
162             # Change to watching only if handle becomes writable
163             $reactor->watch($first, 0, 1);
164              
165             # Turn file descriptor into handle and watch if it becomes readable
166             my $second = IO::Handle->new_from_fd($fd, 'r');
167             $reactor->io($second => sub ($reactor, $writable) {
168             say $writable ? 'Second handle is writable' : 'Second handle is readable';
169             })->watch($second, 1, 0);
170              
171             # Add a timer
172             $reactor->timer(15 => sub ($reactor) {
173             $reactor->remove($first);
174             $reactor->remove($second);
175             say 'Timeout!';
176             });
177              
178             # Start reactor if necessary
179             $reactor->start unless $reactor->is_running;
180              
181             =head1 DESCRIPTION
182              
183             L is a low-level event reactor based on L.
184              
185             =head1 EVENTS
186              
187             L inherits all events from L.
188              
189             =head1 METHODS
190              
191             L inherits all methods from L and implements the following new ones.
192              
193             =head2 again
194              
195             $reactor->again($id);
196             $reactor->again($id, 0.5);
197              
198             Restart timer and optionally change the invocation time. Note that this method requires an active timer.
199              
200             =head2 io
201              
202             $reactor = $reactor->io($handle => sub {...});
203              
204             Watch handle for I/O events, invoking the callback whenever handle becomes readable or writable.
205              
206             # Callback will be executed twice if handle becomes readable and writable
207             $reactor->io($handle => sub ($reactor, $writable) {
208             say $writable ? 'Handle is writable' : 'Handle is readable';
209             });
210              
211             =head2 is_running
212              
213             my $bool = $reactor->is_running;
214              
215             Check if reactor is running.
216              
217             =head2 next_tick
218              
219             my $undef = $reactor->next_tick(sub {...});
220              
221             Execute callback as soon as possible, but not before returning or other callbacks that have been registered with this
222             method, always returns C.
223              
224             =head2 one_tick
225              
226             $reactor->one_tick;
227              
228             Run reactor until an event occurs or no events are being watched anymore.
229              
230             # Don't block longer than 0.5 seconds
231             my $id = $reactor->timer(0.5 => sub {});
232             $reactor->one_tick;
233             $reactor->remove($id);
234              
235             =head2 recurring
236              
237             my $id = $reactor->recurring(0.25 => sub {...});
238              
239             Create a new recurring timer, invoking the callback repeatedly after a given amount of time in seconds.
240              
241             =head2 remove
242              
243             my $bool = $reactor->remove($handle);
244             my $bool = $reactor->remove($id);
245              
246             Remove handle or timer.
247              
248             =head2 reset
249              
250             $reactor->reset;
251              
252             Remove all handles and timers.
253              
254             =head2 start
255              
256             $reactor->start;
257              
258             Start watching for I/O and timer events, this will block until L is called or no events are being watched
259             anymore.
260              
261             # Start reactor only if it is not running already
262             $reactor->start unless $reactor->is_running;
263              
264             =head2 stop
265              
266             $reactor->stop;
267              
268             Stop watching for I/O and timer events.
269              
270             =head2 timer
271              
272             my $id = $reactor->timer(0.5 => sub {...});
273              
274             Create a new timer, invoking the callback after a given amount of time in seconds.
275              
276             =head2 watch
277              
278             $reactor = $reactor->watch($handle, $readable, $writable);
279              
280             Change I/O events to watch handle for with true and false values. Note that this method requires an active I/O watcher.
281              
282             # Watch only for readable events
283             $reactor->watch($handle, 1, 0);
284              
285             # Watch only for writable events
286             $reactor->watch($handle, 0, 1);
287              
288             # Watch for readable and writable events
289             $reactor->watch($handle, 1, 1);
290              
291             # Pause watching for events
292             $reactor->watch($handle, 0, 0);
293              
294             =head1 SEE ALSO
295              
296             L, L, L.
297              
298             =cut