Skip to content

Add brokered timerfd support to ulitebox - #1125

Draft
Will Portnoy (willportnoy) wants to merge 6 commits into
uliteboxfrom
wportnoy/ulitebox-timerfd
Draft

Add brokered timerfd support to ulitebox#1125
Will Portnoy (willportnoy) wants to merge 6 commits into
uliteboxfrom
wportnoy/ulitebox-timerfd

Conversation

@willportnoy

Copy link
Copy Markdown
Member

Adds Linux timerfd support to the ulitebox broker. Timer expiry is delegated to the host through the broker — a dedicated epoll reactor owns real host timerfds and publishes readiness through the notification ring, mirroring the brokered TCP and eventfd model. The guest gets timerfd_create/timerfd_settime/timerfd_gettime plus read, close, and epoll through a new shim subsystem.

Introduce a broker-owned timerfd that delegates timer expiry to the host
platform, mirroring the brokered TCP/eventfd model: the dedicated epoll
reactor in litebox_broker_platform_linux_userland owns a real Linux
timerfd and publishes READ readiness via the notification ring; the
broker core exposes a provider-driven TimerfdObject; broker_local gains a
create/set/get/read client; and the wire protocol carries the new
Timerfd request/response family.

Details:
- protocol: TimerfdSpec + Create/Set/Get/Read messages and wire codec;
  BrokerOperation::Timerfd / BrokerResult::Timerfd; bump
  MAX_ENCODED_ACTIVE_MESSAGE_SIZE 38->54 (timerfd set is the new largest
  active message, still within the 112-byte control-ring slot).
- core: TimerfdProvider/PlatformTimerfd traits, TimerfdObject bounded by
  max_references, ObjectEntry::Timerfd, UnsupportedTimerfdProvider default
  plus a with_timerfd_provider builder to avoid a BrokerCore::new ripple.
- platform: LinuxTimerfdProvider + reactor owning host timerfd fds in an
  edge-triggered epoll set; readiness snapshot updated on expiry/drain.
- userland: wire LinuxTimerfdProvider into the broker binary.
- host/local: dispatch + client plumbing; exhaustive Timerfd match arms.

The userland_broker integration test drives the real broker binary end to
end: it arms a 5ms one-shot host timer, observes READ readiness, and
drains exactly one expiration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ea6a6c0d-6bb9-4eed-b8f6-5059a53e326d
Add litebox::event::timer::Timer, a local-core timer backed by a
broker-owned host timerfd, mirroring EventCounter: new/set_time/get_time/
read plus IOPollable and Drop, with a TimerError enum (non_exhaustive,
matching the EventCounterError sibling convention) and the corresponding
BrokerObjectError conversions. Extend the BrokerControl trait with
create/set/get/read_timerfd and implement them on BrokerLocalControl.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ea6a6c0d-6bb9-4eed-b8f6-5059a53e326d
Add the guest syscall surface so a sandboxed program can use timerfds:
timerfd_create/timerfd_settime/timerfd_gettime, plus read/close/epoll
routing, wrapping litebox::event::timer::Timer in a TimerfdSubsystem that
mirrors the eventfd subsystem.

- common_linux: Itimerspec ABI struct, TfdFlags/TfdTimerFlags, the three
  SyscallRequest variants and their decode arms, and From<TimerError> for
  Errno (TryOpError<TimerError> converts via the existing generic impl).
- shim: TimerfdSubsystem/TimerFile; sys_timerfd_* with itimerspec
  marshalling (EINVAL on negative guest seconds, EOVERFLOW on host->guest
  overflow, NULL old_value tolerated); an extra run_on_raw_fd closure and
  matching arms in do_read (8-byte expiration count), write (EINVAL),
  do_close, epoll (EpollDescriptor/DescriptorRef), fcntl and ioctl.

clockid is restricted to CLOCK_REALTIME/CLOCK_MONOTONIC. New owned-enum
variants trip E0004 at every dispatch site by design; each is resolved
with an explicit Timerfd arm, no wildcards.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ea6a6c0d-6bb9-4eed-b8f6-5059a53e326d
Drop the fd suffix from the broker-facing timer object so it matches the
Event/Socket/Pipe convention (the fd suffix belongs to the shim
subsystem, not the broker object). No behavior change.

- protocol: timerfd.rs -> timer.rs, wire/timerfd.rs -> wire/timer.rs;
  TimerfdRequest/Response -> TimerRequest/Response, TimerfdSpec ->
  TimerSpec, BrokerOperation::Timerfd/BrokerResult::Timerfd ->
  ::Timer, encode/decode_timerfd_* -> _timer_*.
- core: timerfd.rs -> timer.rs; TimerfdProvider/PlatformTimerfd/
  TimerfdObject/TimerfdResource/UnsupportedTimerfdProvider ->
  Timer*; ObjectEntry::Timerfd -> ::Timer; with_timerfd_provider ->
  with_timer_provider.
- local/host/platform/userland: create/set/get/read_timerfd ->
  *_timer; LinuxTimerfdProvider/LinuxTimerfd -> LinuxTimer*;
  handle_timerfd_request -> handle_timer_request.
- litebox: BrokerControl timer methods and the guest Timer follow suit.

The shim keeps TimerfdSubsystem / sys_timerfd_* / TfdFlags and rustix
keeps TimerfdClockId/TimerfdFlags, matching how eventfd names its shim
subsystem while the broker object is Event.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ea6a6c0d-6bb9-4eed-b8f6-5059a53e326d
Two timerfd read/settime outcomes previously collapsed to EIO now reach
the guest with the errno Linux uses.

- EINVAL: the shim validates the itimerspec before issuing the broker
  request, rejecting a tv_nsec outside [0, 1e9) (negative seconds were
  already rejected). This is where Linux validates, so a bad
  timerfd_settime now returns EINVAL instead of a host round-trip that
  surfaced as EIO. Covered by unit tests.
- ECANCELED: a CANCEL_ON_SET timer whose backing clock is set
  discontinuously reports ECANCELED once and disarms. The reactor now
  detects the host ECANCELED read and carries a cancelled outcome through
  the timerfd read path (TimerRead + ReadTimerResponse.cancelled ->
  BrokerControl -> guest Timer), which the guest maps to ECANCELED via a
  new TimerError::Cancelled. This is kept entirely within timerfd-owned
  code so the shared broker ErrorCode/BrokerError wire enums are
  untouched; the guest-visible behavior is read() -> ECANCELED.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ea6a6c0d-6bb9-4eed-b8f6-5059a53e326d
Add tests/timerfd.c, a self-validating guest that exercises the timerfd
syscall surface end to end: one-shot and interval timers, gettime,
nonblocking and blocking reads, poll and epoll wakeups, dup sharing,
CLOEXEC/NONBLOCK flags, an unknown clock, and an out-of-range itimerspec.
Its assertions encode native Linux semantics.

test_runner_broker_timerfd_with_rewriter runs the same binary twice: once
on the native baseline (the gold standard, proving the assertions match
real Linux) and once under Litebox with a broker-owned host timer, and
asserts the broker released one object per created timer. spawn_test_broker
now installs LinuxTimerProvider so broker-backed guests can create timers.

This covers the blocking-read and epoll wakeup paths the broker-only
integration test does not, and the EINVAL fidelity fix (a tv_nsec >= 1e9
timerfd_settime returns EINVAL under Litebox exactly as it does natively).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ea6a6c0d-6bb9-4eed-b8f6-5059a53e326d
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

🤖 SemverChecks 🤖 ⚠️ Potential breaking API changes detected ⚠️

Click for details
--- failure enum_variant_added: enum variant added on exhaustive enum ---

Description:
A publicly-visible enum without #[non_exhaustive] has a new variant.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#enum-variant-new
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.50.0/src/lints/enum_variant_added.ron

Failed in:
  variant BrokerOperation:Timerfd in /home/runner/work/litebox/litebox/litebox_broker_protocol/src/message.rs:48
  variant BrokerResult:Timerfd in /home/runner/work/litebox/litebox/litebox_broker_protocol/src/message.rs:156

--- failure enum_no_repr_variant_discriminant_changed: enum variant had its discriminant change value ---

Description:
The enum's variant had its discriminant value change. This breaks downstream code that used its value via a numeric cast like `as isize`.
        ref: https://doc.rust-lang.org/reference/items/enumerations.html#assigning-discriminant-values
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.50.0/src/lints/enum_no_repr_variant_discriminant_changed.ron

Failed in:
  variant SyscallRequest::Pipe2 68 -> 71 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2331
  variant SyscallRequest::Clone 69 -> 72 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2335
  variant SyscallRequest::Clone3 70 -> 73 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2338
  variant SyscallRequest::SetThreadArea 71 -> 74 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2343
  variant SyscallRequest::ClockGettime 72 -> 75 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2346
  variant SyscallRequest::ClockGetres 73 -> 76 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2350
  variant SyscallRequest::ClockNanosleep 74 -> 77 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2354
  variant SyscallRequest::Gettimeofday 75 -> 78 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2360
  variant SyscallRequest::Time 76 -> 79 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2364
  variant SyscallRequest::Getrlimit 77 -> 80 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2367
  variant SyscallRequest::Setrlimit 78 -> 81 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2371
  variant SyscallRequest::Prlimit 79 -> 82 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2375
  variant SyscallRequest::SetTidAddress 80 -> 83 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2386
  variant SyscallRequest::Gettid 81 -> 84 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2389
  variant SyscallRequest::SetRobustList 82 -> 85 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2390
  variant SyscallRequest::GetRobustList 83 -> 86 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2393
  variant SyscallRequest::GetRandom 84 -> 87 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2398
  variant SyscallRequest::Getpid 85 -> 88 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2403
  variant SyscallRequest::Getppid 86 -> 89 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2404
  variant SyscallRequest::Getuid 87 -> 90 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2405
  variant SyscallRequest::Geteuid 88 -> 91 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2406
  variant SyscallRequest::Getgid 89 -> 92 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2407
  variant SyscallRequest::Getegid 90 -> 93 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2408
  variant SyscallRequest::Sysinfo 91 -> 94 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2409
  variant SyscallRequest::CapGet 92 -> 95 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2412
  variant SyscallRequest::GetDirent64 93 -> 96 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2416
  variant SyscallRequest::SchedGetAffinity 94 -> 97 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2421
  variant SyscallRequest::SchedYield 95 -> 98 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2426
  variant SyscallRequest::Futex 96 -> 99 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2427
  variant SyscallRequest::Execve 97 -> 100 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2430
  variant SyscallRequest::Umask 98 -> 101 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2435
  variant SyscallRequest::Prctl 99 -> 102 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2438
  variant SyscallRequest::Alarm 100 -> 103 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2441
  variant SyscallRequest::Pause 101 -> 104 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2444
  variant SyscallRequest::SetITimer 102 -> 105 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2445
  variant SyscallRequest::GetITimer 103 -> 106 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2450
  variant SyscallRequest::Statx 104 -> 107 in /home/runner/work/litebox/litebox/litebox_common_linux/src/lib.rs:2454

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant