line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package IO::AsyncX::SharedTimer; |
2
|
|
|
|
|
|
|
# ABSTRACT: Low-accuracy shared timers for IO::Async |
3
|
2
|
|
|
2
|
|
36161
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
87
|
|
4
|
2
|
|
|
2
|
|
10
|
use warnings; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
61
|
|
5
|
|
|
|
|
|
|
|
6
|
2
|
|
|
2
|
|
919
|
use parent qw(IO::Async::Notifier); |
|
2
|
|
|
|
|
554
|
|
|
2
|
|
|
|
|
13
|
|
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
our $VERSION = '0.001'; |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
=head1 NAME |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
IO::AsyncX::SharedTimer - provides L timers which sacrifice accuracy for performance |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=head1 VERSION |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
version 0.001 |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 SYNOPSIS |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# Needs to be added to a loop before you can |
21
|
|
|
|
|
|
|
# call any other methods |
22
|
|
|
|
|
|
|
my $loop = IO::Async::Loop->new; |
23
|
|
|
|
|
|
|
$loop->add( |
24
|
|
|
|
|
|
|
my $timer = IO::AsyncX::SharedTimer->new( |
25
|
|
|
|
|
|
|
# Combine timers into 50ms buckets, and |
26
|
|
|
|
|
|
|
# use cached value for ->now with 50ms expiry |
27
|
|
|
|
|
|
|
resolution => 0.050, |
28
|
|
|
|
|
|
|
) |
29
|
|
|
|
|
|
|
); |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# Report current time, accurate to ~50ms (defined |
32
|
|
|
|
|
|
|
# by the resolution parameter, as above) |
33
|
|
|
|
|
|
|
use feature qw(say); |
34
|
|
|
|
|
|
|
say "Time is roughly " . $timer->now; |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
# Set a timeout for ~30s on an I/O operation |
37
|
|
|
|
|
|
|
Future->wait_any( |
38
|
|
|
|
|
|
|
$client->read_until(qr/\n/), |
39
|
|
|
|
|
|
|
$timer->timeout_future(after => 30) |
40
|
|
|
|
|
|
|
)->get; |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
=head1 DESCRIPTION |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
This module provides various time-related utility methods for use |
45
|
|
|
|
|
|
|
with larger L applications. |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
In situations where you have many related timers - connection expiry, |
48
|
|
|
|
|
|
|
for example - there may be some overhead in having each of these in the |
49
|
|
|
|
|
|
|
timer priority queue as a separate event. Sometimes the exact trigger |
50
|
|
|
|
|
|
|
time is not so important, which is where this class comes in. You get |
51
|
|
|
|
|
|
|
to specify an accuracy, and all timers which would occur within the |
52
|
|
|
|
|
|
|
same window of time will be grouped together as a single timer event. |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
This may reduce calls to poll/epoll and timer management overhead, but |
55
|
|
|
|
|
|
|
please benchmark the difference before assuming that this module will |
56
|
|
|
|
|
|
|
be worth using - for some applications the overhead this introduces will |
57
|
|
|
|
|
|
|
outweigh any potential benefits. |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
One benchmark gave the following results for 1ms resolution across 100 |
60
|
|
|
|
|
|
|
timers set to rand(1) seconds: |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
Rate normal shared |
63
|
|
|
|
|
|
|
normal 32.9/s -- -50% |
64
|
|
|
|
|
|
|
shared 65.7/s 100% -- |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
See the examples/ directory for code. |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
=cut |
69
|
|
|
|
|
|
|
|
70
|
2
|
|
|
2
|
|
28022
|
use Time::HiRes (); |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
26
|
|
71
|
2
|
|
|
2
|
|
879
|
use curry::weak; |
|
2
|
|
|
|
|
1399
|
|
|
2
|
|
|
|
|
280
|
|
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=head1 METHODS |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
=cut |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=head2 configure |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
Change the current resolution. |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
Takes one named parameter: |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=over 4 |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
=item * resolution - the resolution for timers and L, in seconds |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
=back |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
=cut |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
sub configure { |
92
|
2
|
|
|
2
|
1
|
7310
|
my ($self, %args) = @_; |
93
|
2
|
50
|
|
|
|
9
|
if(exists $args{resolution}) { |
94
|
2
|
|
|
|
|
12
|
$self->{resolution} = delete $args{resolution}; |
95
|
|
|
|
|
|
|
} |
96
|
2
|
|
|
|
|
14
|
$self->SUPER::configure(%args); |
97
|
|
|
|
|
|
|
} |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
=head2 resolution |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
Returns the current resolution. |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
=cut |
104
|
|
|
|
|
|
|
|
105
|
31
|
|
|
31
|
1
|
1007
|
sub resolution { shift->{resolution} } |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
=head2 now |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
Returns an approximation of the current time. |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
On first call, it will return (and cache) the value provided by |
112
|
|
|
|
|
|
|
the L C |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
Subsequent calls will return this same value. The cached value expires |
115
|
|
|
|
|
|
|
after L seconds - note that the expiry happens via the event |
116
|
|
|
|
|
|
|
loop so if your code does not cede control back to the main event loop |
117
|
|
|
|
|
|
|
in a timely fashion, the cached value will not expire. Put another way: |
118
|
|
|
|
|
|
|
the value will be cached for at least L seconds. |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
There's a good chance that the method call overhead will incur a heavier |
121
|
|
|
|
|
|
|
performance impact than just calling L C |
122
|
|
|
|
|
|
|
As always, profile and benchmark first. |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
Example usage: |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
my $start = $timer->now; |
127
|
|
|
|
|
|
|
$loop->run; |
128
|
|
|
|
|
|
|
my $elapsed = 1000.0 * ($timer->now - $start); |
129
|
|
|
|
|
|
|
say "Operation took about ${elapsed}ms to complete."; |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
=cut |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
sub now { |
134
|
22
|
|
|
22
|
1
|
1002727
|
my ($self) = @_; |
135
|
22
|
|
50
|
|
|
92
|
$self->{after} ||= ($self->loop or die "Must add this timer to a loop first")->delay_future( |
|
|
|
66
|
|
|
|
|
136
|
|
|
|
|
|
|
after => $self->resolution |
137
|
|
|
|
|
|
|
)->on_ready($self->curry::weak::expire); |
138
|
22
|
|
66
|
|
|
6662
|
$self->{now} //= Time::HiRes::time; |
139
|
|
|
|
|
|
|
} |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
=head2 delay_future |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
Returns a L which will resolve after approximately L seconds. |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
See the C documentation in L for more details. |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
=cut |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
=head2 timeout_future |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
Returns a L which will fail after approximately L seconds. |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
See the C documentation in L for more details. |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
=cut |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
BEGIN { |
158
|
2
|
|
|
2
|
|
7
|
for my $m (qw(delay_future timeout_future)) { |
159
|
2
|
|
|
2
|
|
10
|
no strict 'refs'; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
312
|
|
160
|
4
|
|
|
|
|
152
|
*{__PACKAGE__ . '::' . $m} = sub { |
161
|
11
|
|
|
11
|
|
3435
|
my ($self, %args) = @_; |
162
|
11
|
50
|
|
|
|
44
|
my $at = exists $args{at} ? delete $args{at} : $self->now + delete $args{after} |
|
|
50
|
|
|
|
|
|
163
|
|
|
|
|
|
|
or die "Invalid or unspecified time"; |
164
|
|
|
|
|
|
|
# Resolution may change over time. We don't want to stomp on old values when this happens. |
165
|
|
|
|
|
|
|
# We also want to support both timeout+delay with the same values. |
166
|
11
|
|
|
|
|
29
|
my $bucket = join '-', $m, $self->resolution, int(($at - $^T) / $self->resolution); |
167
|
11
|
|
|
|
|
31
|
my $f = $self->loop->new_future; |
168
|
|
|
|
|
|
|
($self->{bucket}{$bucket} ||= $self->loop->$m(at => $at)->on_ready(sub { |
169
|
5
|
|
|
5
|
|
1502299
|
delete $self->{bucket}{$bucket} |
170
|
11
|
|
66
|
|
|
247
|
}))->on_ready($f); |
171
|
11
|
|
|
|
|
579
|
$f |
172
|
4
|
|
|
|
|
15
|
}; |
173
|
|
|
|
|
|
|
} |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
sub expire { |
177
|
5
|
|
|
5
|
0
|
199613
|
my ($self) = @_; |
178
|
5
|
|
|
|
|
12
|
delete @{$self}{qw(now after)}; |
|
5
|
|
|
|
|
29
|
|
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
1; |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
__END__ |