File Coverage

blib/lib/POE/Resource/Clock.pm
Criterion Covered Total %
statement 44 166 26.5
branch 7 42 16.6
condition 1 14 7.1
subroutine 9 22 40.9
pod n/a
total 61 244 25.0


line stmt bran cond sub pod time code
1             # Manage a platonic, monotonic clock to keep the event queue ordered
2              
3             package POE::Resource::Clock;
4              
5 177     177   789 use vars qw($VERSION);
  177         260  
  177         8877  
6             $VERSION = '1.367'; # NOTE - Should be #.### (three decimal places)
7              
8 177     177   1225 use strict;
  177         237  
  177         4596  
9              
10 177     177   737 use Config;
  177         249  
  177         7113  
11 177     177   50183 use POSIX;
  177         677752  
  177         1366  
12 177     177   522197 use POE::Pipe::OneWay;
  177         632  
  177         5842  
13 177     177   1020 use File::Spec;
  177         270  
  177         370858  
14              
15             require Exporter;
16             our @EXPORT_OK = qw( monotime sleep walltime wall2mono mono2wall time );
17             our @ISA = qw( Exporter );
18              
19             sub DEBUG () { 0 }
20              
21             sub CLK_TIMEOUT () { 0 }
22             sub CLK_SKEW () { 1 }
23              
24             sub CLK_EN_READ () { "rt-lock-read" }
25              
26              
27             # Perform a runtime check for a compile-time flag.
28             #
29             # TODO - Enable compiler optimization of all calls to this function.
30             # The customary way to do this is to migrate the environment variable
31             # value into a constant at compile time, then for all callers to check
32             # the constant directly. This is such a common thing to do that POE
33             # should define a utility library for it.
34              
35             sub _do_X
36             {
37 354     354   749 my( $X, $default ) = @_;
38 354         466 my $m = $X;
39 354 50       4371 return POE::Kernel->can( $m )->() if POE::Kernel->can( $m );
40 354         733 my $k = "POE_$X";
41 354 50       1421 return $ENV{$k} if exists $ENV{$k};
42 354 50       1314 return $default if defined $default;
43 354         1774 return 1;
44             }
45              
46              
47             # Try to get the exact difference between the monotonic clock's epoch
48             # and the system clock's epoch. We do this by comparing the 2 for
49             # 0.25 second or 10 samples. To compensate for delays between calling
50             # time and get_time, we run in both order. Even so, I still see up to
51             # 10 mS divergence in my dev VM between invocations.
52             #
53             # Only called once, at compile time.
54              
55             sub _exact_epoch
56             {
57 0     0     my( $monoclock ) = @_;
58              
59 0           my $N=0;
60 0           my $total = 0;
61 0           my $end = $monoclock->get_time() + 0.25;
62 0   0       while( $end > $monoclock->get_time() or $N < 20) {
63 0           my $hr = Time::HiRes::time();
64 0           my $mono = $monoclock->get_time();
65 0           $total += $hr - $mono;
66 0           $N++;
67 0           $mono = $monoclock->get_time();
68 0           $hr = Time::HiRes::time();
69 0           $total += $hr - $mono;
70 0           $N++;
71             }
72 0           DEBUG and POE::Kernel::_warn( " RT clock samples=$N" );
73 0           return $total/$N;
74             }
75              
76              
77             #########################################
78             sub _get_epoch
79             {
80 0     0     my( $monoclock, $wallclock ) = @_;
81 0           return $wallclock->get_time() - $monoclock->get_time();
82             }
83              
84              
85             #########################################
86             our $FORMAT = 'iF';
87             our $LENGTH = length pack $FORMAT, 0, 0;
88             sub _pipe_write
89             {
90 0     0     my( $write, $op, $skew ) = @_;
91 0           DEBUG and POE::Kernel::_warn( " write op=$op" );
92 0           my $buffer = pack $FORMAT, $op, $skew;
93 0           syswrite( $write, $buffer, $LENGTH );
94             }
95              
96              
97             #########################################
98             sub _pipe_read
99             {
100 0     0     my( $read ) = @_;
101 0           my $buffer;
102 0           sysread( $read, $buffer, $LENGTH );
103 0 0         return unless length $buffer;
104 0           return unpack $FORMAT, $buffer;
105             }
106              
107              
108             our( $SIGACT, $SIGSET );
109             sub _build_sig
110             {
111 0     0     my( $write ) = @_;
112             my $handler = sub {
113 0     0     DEBUG and POE::Kernel::_warn( " timeout" );
114 0           _pipe_write( $write, CLK_TIMEOUT, 0 );
115 0           };
116             my $default = eval { _sig_number( 'RTMIN' ) } ||
117 0   0       eval { _sig_number( 'RTALRM' ) } ||
118             SIGALRM;
119              
120 0   0       my $signal = _do_X( 'CLOCK_SIGNAL', $default ) || $default;
121 0           $SIGSET = POSIX::SigSet->new( $signal );
122 0           $SIGACT = POSIX::SigAction->new( $handler, $SIGSET, 0 );
123 0           $SIGACT->safe(1);
124 0           POSIX::sigaction( $signal, $SIGACT );
125 0           return $signal;
126             }
127              
128              
129             #########################################
130             sub _rt_setup
131             {
132 0     0     my( $read, $kernel ) = @_;
133 0           $kernel->loop_pause_time_watcher();
134 0           DEBUG and POE::Kernel::_warn( " Setup RT pipe" );
135             # Add to the select list
136 0           $kernel->_data_handle_condition( $read );
137 0           $kernel->loop_watch_filehandle( $read, POE::Kernel::MODE_RD() );
138             }
139              
140              
141             our $EPSILON = 0.0001;
142             sub _rt_resume
143             {
144 0     0     my( $what, $timer, $kernel, $pri ) = @_;
145 0           DEBUG and POE::Kernel::_warn( " $what pri=$pri" );
146 0           $kernel->loop_pause_time_watcher();
147 0 0         if( $pri <= monotime() ) {
148 0           $timer->set_timeout( $EPSILON );
149             }
150             else {
151 0           $timer->set_timeout( $pri, 0, 1 );
152             }
153             }
154              
155              
156             sub _rt_pause
157             {
158 0     0     my( $timer, $kernel ) = @_;
159 0           DEBUG and POE::Kernel::_warn( " Pause" );
160 0           $timer->set_timeout( 60 );
161 0           $kernel->loop_pause_time_watcher();
162             }
163              
164              
165             #########################################
166             sub _rt_read_pipe
167             {
168 0     0     my( $kernel, $read ) = @_;
169 0           my $dispatch_once;
170 0           while( 1 ) {
171 0           my( $op, $skew ) = _pipe_read( $read );
172 0 0         return unless defined $op;
173 0           DEBUG and POE::Kernel::_warn( " Read pipe op=$op" );
174 0 0         if( $op == CLK_TIMEOUT ) {
    0          
175 0 0         next unless $dispatch_once;
176 0           $kernel->_data_ev_dispatch_due();
177 0           $dispatch_once = 1;
178             }
179             elsif( $op == CLK_SKEW ) {
180 0           rt_skew( $kernel );
181 0           $dispatch_once = 0;
182             }
183 0           elsif( DEBUG ) {
184             POE::Kernel::_warn( " Unknown op=$op" );
185             }
186             }
187             }
188              
189              
190             #########################################
191             sub _rt_ready
192             {
193 0     0     my( $read, $frd, $kernel, $fileno ) = @_;
194 0 0         return 0 unless $frd == $fileno;
195 0           _rt_read_pipe( $kernel, $read );
196 0           return 1;
197             }
198              
199              
200             #########################################
201             my %SIGnames;
202             sub _sig_number
203             {
204 0     0     my( $name ) = @_;
205 0 0         return $name if $name =~ /^\d+$/;
206 0           my $X = 0;
207 0 0         $X = $1 if $name =~ s/\+(\d+)$//;
208 0 0         unless( %SIGnames ) {
209             # this code is lifted from Config pod
210 0 0 0       die "Config is missing either sig_name or sig_num; You must use a numeric signal"
211             unless $Config{sig_name} and $Config{sig_num};
212 0           my @names = split ' ', $Config{sig_name};
213 0           @SIGnames{@names} = split ' ', $Config{sig_num};
214             }
215 0           return $SIGnames{ $name }+$X;
216             }
217              
218              
219             #########################################
220             BEGIN {
221 177     177   488 my $done;
222             my $have_clock;
223 177 50       758 if( _do_X( 'USE_POSIXRT' ) ) {
224 177         289 eval {
225 177         49403 require File::Spec->catfile( qw( POSIX RT Clock.pm ) );
226 0         0 require File::Spec->catfile( qw( POSIX RT Timer.pm ) );
227 0         0 my $monoclock = POSIX::RT::Clock->new( 'monotonic' );
228 0         0 my $wallclock = POSIX::RT::Clock->new( 'realtime' );
229 0         0 *monotime = sub { return $monoclock->get_time(); };
  0         0  
230 0         0 *walltime = sub { return $wallclock->get_time(); };
  0         0  
231 0         0 *sleep = sub { $monoclock->sleep_deeply(@_) };
  0         0  
232 0 0       0 if( _do_X( 'USE_STATIC_EPOCH' ) ) {
233             # This is where we cheat: without a static epoch the tests fail
234             # because they expect alarm(), alarm_set() to arrive in order
235             # Calling _get_epoch() each time would preclude this
236 0         0 my $epoch = 0;
237 0 0       0 if( _do_X( 'USE_EXACT_EPOCH', 0 ) ) {
238 0         0 $epoch = _exact_epoch( $monoclock, $wallclock );
239             }
240             else {
241 0         0 $epoch = _get_epoch( $monoclock, $wallclock );
242             }
243 0         0 DEBUG and warn( " epoch=$epoch" );
244 0         0 *wall2mono = sub { $_[0] - $epoch };
  0         0  
245 0         0 *mono2wall = sub { $_[0] + $epoch };
  0         0  
246             }
247             else {
248 0         0 *wall2mono = sub { $_[0] - _get_epoch($monoclock, $wallclock) };
  0         0  
249 0         0 *mono2wall = sub { $_[0] + _get_epoch($monoclock, $wallclock) };
  0         0  
250              
251 0         0 my ($rd, $wr) = POE::Pipe::OneWay->new();
252 0 0       0 die "Unable to build pipe: $!" unless defined $rd;
253              
254 0         0 my $signal = _build_sig( $wr );
255              
256 0         0 my $timer = POSIX::RT::Timer->new(
257             value => 0,
258             interval => 0,
259             clock => 'monotonic',
260             signal => $signal
261             );
262              
263 0         0 $EPSILON = $monoclock->get_resolution();
264 0         0 DEBUG and warn( " epsilon=$EPSILON" );
265             #*clock_pause = sub { _rt_pause( $timer, @_ ); };
266             #*clock_reset = sub { _rt_resume( Reset = > $timer, @_ ); };
267             #*clock_resume = sub { _rt_resume( Resume = > $timer, @_ ); };
268             #*clock_setup = sub { _rt_setup( $rd, @_ ) };
269 0         0 my $frd = fileno( $rd );
270             #*clock_read = sub { _rt_ready( $rd, $frd, @_ ) };
271 0         0 $have_clock = 1;
272             }
273 0         0 $done = 1;
274             };
275 177         754 if( DEBUG ) {
276             warn( " POSIX::RT::Clock not installed: $@" ) if $@;
277             warn( " using POSIX::RT::Clock" ) if $done;
278             }
279             }
280 177 50 33     1113 if( !$done and _do_X( 'USE_HIRES' ) ) {
281 177         317 eval {
282 177         124645 require File::Spec->catfile( qw( Time HiRes.pm ) );
283 177         392097 *monotime = \&Time::HiRes::time;
284 177         454 *walltime = \&Time::HiRes::time;
285 177         369 *sleep = \&Time::HiRes::sleep;
286 177     1500   1216 *wall2mono = sub { return $_[0] };
  1500         4371  
287 177     0   566 *mono2wall = sub { return $_[0] };
  0         0  
288 177         352 $done = 1;
289             };
290 177         301 if( DEBUG ) {
291             warn( " Time::HiRes not installed: $@" )if $@;
292             warn( " using Time::HiRes" ) if $done;
293             }
294             }
295 177 50       711 unless( $done ) {
296             # \&CORE::time fails :-(
297 0         0 *monotime = sub { CORE::time };
  0         0  
298 0         0 *walltime = sub { CORE::time };
  0         0  
299 0         0 *sleep = sub { CORE::sleep(@_) };
  0         0  
300 0         0 *wall2mono = sub { return $_[0] };
  0         0  
301 0         0 *mono2wall = sub { return $_[0] };
  0         0  
302 0         0 warn( " using CORE::time" )if DEBUG;
303             }
304              
305 177 50       704 unless( $have_clock ) {
306             #*clock_pause = sub { $_[0]->loop_pause_time_watcher() };
307             #*clock_reset = sub { $_[0]->loop_reset_time_watcher(mono2wall($_[1])) };
308             #*clock_resume = sub { $_[0]->loop_resume_time_watcher(mono2wall($_[1])) };
309             #*clock_setup = sub { 0 };
310             #*clock_read = sub { 0 };
311             }
312              
313             # *time = sub { Carp::confess( "This should be monotime" ) };
314 177         7155 *time = \&walltime;
315             }
316              
317             1;
318              
319             __END__