NAME

Linux::Event::Loop - Linux-native event loop (epoll + timerfd + signalfd + eventfd + pidfd)

SYNOPSIS

use v5.36;
use Linux::Event;

my $loop = Linux::Event->new;   # epoll backend, monotonic clock

# I/O watcher (read/write) with user data stored on the watcher:
my $conn = My::Conn->new(...);

my $w = $loop->watch($fh,
  read  => \&My::Conn::on_read,
  write => \&My::Conn::on_write,
  error => \&My::Conn::on_error,   # optional
  data  => $conn,                   # optional (avoid closure captures)

  edge_triggered => 0,              # optional, default false
  oneshot        => 0,              # optional, default false
);

$w->disable_write;

# Timers (monotonic)
my $id = $loop->after(0.250, sub ($loop) {
  say "250ms later";
});

# Signals (signalfd): strict 4-arg callback
my $sub = $loop->signal('INT', sub ($loop, $sig, $count, $data) {
  say "SIG$sig ($count)";
  $loop->stop;
});

# Wakeups (eventfd): watch like a normal fd
my $waker = $loop->waker;
$loop->watch($waker->fh,
  read => sub ($loop, $fh, $watcher) {
    my $n = $waker->drain;
    ... handle non-fd work ...
  },
);

# Pidfds (pidfd): one-shot exit notification
my $pid = fork() // die "fork: $!";
if ($pid == 0) { exit 42 }

my $psub = $loop->pid($pid, sub ($loop, $pid, $status, $data) {
  require POSIX;
  if (POSIX::WIFEXITED($status)) {
    say "child $pid exited: " . POSIX::WEXITSTATUS($status);
  }
});

$loop->run;

DESCRIPTION

Linux::Event::Loop is a minimal, Linux-native event loop that exposes Linux FD primitives cleanly and predictably. It is built around:

  • epoll(7) for I/O readiness

  • timerfd(2) for timers

  • signalfd(2) for signal delivery

  • eventfd(2) for explicit wakeups

  • pidfd_open(2) (via Linux::FD::Pid) for process lifecycle notifications

Linux::Event is intentionally not a networking framework, protocol layer, retry/backoff engine, process supervisor, or socket abstraction. Ownership is explicit; there is no implicit close, and teardown operations are idempotent.

CONSTRUCTION

new(%opts) -> $loop

my $loop = Linux::Event->new(
  backend => 'epoll',   # default
  clock   => $clock,    # optional; must provide tick/now_ns/etc.
  timer   => $timer,    # optional; must provide after/disarm/read_ticks/fh
);

Options:

  • backend

    Either the string 'epoll' (default) or a backend object that implements watch, unwatch, and run_once.

  • clock

    An object implementing the clock interface used by the scheduler. By default, a monotonic clock is used.

  • timer

    An object implementing the timerfd interface used by the loop. By default, Linux::Event::Timer is used.

RUNNING THE LOOP

run() / run_once($timeout_seconds) / stop()

run() enters the dispatch loop and continues until stop() is called.

run_once($timeout_seconds) runs at most one backend wait/dispatch cycle. The timeout is in seconds; fractions are allowed.

WATCHERS

watch($fh, %spec) -> Linux::Event::Watcher

Create (or replace) a watcher for a filehandle.

Watchers are keyed internally by file descriptor (fd). Calling watch() again for the same fd replaces the existing watcher atomically.

Supported keys in %spec:

  • read - coderef (optional)

  • write - coderef (optional)

  • error - coderef (optional). Called on EPOLLERR.

  • data - user data (optional). Stored on the watcher to avoid closure captures.

  • edge_triggered - boolean (optional, advanced). Defaults to false.

  • oneshot - boolean (optional, advanced). Defaults to false.

    If true, the watcher uses EPOLLONESHOT-style semantics: after an event is delivered, the fd is disabled inside the kernel until it is re-armed. Re-arming is typically done by forcing an epoll MOD (for example by toggling disable_read/enable_read or disable_write/enable_write).

Handlers are invoked as:

read  => sub ($loop, $fh, $watcher) { ... }
write => sub ($loop, $fh, $watcher) { ... }
error => sub ($loop, $fh, $watcher) { ... }

unwatch($fh) -> bool

Remove the watcher for $fh. Returns true if a watcher was removed, false if $fh was not watched (or had no fd). Calling unwatch() multiple times is safe.

Dispatch contract

When the backend reports events for a file descriptor, the loop dispatches callbacks in this order (when applicable):

1. error
2. read
3. write

This order is frozen.

SIGNALS

signal($sig_or_list, $cb, %opt) -> Linux::Event::Signal::Subscription

Register a signal handler using Linux signalfd.

$sig_or_list may be a signal number (e.g. 2), a signal name ('INT' or 'SIGINT'), or an arrayref of those values.

Callback ABI (strict): the callback is always invoked with 4 arguments:

sub ($loop, $sig, $count, $data) { ... }

Only one handler is stored per signal; calling signal() again for the same signal replaces the previous handler.

Options:

  • data - arbitrary user value passed as the final callback argument.

Returns a subscription handle with an idempotent cancel method.

See Linux::Event::Signal.

WAKEUPS

waker() -> Linux::Event::Wakeup

Returns the loop's singleton waker object (an eventfd(2) handle) used to wake the loop from another thread or process.

The waker is created lazily on first use and is never destroyed for the lifetime of the loop.

The waker exposes a readable filehandle ($waker->fh) suitable for $loop->watch(...), and provides $waker->signal and $waker->drain methods. No watcher is installed automatically.

See Linux::Event::Wakeup.

PIDFDS

pid($pid, $cb, %opts) -> Linux::Event::Pid::Subscription

Registers a pidfd watcher for $pid.

Callback ABI (strict): the callback is always invoked with 4 arguments:

sub ($loop, $pid, $status, $data) { ... }

If reap => 1 (default), the loop attempts a non-blocking reap of the PID and passes a wait-status compatible value in $status. If reap => 0, no reap is attempted and $status is undef.

This is a one-shot subscription: after a defined status is observed and the callback is invoked, the subscription is automatically canceled.

Replacement semantics apply per PID: calling pid() again for the same $pid replaces the existing subscription.

See Linux::Event::Pid for full semantics and caveats (child-only reaping).

TIMERS

Timers use a monotonic clock.

after($seconds, $cb) -> $id

Schedule $cb to run after $seconds. Fractions are allowed.

Timer callbacks are invoked as:

sub ($loop) { ... }

at($deadline_seconds, $cb) -> $id

Schedule $cb at an absolute monotonic deadline in seconds (same timebase as the clock used by this loop). Fractions are allowed.

cancel($id) -> bool

Cancel a scheduled timer. Returns true if a timer was removed.

NOTES

Threading and forking helpers

Linux::Event intentionally does not provide $loop->thread or $loop->fork helpers. Concurrency helpers are policy-layer constructs and belong in separate distributions. The core provides primitives (waker, pid) that make such helpers straightforward to implement in user code.

VERSION

This document describes Linux::Event::Loop version 0.006.

AUTHOR

Joshua S. Day

LICENSE

Same terms as Perl itself.

STABILITY

As of version 0.006, the public API and the following contracts are frozen:

  • I/O watcher callback ABI and dispatch order (error, then read, then write)

  • Timer callback ABI (($loop))

  • Signal callback ABI (($loop, $sig, $count, $data)) and replacement semantics per signal

  • Wakeup (waker) single-instance contract

  • Pid subscription callback ABI (($loop, $pid, $status, $data)) and replacement semantics per PID

Future releases will be additive and will not change existing behavior.