File Coverage

blib/lib/Linux/Event/Wakeup.pm
Criterion Covered Total %
statement 72 80 90.0
branch 15 30 50.0
condition 2 6 33.3
subroutine 11 12 91.6
pod 3 5 60.0
total 103 133 77.4


line stmt bran cond sub pod time code
1             package Linux::Event::Wakeup;
2 13     13   134 use v5.36;
  13         36  
3 13     13   68 use strict;
  13         40  
  13         234  
4 13     13   40 use warnings;
  13         15  
  13         828  
5              
6             our $VERSION = '0.012';
7              
8 13     13   151 use Carp qw(croak);
  13         53  
  13         754  
9 13     13   55 use Scalar::Util qw(weaken);
  13         16  
  13         566  
10 13     13   51 use Fcntl qw(F_GETFD F_SETFD FD_CLOEXEC);
  13         24  
  13         8126  
11              
12             # eventfd-backed wakeups for Linux::Event.
13             #
14             # Semantics contract (single-waker model):
15             # - Exactly one waker per loop (cached by Loop).
16             # - Lazily created on first use.
17             # - Never destroyed during loop lifetime.
18             # - The Loop installs an internal read watcher that drains the fd.
19             # - User code MUST NOT watch($waker->fh, ...) directly.
20             # - signal() is safe from any thread.
21             # - drain() is non-blocking and returns the coalesced count.
22              
23 1     1 0 2 sub new ($class, %args) {
  1         1  
  1         2  
  1         1  
24 1         2 my $loop = delete $args{loop};
25 1 50       2 croak "loop is required" if !$loop;
26 1 50       2 croak "unknown args: " . join(", ", sort keys %args) if %args;
27              
28 1         2 weaken($loop);
29              
30 1         4 return bless {
31             loop => $loop,
32             _fh => undef,
33             }, $class;
34             }
35              
36 0     0 0 0 sub loop ($self) { return $self->{loop} }
  0         0  
  0         0  
  0         0  
37              
38 2     2 1 914 sub fh ($self) {
  2         2  
  2         7  
39 2         15 $self->_ensure_fd;
40 2         13 return $self->{_fh};
41             }
42              
43 1     1 1 2 sub signal ($self, $n = 1) {
  1         2  
  1         1  
  1         2  
44 1         2 $self->_ensure_fd;
45              
46 1 50       2 $n = 1 if !defined $n;
47 1 50 33     32 croak "signal() increment must be a positive integer" if $n !~ /\A\d+\z/ || $n < 1;
48              
49 1 50       4 my $fh = $self->{_fh} or croak "waker not initialized";
50              
51 1         1 my $ok = eval { $fh->add(int($n)); 1 };
  1         8  
  1         2  
52 1 50       2 if (!$ok) {
53             # add() will fail with EAGAIN if the counter would overflow in non-blocking mode.
54 0 0       0 croak "eventfd add failed: $@" if $@;
55 0         0 croak "eventfd add failed: $!";
56             }
57              
58 1         2 return 1;
59             }
60              
61 2     2 1 7 sub drain ($self) {
  2         4  
  2         2  
62 2         4 $self->_ensure_fd;
63              
64 2 50       4 my $fh = $self->{_fh} or return 0;
65              
66 2         2 my $total = 0;
67 2         2 while (1) {
68 3         4 my $v = eval { $fh->get };
  3         15  
69 3 100       5 if (!defined $v) {
70 2 50 33     13 last if $!{EAGAIN} || $!{EWOULDBLOCK};
71 0 0       0 die $@ if $@;
72 0         0 last;
73             }
74 1         2 $total += $v;
75             }
76              
77 2         33 return $total;
78             }
79              
80 5     5   5 sub _ensure_fd ($self) {
  5         4  
  5         6  
81 5 100       11 return if $self->{_fh};
82              
83             # External dependency for eventfd integration.
84             # Loaded lazily so the core loop can run even when Linux::FD::Event
85             # is not installed.
86 1 50       3 eval { require Linux::FD::Event; 1 }
  1         5  
  1         2  
87             or croak "Linux::FD::Event is required for waker() support: $@";
88              
89             # Non-blocking is critical for epoll integration; we drain to EAGAIN.
90 1         27 my $fh = Linux::FD::Event->new(0, 'non-blocking');
91              
92             # Ensure CLOEXEC (Linux::FD::Event does not currently expose this flag).
93 1         6 my $fd = fileno($fh);
94 1 50       3 if (defined $fd) {
95 1         3 my $cur = fcntl($fh, F_GETFD, 0);
96 1 50       2 if (defined $cur) {
97 1         3 fcntl($fh, F_SETFD, $cur | FD_CLOEXEC);
98             }
99             }
100              
101 1         3 $self->{_fh} = $fh;
102 1         1 return;
103             }
104              
105             1;
106              
107             __END__