File Coverage

blib/lib/Timeout/Queue.pm
Criterion Covered Total %
statement 79 84 94.0
branch 23 28 82.1
condition 3 6 50.0
subroutine 14 14 100.0
pod 10 10 100.0
total 129 142 90.8


line stmt bran cond sub pod time code
1             package Timeout::Queue;
2 8     8   192434 use strict;
  8         22  
  8         312  
3 8     8   45 use warnings;
  8         15  
  8         468  
4              
5             our $VERSION = '1.02';
6              
7             =head1 NAME
8              
9             Timeout::Queue - A priority queue made for handling timeouts
10              
11             =head1 DESCRIPTION
12              
13             This module is a simple priority queue based on perl's own array structures.
14             The actual sleeping is not done by this module as it is ment for integration
15             with a IO::Select based event loop or similar.
16              
17             Inserts are handled by using splice, deletes are done by marking and later
18             shifting off when it is posible.
19              
20             =head1 SYNOPSIS
21            
22             #
23             # Use as an object
24             #
25              
26             use Timeout::Queue;
27              
28             my $timeouts = new Timeout::Queue(Time => sub { return time; });
29             $timeouts->queue(
30             timeout => 1, # time out in 1 seconds.
31             callme => sub {
32             print "I timed out!!\n";
33             }
34             );
35             sleep $timeouts->timeout();
36              
37             foreach my $item ($timeouts->handle()) {
38             $item->{callme}->();
39             }
40              
41             #
42             # Use with functions and own array
43             #
44              
45             use Timeout::Queue qw(queue_timeout handle_timeout, get_timeout);
46              
47             my @timeouts;
48             my $timeout;
49             my $timeout_id = 1;
50              
51             queue_timeout(\@timeouts, time,
52             timeout_id = ++$timeout_id,
53             timeout => 1, # time out in 1 seconds.
54             callme => sub {
55             print "I timed out!!\n";
56             }
57             );
58              
59             # Get the first timeout
60             $timeout = get_timeout(\@timeouts, time);
61              
62             sleep $timeout;
63              
64             foreach my $item (handle_timeout(\@timeouts, time)) {
65             $item->{callme}->();
66             }
67              
68             # Get the next timeout
69             $timeout = get_timeout(\@timeouts, time);
70              
71              
72             =head1 METHODS
73              
74             =over
75              
76             =cut
77              
78 8     8   48 use base "Exporter";
  8         24  
  8         10294  
79              
80             our @EXPORT_OK = qw(queue_timeout delete_timeout handle_timeout get_timeout);
81              
82             =item new()
83              
84             Creates a new Timeout::Queue object.
85              
86             You can optionally add a a "Time" option if you would like to use something
87             else than the build in time function. This can be usefull if your sleeping
88             mechanism supports sub second precision.
89            
90             The default works like this if nothing is given:
91              
92             $timeouts->new(Time => sub { return time; });
93              
94             =cut
95              
96             sub new {
97 6     6 1 127 my ($class, %opts) = @_;
98              
99             my %self = (
100             timeouts => [],
101             timeout => undef,
102             timeout_id => 0,
103 4     4   18 time => sub { return time; },
104 6         56 last_time => 0,
105             );
106            
107 6 100       35 $self{time} = $opts{Time} if exists $opts{Time};
108              
109 6   33     70 return bless \%self, (ref $class || $class);
110             }
111              
112             =item queue(timeout => $timeout)
113              
114             Queue a new timeout item, only the timeout values is used from the list. The
115             rest will be returned later in a hash reference by C.
116              
117             Returns the timeout id or an array with timeout id and the next timeout in the queue.
118              
119             =cut
120              
121             sub queue {
122 11     11 1 77 my ($self, @item) = @_;
123            
124 11         67 my $timeout = queue_timeout($self->{timeouts}, $self->{time}->(), @item,
125             timeout_id => ++$self->{timeout_id});
126            
127 11 50       36 if(wantarray) {
128 0         0 return ($self->{timeout_id}, $timeout);
129             } else {
130 11         41 return $self->{timeout_id};
131             }
132             }
133              
134             =item delete($key, $value)
135              
136             Delete the item's where key and value are equal to what is given.
137              
138             Returns the next timeout in the queue.
139              
140             =cut
141              
142             sub delete {
143 4     4 1 8 my ($self, $key, $value) = @_;
144 4         15 return delete_timeout($self->{timeouts}, $self->{time}->(), $key, $value);
145             }
146              
147             =item handle()
148              
149             Returns all the items that have timed out so far.
150              
151             =cut
152              
153             sub handle {
154 4     4 1 896 my ($self) = @_;
155 4         21 return handle_timeout($self->{timeouts},
156             $self->{time}->());
157             }
158              
159             =item timeout()
160              
161             Return the next timeout on the queue or undef if it's empty.
162              
163             =cut
164              
165             sub timeout {
166 6     6 1 29 my ($self) = @_;
167 6         20 return get_timeout($self->{timeouts}, $self->{time}->());
168             }
169              
170              
171             =item timeouts()
172              
173             Return array refrence with queued timeouts.
174              
175             =cut
176              
177             sub timeouts {
178 8     8 1 20 my ($self) = @_;
179 8         45 return $self->{timeouts};
180             }
181              
182             =item queue_timeout(\@timeouts, timeout => $timeout)
183              
184             Queue a new timeout item, only the timeout values is used from the list. The
185             rest will be returned later in a hash reference by C.
186              
187             Returns the next timeout or -1 if it was not change by the queueing.
188              
189             =cut
190              
191             sub queue_timeout {
192 12     12 1 118 my ($timeouts, $time, %item) = @_;
193            
194 12         21 my $timeout = -1;
195 12         30 $item{expires} = $time + $item{timeout};
196            
197             #print "expires: $item{expires}\n";
198              
199             # Optimize by adding from the end as this will be the case
200             # when we have a default timeout that never changes.
201 12 100       21 if(@{$timeouts} == 0) {
  12 100       52  
202             # The queue is empty
203 7         15 push(@{$timeouts}, \%item);
  7         19  
204 7         17 $timeout = $item{expires} - $time;
205             } elsif ($item{expires} > $timeouts->[-1]{expires}) {
206             # The item is bigger than anything else
207 1         1 push(@{$timeouts}, \%item);
  1         20  
208             } else {
209             # Insert the timeout in the right place in the timeout queue
210 4         7 for(my $i=int(@{$timeouts})-1; $i >= 0; $i--) {
  4         21  
211 6 50       32 if($timeouts->[$i]{expires} == 0) {
    100          
    50          
212             # Deleted item, ignore.
213             } elsif($item{expires} >= $timeouts->[$i]{expires}) {
214             # The item fits somewhere in the middle
215 4         6 splice(@{$timeouts}, $i+1,0, \%item);
  4         19  
216 4         23 last;
217             } elsif ($i == 0) {
218             # The item was small than anything else
219 0         0 unshift (@{$timeouts}, \%item);
  0         0  
220 0         0 $timeout = $item{expires} - $time;
221             }
222             }
223             }
224              
225 12         28 return $timeout;
226             }
227              
228             =item delete_timeout(\@timeouts, $key, $value)
229              
230             Delete the item's where key and value are equal to what is given.
231              
232             Returns the next timeout.
233              
234             =cut
235              
236             sub delete_timeout {
237 4     4 1 17 my ($timeouts, $time, $key, $value) = @_;
238 4         7 my $timeout;
239            
240             # Make item as delete.
241 4         6 for(my $i=0; $i < int(@{$timeouts}); $i++) {
  12         31  
242 8 100 66     49 if(exists $timeouts->[$i]{$key} and $value eq $timeouts->[$i]{$key}) {
243 3         8 $timeouts->[$i]{expires} = 0;
244             }
245             }
246              
247             # Trim @timeouts queue and set timeout
248 4         6 while(my $item = shift(@{$timeouts})) {
  7         22  
249 6 100       20 if($item->{expires} != 0) {
250 3         27 unshift(@{$timeouts}, $item);
  3         8  
251 3         4 $timeout = $item->{expires} - $time;
252 3         6 last;
253             }
254             }
255              
256             # Trim @timeouts queue from behind.
257 4         6 while(my $item = pop(@{$timeouts})) {
  4         10  
258 3 50       9 if($item->{expires} != 0) {
259 3         4 push(@{$timeouts}, $item);
  3         4  
260 3         5 last;
261             }
262             }
263              
264 4         13 return $timeout;
265             }
266              
267              
268             =item handle_timeout(\@timeouts, time())
269              
270             Returns all the items that have timed out so far.
271              
272             =cut
273              
274             sub handle_timeout {
275 5     5 1 35 my ($timeouts, $time) = @_;
276            
277 5         10 my @items;
278 5         9 while(my $item = shift @{$timeouts}) {
  9         42  
279 5 50       40 if($item->{expires} == 0) {
    100          
280 0         0 next; # Remove item from queue
281            
282             } elsif($item->{expires} <= $time) {
283 4         11 push(@items, $item);
284            
285             } else {
286             # No more items timed out, put back on queue.
287 1         2 unshift(@{$timeouts}, $item);
  1         3  
288 1         2 last;
289             }
290             }
291              
292 5         21 return @items;
293             }
294              
295             =item get_timeout(\@timeouts, time())
296              
297             Return the next timeout on the queue or undef if it's empty.
298              
299             =cut
300              
301             sub get_timeout {
302 8     8 1 977 my ($timeouts, $time) = @_;
303              
304 8 100       27 if(@{$timeouts} > 0) {
  8         26  
305 7         25 my $timeout = ($timeouts->[0]{expires}-$time);
306 7 100       46 return $timeout >= 0 ? $timeout : 0;
307             } else {
308 1         109 return;
309             }
310             }
311              
312             =back
313              
314             =head1 AUTHOR
315              
316             Troels Liebe Bentsen
317              
318             =head1 COPYRIGHT
319              
320             Copyright(C) 2005-2007 Troels Liebe Bentsen
321              
322             This library is free software; you can redistribute it and/or modify
323             it under the same terms as Perl itself.
324              
325             =cut
326              
327             1;