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:

$self

The Event object.

$count

The eventfd counter value consumed for this delivery.

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 data

  • lifecycle 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.