Kernel Timer design
Linux::Event::Kernel::Timer is the scheduled-work leaf for
Linux::Event::Loop. Applications provide on_timer as a constructor closure
or reusable subclass method, keep optional instance context in data, and
attach through either loop => $loop or $loop->add($timer).
Public API
A Timer can be constructed directly with lexical callback state:
my $timer = Linux::Event::Kernel::Timer->new(
loop => $loop,
every => 15,
on_timer => sub ($timer) {
$connection->write("ping\n");
},
);
A concrete Timer subclass remains useful for reusable named behavior:
package Heartbeat;
use parent 'Linux::Event::Kernel::Timer';
sub on_timer ($timer) {
my $connection = $timer->data;
$connection->write("ping\n");
}
Construction accepts one schedule:
after => $secondsfor a relative one-shot deadline;at => $monotonic_secondsfor an absolute one-shot deadline;every => $secondsfor fixed-rate recurrence;afteroratpluseveryfor a distinct first deadline.
after and at are mutually exclusive. Public values are fractional seconds;
internal deadlines are integer nanoseconds.
Linux::Event::Kernel::Timer->now returns the same monotonic clock used by
absolute deadlines.
reschedule() accepts the same scheduling grammar and returns the same object.
It is valid while active and from inside on_timer. Cancellation and final
one-shot expiration are terminal and cannot be reattached or rescheduled.
The Loop deliberately has no Timer factory. Timer remains a normal attachable semantic resource:
my $timer = $loop->add(Heartbeat->new(
every => 15,
data => $connection,
));
The method CV is cached once per subclass. A constructor callback overrides it for one object and is retained once in that Timer's native descriptor. Timer delivery performs no repeated method lookup or callback-style selection.
Native scheduler
One Loop owns one lazily created timerfd and one indexed native minimum heap.
The timerfd is armed to the heap root with TFD_TIMER_ABSTIME; many public
Timer objects still require only one kernel timer descriptor per Loop.
Each heap entry stores its current index so cancellation and rescheduling do not require a linear search.
The ordering key is (deadline, sequence). Sequence provides FIFO delivery for
identical deadlines. Callback dispatch is bounded per expiration batch so a
large timer cohort cannot permanently exclude ordinary descriptor readiness.
An immediate or already-past deadline is never invoked inline from the
constructor. A Timer scheduled for immediate delivery from inside on_timer
is likewise deferred to a later Loop turn, avoiding recursive callback chains.
Recurrence and missed ticks
Recurring Timers are fixed-rate. The next deadline advances from the prior scheduled deadline instead of callback completion, avoiding cumulative drift from ordinary callback latency.
If the Loop is late by multiple intervals, the schedule advances beyond the
current monotonic clock and delivers one semantic callback. expirations
reports how many periodic ticks that callback represents.
The recurring Timer is reinserted before callback entry. If an application callback throws, the exception can propagate without silently deleting the recurring schedule. The application can cancel or reschedule after catching the exception.
Ownership and cleanup
An attached active Kernel::Timer is retained by its Loop. Dropping an
application reference does not implicitly cancel scheduled work.
The Timer's ownership relation to the Loop is arranged so Loop destruction can terminate remaining timer resources instead of leaving a cycle.
cancel() is idempotent and terminal. It removes the heap entry and releases
application data according to the Timer lifecycle contract. A one-shot Timer
performs terminal cleanup after its final callback.
When cancellation occurs inside on_timer, callback-local state remains valid
until the callback returns.
Timer callbacks can directly modify other semantic resources retained in
data, such as closing an IO::Sock::Stream, pausing an
IO::Sock::Listener, or stopping the Loop.
Ordered-byte deadline integration
Established idle/read/write/operation deadlines use private timer objects on the same Loop scheduler. They are not exposed as application Timers.
An ordered-byte resource owns at most one private scheduler entry representing its earliest current deadline. This preserves one shared timerfd design while keeping deadline policy with the resource it protects.
See ORDERED-BYTE-DEADLINES.md for that policy.
Performance contract
The performance regression suite gates Timer attach/cancel churn and immediate expiration delivery alongside reactor and ordered-byte workloads.
The dedicated timer microbenchmark measures lifecycle, indexed-heap rescheduling, and equal-deadline expiration at multiple active Timer counts. Loop statistics expose timerfd creation/rearm, scheduling, cancellation, callback delivery, coalescing, and maximum heap size.
Native implementation boundary
Linux::Event::Kernel::Timer is both the supported public class and the native
timerfd scheduler host. Private descriptor state remains behind that class and
does not add a Perl dispatch layer to timer delivery.