File Coverage

blib/lib/Linux/Event/Timer.pm
Criterion Covered Total %
statement 72 95 75.7
branch 19 44 43.1
condition 2 5 40.0
subroutine 12 15 80.0
pod 7 7 100.0
total 112 166 67.4


line stmt bran cond sub pod time code
1             package Linux::Event::Timer;
2              
3 2     2   446644 use v5.36;
  2         7  
4 2     2   13 use strict;
  2         7  
  2         95  
5 2     2   10 use warnings;
  2         9  
  2         221  
6              
7             our $VERSION = '0.010';
8              
9 2     2   11 use Carp qw(croak);
  2         15  
  2         139  
10 2     2   12 use Scalar::Util qw(blessed);
  2         3  
  2         139  
11              
12 2     2   1101 use Linux::FD::Timer 0.015 ();
  2         19014  
  2         79  
13 2     2   16 use Fcntl qw(FD_CLOEXEC F_GETFD F_SETFD);
  2         4  
  2         1708  
14              
15             # ------------------------------------------------------------------
16             # Constructor
17             # ------------------------------------------------------------------
18              
19 1     1 1 146459 sub new ($class, %opt) {
  1         2  
  1         3  
  1         2  
20              
21 1 50       5 croak "Linux::Event::Timer only works on Linux"
22             unless $^O eq 'linux';
23              
24 1         5 my ($after, $every) = delete @opt{qw(after every)};
25              
26 1         1 my $mode_count = 0;
27 1 50       4 $mode_count++ if defined $after;
28 1 50       3 $mode_count++ if defined $every;
29 1 50       4 croak "Only one of after/every may be supplied"
30             if $mode_count > 1;
31              
32 1         3 my $self = bless {}, $class;
33              
34 1 50       5 if (defined $opt{timerfd}) {
35 0         0 my $tfd = delete $opt{timerfd};
36              
37 0 0       0 croak "timerfd must be an object"
38             unless blessed($tfd);
39              
40             # Linux::FD::Timer objects are filehandles with timer methods.
41             # We only require the operations this wrapper exposes.
42 0         0 for my $m (qw(set_timeout receive)) {
43 0 0       0 croak "timerfd missing required method '$m'"
44             unless $tfd->can($m);
45             }
46              
47 0         0 $self->{tfd} = $tfd;
48             }
49             else {
50 1 50       3 my $nonblocking = exists $opt{nonblocking} ? !!$opt{nonblocking} : 1;
51 1 50       3 my $cloexec = exists $opt{cloexec} ? !!$opt{cloexec} : 1;
52 1 50       4 my $clock = exists $opt{clock} ? $opt{clock} : 'monotonic';
53              
54 1         2 my @flags;
55 1 50       3 push @flags, 'non-blocking' if $nonblocking;
56              
57             # Linux::FD::Timer currently documents 'non-blocking' as a supported flag.
58             # We implement cloexec ourselves via fcntl if requested.
59 1 50       81 my $tfd = Linux::FD::Timer->new($clock, @flags)
60             or croak "Linux::FD::Timer->new('$clock') failed";
61              
62 1         6 $self->{tfd} = $tfd;
63              
64 1 50       8 if ($cloexec) {
65 1         3 my $fd = fileno($tfd);
66 1 50       4 if (defined $fd) {
67 1         4 my $old = fcntl($tfd, F_GETFD, 0);
68 1   50     7 fcntl($tfd, F_SETFD, ($old // 0) | FD_CLOEXEC);
69             }
70             }
71             }
72              
73 1 50       3 if (keys %opt) {
74 0         0 croak "Unknown option(s): " . join(", ", sort keys %opt);
75             }
76              
77             # Optional constructor arming
78 1 50       2 $self->after($after) if defined $after;
79 1 50       6 $self->every($every) if defined $every;
80              
81 1         5 return $self;
82             }
83              
84             # ------------------------------------------------------------------
85             # Event loop integration
86             # ------------------------------------------------------------------
87              
88 2     2 1 443 sub fd ($self) { fileno($self->{tfd}) }
  2         7  
  2         3  
  2         18  
89              
90 0     0 1 0 sub fh ($self) { $self->{tfd} }
  0         0  
  0         0  
  0         0  
91              
92 2     2 1 40199 sub read_ticks ($self) {
  2         5  
  2         6  
93 2         35 my $n = $self->{tfd}->receive;
94 2 50       13 return defined($n) ? $n : 0;
95             }
96              
97             # ------------------------------------------------------------------
98             # Arming
99             # ------------------------------------------------------------------
100              
101 0     0 1 0 sub disarm ($self) {
  0         0  
  0         0  
102 0         0 $self->{tfd}->set_timeout(0, 0);
103 0         0 return $self;
104             }
105              
106 0     0 1 0 sub after ($self, $seconds) {
  0         0  
  0         0  
  0         0  
107 0         0 _num($seconds, 'seconds');
108 0 0       0 $seconds = 0 if $seconds < 0;
109 0         0 $self->{tfd}->set_timeout($seconds, 0);
110 0         0 return $self;
111             }
112              
113 1     1 1 2 sub every ($self, $interval) {
  1         15  
  1         2  
  1         1  
114 1         5 _num($interval, 'interval');
115 1 50       3 croak "interval must be > 0"
116             unless $interval > 0;
117              
118 1         15 $self->{tfd}->set_timeout($interval, $interval);
119 1         2 return $self;
120             }
121              
122             # ------------------------------------------------------------------
123             # Internal validation
124             # ------------------------------------------------------------------
125              
126 1     1   2 sub _num ($v, $name) {
  1         1  
  1         2  
  1         1  
127 1 50       3 croak "$name is required" unless defined $v;
128 1 50 33     19 croak "$name must be numeric"
129             if ref($v) || $v !~ /\A-?(?:\d+(?:\.\d*)?|\.\d+)\z/;
130 1         2 return;
131             }
132              
133             1;
134              
135             __END__