NAME
Linux::Event::Kernel::Timer - Schedule one-time or recurring work
SYNOPSIS
use v5.36;
use Linux::Event::Loop;
use Linux::Event::Kernel::Timer;
my $loop = Linux::Event::Loop->new;
my $timer = Linux::Event::Kernel::Timer->new(
loop => $loop,
after => 1,
on_timer => sub ($self) {
say "One second has passed";
$loop->stop;
},
);
$loop->run;
DESCRIPTION
Linux::Event::Kernel::Timer schedules work to happen later on a Linux::Event::Loop.
A Timer can:
fire once after a delay
fire once at a particular monotonic-clock time
repeat at a fixed interval
start repeating after a different first delay
be rescheduled while active
be cancelled
For example, a recurring heartbeat can be written as:
my $heartbeat = Linux::Event::Kernel::Timer->new(
loop => $loop,
every => 15,
on_timer => sub ($self) {
$connection->write("ping\n");
},
);
All public time values are expressed in seconds and may be fractional.
ONE-SHOT TIMERS
after
Use after for a timer relative to now:
my $timer = Linux::Event::Kernel::Timer->new(
loop => $loop,
after => 2.5,
on_timer => sub ($self) {
say "2.5 seconds later";
},
);
after is measured from the time the Timer becomes active on its Loop.
A value of zero is allowed:
after => 0
but the callback is not invoked immediately from the constructor.
It runs on a later Loop turn.
This avoids surprising reentrant callbacks during object construction.
RECURRING TIMERS
every
Use every for a repeating Timer:
my $timer = Linux::Event::Kernel::Timer->new(
loop => $loop,
every => 5,
on_timer => sub ($self) {
say "Five-second heartbeat";
},
);
every must be greater than zero.
With every alone, the first callback occurs after one interval.
For example:
every => 5
means approximately:
5 seconds
10 seconds
15 seconds
...
until the Timer is cancelled or rescheduled.
A DIFFERENT FIRST DELAY
after may be combined with every:
my $timer = Linux::Event::Kernel::Timer->new(
loop => $loop,
after => 1,
every => 10,
on_timer => sub ($self) {
...
},
);
The first callback is scheduled after one second.
Later callbacks occur on the ten-second recurring schedule.
This is useful when the first action should happen quickly but normal repetition should happen less often.
ABSOLUTE MONOTONIC DEADLINES
at
Use at when you already have an absolute monotonic-clock time:
my $when = Linux::Event::Kernel::Timer->now + 10;
my $timer = Linux::Event::Kernel::Timer->new(
loop => $loop,
at => $when,
on_timer => sub ($self) {
say "deadline reached";
},
);
at uses the same monotonic clock returned by:
Linux::Event::Kernel::Timer->now
It is not wall-clock time.
Do not pass values from time() or calendar timestamps to at.
Monotonic time is intentionally unaffected by wall-clock corrections such as NTP adjustments or a user changing the system clock.
at with every
An absolute first deadline may also begin a recurring schedule:
my $start = Linux::Event::Kernel::Timer->now + 2;
my $timer = Linux::Event::Kernel::Timer->new(
loop => $loop,
at => $start,
every => 30,
...
);
The first callback occurs at $start and subsequent callbacks continue at the configured interval.
SCHEDULE RULES
The valid schedule forms are:
after => $seconds
at => $monotonic_seconds
every => $seconds
after => $first_delay,
every => $interval
at => $first_deadline,
every => $interval
after and at cannot be used together.
after and at are non-negative.
every must be positive.
THE TIMER CALLBACK
on_timer
The constructor form is:
on_timer => sub ($self) {
...
}
The callback receives the Timer object itself.
For example:
my $timer = Linux::Event::Kernel::Timer->new(
loop => $loop,
every => 1,
on_timer => sub ($self) {
say "tick";
},
);
The callback runs as normal Loop work.
It may interact with other Linux::Event resources:
on_timer => sub ($self) {
$connection->write("heartbeat\n");
$listener->pause;
}
or stop the Loop:
on_timer => sub ($self) {
$self->loop->stop;
}
CONSTRUCTOR CALLBACKS OR SUBCLASS METHODS
Timer behavior may be supplied directly with on_timer:
my $timer = Linux::Event::Kernel::Timer->new(
every => 5,
on_timer => sub ($self) {
...
},
);
or implemented by a subclass:
package Heartbeat;
use parent 'Linux::Event::Kernel::Timer';
sub on_timer ($self) {
$self->data->write("ping\n");
}
package main;
my $timer = Heartbeat->new(
loop => $loop,
every => 5,
data => $connection,
);
A constructor on_timer callback overrides the subclass method for that particular Timer.
Constructor callbacks are usually simplest when the Timer needs lexical application state.
Subclassing is useful when the timer behavior itself is reusable.
APPLICATION DATA
data
A Timer may carry arbitrary application data:
my $timer = Linux::Event::Kernel::Timer->new(
loop => $loop,
every => 5,
data => $connection,
on_timer => sub ($self) {
$self->data->write("ping\n");
},
);
Retrieve it with:
my $value = $timer->data;
While the Timer is nonterminal, it may also be replaced:
$timer->data($new_value);
Terminal Timers no longer accept a new data value.
A final one-shot expiration and explicit cancellation release the Timer's stored application data according to the Timer lifecycle.
RESCHEDULING
reschedule
An active Timer can be given a new schedule:
$timer->reschedule(
after => 10,
);
or:
$timer->reschedule(
every => 2,
);
or:
$timer->reschedule(
after => 1,
every => 30,
);
reschedule accepts the same scheduling forms as new.
It returns the same Timer object.
Rescheduling from the callback
A Timer may reschedule itself:
my $timer = Linux::Event::Kernel::Timer->new(
loop => $loop,
after => 1,
on_timer => sub ($self) {
do_some_work();
$self->reschedule(
after => 5,
);
},
);
This can be useful for work whose next delay depends on the result of the current callback.
A one-shot Timer is still active while its callback is running, so it may be rescheduled from inside that callback.
If it is not rescheduled, its completed one-shot expiration becomes terminal after the callback.
CANCELLING A TIMER
cancel
$timer->cancel;
Cancel future delivery.
Cancellation is terminal.
A cancelled Timer cannot later be rescheduled or added to another Loop.
Calling cancel again is harmless.
A Timer may also cancel itself from inside its callback:
on_timer => sub ($self) {
...
$self->cancel;
}
TIMER LIFECYCLE
A newly constructed detached Timer begins unattached.
After it is added to a Loop, it becomes active.
A one-shot Timer becomes expired after its final callback unless it was rescheduled.
An explicitly cancelled Timer becomes cancelled.
state
my $state = $timer->state;
The public lifecycle states are:
unattached
active
expired
cancelled
is_active
if ($timer->is_active) {
...
}
Return true while the Timer is scheduled or its callback is currently firing.
is_terminal
if ($timer->is_terminal) {
...
}
Return true after final expiration or cancellation.
Terminal Timers cannot be revived.
ATTACHING TO A LOOP
The usual form attaches during construction:
my $timer = Linux::Event::Kernel::Timer->new(
loop => $loop,
after => 1,
on_timer => sub ($self) {
...
},
);
A Timer may also be constructed detached:
my $timer = Linux::Event::Kernel::Timer->new(
after => 1,
on_timer => sub ($self) {
...
},
);
and attached later:
$loop->add($timer);
add returns the same Timer object, so this is also valid:
my $timer = $loop->add(
Linux::Event::Kernel::Timer->new(
after => 1,
on_timer => sub ($self) {
...
},
)
);
Once attached, a Timer belongs to that Loop for its lifetime.
LOOP OWNERSHIP
An active Timer is retained by its Loop.
This means this is safe:
Linux::Event::Kernel::Timer->new(
loop => $loop,
after => 1,
on_timer => sub ($self) {
say "still fires";
},
);
The application does not need to keep another reference merely to prevent the active Timer from disappearing.
The Loop releases its ownership when the Timer becomes terminal.
FIXED-RATE RECURRENCE
Recurring timers use a fixed-rate schedule.
The next deadline is calculated from the previous scheduled deadline, not from the time the callback finishes.
For example, with:
every => 1
a callback that takes 0.1 seconds does not intentionally turn the schedule into:
callback finished + 1 second
for every repetition.
This avoids accumulating ordinary callback execution time as timer drift.
MISSED TICKS
A Loop can occasionally be too busy to run a recurring Timer exactly when one or more intervals expire.
Linux::Event does not call the callback repeatedly in a burst merely to replay every missed interval.
Instead, missed recurring intervals are coalesced into one callback.
expirations
Inside the callback:
on_timer => sub ($self) {
my $ticks = $self->expirations;
say "$ticks timer interval(s) elapsed";
}
expirations reports how many periodic ticks the current delivery represents.
Normally this is:
1
If the Loop was delayed long enough to cross several recurring deadlines, it may be greater than one.
The recurring schedule is advanced beyond the current monotonic time and continues from its fixed-rate timeline.
CURRENT DEADLINE AND INTERVAL
deadline
my $deadline = $timer->deadline;
For an active Timer, return its current absolute monotonic deadline in seconds.
For a detached Timer created with an absolute at value, that absolute deadline is also available before attachment.
A relative detached Timer does not yet have an absolute Loop deadline.
interval
my $seconds = $timer->interval;
Return the recurring interval in seconds.
A one-shot Timer has an interval of zero.
MONOTONIC TIME
now
my $now = Linux::Event::Kernel::Timer->now;
Return the current monotonic clock value in seconds.
This is the clock used by at and deadline.
For example:
my $deadline =
Linux::Event::Kernel::Timer->now + 0.250;
my $timer = Linux::Event::Kernel::Timer->new(
at => $deadline,
...
);
IMMEDIATE AND PAST DEADLINES
A Timer scheduled with:
after => 0
or with an at value that has already passed does not invoke on_timer inline from new or reschedule.
It becomes normal pending Loop work and fires on a later dispatch turn.
The same rule applies when an immediate Timer is scheduled from another Timer callback.
This prevents recursive timer-callback chains.
LOOP-AWARE FORKING
Linux::Event::Loop supports explicit Timer dispositions during managed fork.
A Timer may be:
clone-
Remain active in the parent and create an independent active Timer in the child.
The child Timer preserves the same absolute monotonic deadline.
move-
Move the active Timer to the child.
After a successful move, the parent Timer is terminal.
Timer share is not supported.
A Timer omitted from the disposition lists remains parent-only and its inherited child copy is dropped.
For example:
my $pid = $loop->fork(
clone => [$heartbeat],
move => [$child_only_timer],
);
See Linux::Event::Loop for the full managed-fork contract and its quiescence requirements.
IMPLEMENTATION MODEL
Applications do not need to create or manage Linux timerfd descriptors.
All active Timers on one Loop share one private timerfd-backed scheduler.
Linux::Event keeps scheduled Timers in an indexed native minimum heap and arms the shared timerfd for the next deadline.
Therefore:
1 Timer
does not mean:
1 timerfd
and:
10,000 Timers
do not require 10,000 timerfds.
This shared scheduler is an implementation detail, but it explains why Timer is a logical scheduled resource rather than a thin wrapper around one kernel timer descriptor.
Timers with identical deadlines are delivered in stable scheduling order, and timer dispatch is bounded so a large timer cohort does not permanently exclude other ready Loop resources.
PERFORMANCE MODEL
Timer callback policy is resolved when the Timer is constructed.
A subclass method is cached per Timer class, while a constructor on_timer callback is retained for that object.
Recurring delivery therefore does not need to repeatedly perform method lookup or decide between callback styles.
The shared native scheduler also provides indexed cancellation and rescheduling rather than searching all active Timers linearly.
These details normally require no application action.
SEE ALSO
Linux::Event, Linux::Event::Loop, Linux::Event::Kernel::Signal, Linux::Event::Kernel::Event, docs/TIMER-DESIGN.md, docs/ORDERED-BYTE-DEADLINES.md.