NAME
Linux::Event::Kernel::Event - Wake an event loop from another execution context
SYNOPSIS
use v5.36;
use Linux::Event::Loop;
use Linux::Event::Kernel::Event;
my $loop = Linux::Event::Loop->new;
my $event = Linux::Event::Kernel::Event->new(
loop => $loop,
on_event => sub ($self, $count) {
say "Received $count notification(s)";
$loop->stop;
},
);
$event->signal;
$loop->run;
DESCRIPTION
Linux::Event::Kernel::Event provides an eventfd-backed notification that can wake a Linux::Event::Loop.
Its main purpose is simple:
something outside the Loop has work ready
|
v
$event->signal
|
v
Loop wakes up
|
v
on_event runs normally on the Loop
The producer might be:
another thread
a native extension
an external C library
a forked child process
ordinary application code that wants to notify the Loop
on_event always runs as ordinary Loop dispatch.
signal does not execute the callback inline.
EVENT IS A NOTIFICATION, NOT A MESSAGE QUEUE
This distinction is important.
An Event tells the Loop:
work is available
It does not carry arbitrary Perl data between threads or processes.
Linux eventfd contains a numeric counter.
It cannot safely transport:
Perl objects
coderefs
hashes
strings
arbitrary messages
If another execution context has actual application data to deliver, place that data in an appropriate queue or IPC mechanism first and then signal the Event.
Conceptually:
producer:
put result in queue
$event->signal
Loop:
on_event fires
drain queue
For example:
my $event = Linux::Event::Kernel::Event->new(
loop => $loop,
data => $results,
on_event => sub ($self, $count) {
my $queue = $self->data;
while (my $result = next_result($queue)) {
process_result($result);
}
},
);
The queue is the source of truth for the actual work.
The Event is merely the wakeup notification.
CREATING AN EVENT
The normal constructor form is:
my $event = Linux::Event::Kernel::Event->new(
loop => $loop,
on_event => sub ($self, $count) {
...
},
);
on_event is required unless the class provides an on_event method.
THE CALLBACK
on_event
The callback receives:
on_event => sub ($self, $count) {
...
}
where:
For example:
my $event = Linux::Event::Kernel::Event->new(
loop => $loop,
on_event => sub ($self, $count) {
say "$count wakeup unit(s) arrived";
},
);
SIGNALING
signal
Add one to the Event counter:
$event->signal;
signal returns the Event object.
It does not call on_event immediately.
Instead, the eventfd becomes readable and the callback runs when the owning Loop dispatches that readiness.
signal($increment)
An explicit positive increment may also be supplied:
$event->signal(5);
This adds five to the eventfd counter.
The increment must be a positive integer within the supported eventfd range.
MULTIPLE SIGNALS MAY COALESCE
Several calls to signal can become one callback.
For example:
$event->signal;
$event->signal;
$event->signal;
may later produce:
on_event => sub ($self, $count) {
# $count may be 3
}
This is normal eventfd behavior.
That is another reason not to treat $count as though it represented one specific application message.
If three queue items were published and three signals were sent, the callback might run once with a count of three.
The application should normally drain the associated queue until no work remains.
SIGNALING BEFORE ATTACHMENT
An Event owns its eventfd as soon as it is constructed.
Therefore a detached Event can be signaled before it is added to a Loop:
my $event = Linux::Event::Kernel::Event->new(
on_event => sub ($self, $count) {
...
},
);
$event->signal;
$loop->add($event);
The pending eventfd counter remains available and can make the Event ready once it is attached.
CONSTRUCTOR CALLBACKS OR SUBCLASS METHODS
A constructor callback is often simplest:
my $event = Linux::Event::Kernel::Event->new(
on_event => sub ($self, $count) {
...
},
);
A reusable Event type can instead use a subclass:
package ResultsReady;
use parent 'Linux::Event::Kernel::Event';
sub on_event ($self, $count) {
my $queue = $self->data;
while (my $result = next_result($queue)) {
process_result($result);
}
}
package main;
my $event = ResultsReady->new(
loop => $loop,
data => $results,
);
A constructor on_event callback overrides the subclass method for that particular Event.
APPLICATION DATA
data
Application-owned state may be associated with an Event:
my $event = Linux::Event::Kernel::Event->new(
loop => $loop,
data => $results,
on_event => sub ($self, $count) {
drain_results($self->data);
},
);
Retrieve it with:
my $data = $event->data;
While the Event is nonterminal, it may be changed:
$event->data($new_data);
Cancellation releases the owner-side application data.
ATTACHING TO A LOOP
An Event can be attached during construction:
my $event = Linux::Event::Kernel::Event->new(
loop => $loop,
on_event => sub ($self, $count) {
...
},
);
or created detached:
my $event = Linux::Event::Kernel::Event->new(
on_event => sub ($self, $count) {
...
},
);
and added later:
$loop->add($event);
CANCELLING AN EVENT
cancel
$event->cancel;
Cancellation:
removes the Event from its Loop
closes its owner-side eventfd
releases retained application data
makes the Event terminal
Calling cancel again is harmless.
A cancelled Event cannot be signaled or attached again.
LIFECYCLE
The normal Event states are:
unattached
active
cancelled
state
my $state = $event->state;
Return the current lifecycle state.
A managed-fork child may also observe the special terminal state:
not_inherited
when the parent's Event was intentionally dropped during child Loop reconstruction.
is_active
if ($event->is_active) {
...
}
Return true while attached and active.
is_terminal
if ($event->is_terminal) {
...
}
Return true once the Event can no longer be managed.
LOOP
loop
my $loop = $event->loop;
Return the owning Loop while the Event is active.
After cancellation, no owning Loop is returned.
USING EVENT WITH THREADS
Event is useful for waking the Loop from another thread.
The important ownership rule is:
worker signals
owner Loop dispatches
The worker does not become another owner of the Loop or callback.
On an ithread-enabled Perl, a cloned Event handle may be used for signaling.
It does not gain access to the owner interpreter's:
Loop
callback state
application
datalifecycle management
The owning interpreter remains responsible for the Event object itself.
WHY THE EVENT DOES NOT CARRY PERL VALUES
Arbitrary Perl values belong to a Perl interpreter.
Allowing something like:
$event->signal($perl_object);
to cross thread boundaries would require Linux::Event to define ownership, copying, serialization, cancellation, destruction, and exception behavior for arbitrary Perl state.
Kernel::Event deliberately avoids inventing such a model.
Use the Event as the wakeup primitive and choose a payload mechanism appropriate to the producer.
USING EVENT ACROSS FORK
There are two different cases to understand.
Ordinary CORE::fork
A child created with ordinary CORE::fork inherits the eventfd.
The child may use the inherited Event handle to signal the parent's eventfd until exec or until that inherited handle is closed.
For example, conceptually:
my $pid = CORE::fork();
if ($pid == 0) {
publish_result_through_ipc();
$event->signal;
exit;
}
The child does not gain ownership of the parent's Loop, callback, or application data.
Cross-process payloads still require real IPC or shared storage.
The Event descriptor is close-on-exec.
Linux::Event managed fork
Linux::Event::Kernel::Event currently supports only the default parent-only behavior with Linux::Event::Loop managed fork.
It does not support:
share
clone
move
Therefore an Event should not be placed in those disposition lists.
For example:
my $pid = $loop->fork(
clone => [$timer],
);
The Event remains active in the parent.
The inherited child Event is deliberately dropped as part of rebuilding the child Loop and becomes terminal there.
This prevents an inherited Event from accidentally being treated as a child-owned Loop resource.
CALLBACK EXCEPTIONS
If on_event throws an exception, that exception propagates through ordinary Loop dispatch.
Linux::Event does not silently convert the exception into Event cancellation.
This follows the normal Linux::Event callback model.
COUNTER SATURATION
Linux eventfd counters have a finite range.
If producers attempt to increment an already saturated counter, the nonblocking eventfd write fails.
Linux::Event reports that failure from signal rather than silently discarding the notification.
Applications should normally use Event as a wakeup and drain their actual work queue promptly rather than trying to use the eventfd counter as long-term storage.
IMPLEMENTATION MODEL
Each Event owns one nonblocking, close-on-exec Linux eventfd.
When the counter becomes nonzero, epoll makes the Event readable.
Linux::Event reads the counter and invokes on_event on the owning Loop.
One readiness dispatch performs one counter read.
If producers signal again after that read, the eventfd remains or becomes readable for a later Loop turn.
This prevents a continuously active producer from forcing one Event dispatch to drain forever.
PERFORMANCE MODEL
Event is intentionally small.
The application callback is resolved at construction: a constructor callback is retained for that Event, or a subclass method is cached for its class.
Normal delivery therefore consists primarily of:
eventfd readiness
counter read
cached callback
without repeatedly performing method lookup or callback-style selection.
SEE ALSO
Linux::Event, Linux::Event::Loop, Linux::Event::Kernel::Signal, Linux::Event::Kernel::Process, docs/EVENT-DESIGN.md.