Add brokered timerfd support to ulitebox - #1125
Add brokered timerfd support to ulitebox#1125Will Portnoy (willportnoy) wants to merge 10 commits into
Conversation
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
Bring the timerfd work up to date with ulitebox (df4eb7b -> 9efa6cf). The only conflict was in litebox_runner_linux_userland/tests/run.rs, where both sides appended to BROKER_ONLY_C_TESTS: upstream added tcp_broker_server.c and udp_broker.c, this branch added timerfd.c. Resolved as the union of all entries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ea6a6c0d-6bb9-4eed-b8f6-5059a53e326d
The merge from ulitebox brought in a zero-argument init_platform(); the timerfd broker-control test, added on this branch against the old one-argument signature, still passed None. Drop the argument to match every other call site. This is test-only and unblocks the clippy --all-features and nextest CI jobs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ea6a6c0d-6bb9-4eed-b8f6-5059a53e326d
|
🤖 SemverChecks 🤖 Click for details |
|
Will Portnoy (@willportnoy): I ran multiple agents and got the following findings. All 8 reviewers completed within the five-minute budgets. Highest-confidence findings:
Lower-confidence or disputed findings include strict timer-response boolean validation, protocol-version compatibility, epoll behavior after closing one duplicated descriptor, and a scheduler-sensitive 20 ms native test. The two protocol-version reviewers and two epoll reviewers reached opposing conclusions on those items. |
check_readiness held the object-store read lock while calling a timer's readiness(), which acquires the platform snapshot mutex. Event and pipe readiness are cheap atomic loads and stay under the lock, but socket readiness already deferred outside it; make timer match by cloning the resource under the lock and reading readiness after releasing it. Addresses review finding #5. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ea6a6c0d-6bb9-4eed-b8f6-5059a53e326d
#1: the timer reactor published READ on each expiry edge, but after a reader drained the previous expiration the level stayed READ, so the notification ring coalesced the unchanged value and never woke a parked blocking reader again — an indefinite hang on the second and later expirations of an interval timer (or a re-armed one-shot). Republish the edge instead, matching how the socket reactor forces a wakeup when a readable count grows without a level change. A new timer_reactor integration test drives the real reactor through a coalescing-model sink and reproduces the hang; timerfd.c gains consecutive blocking reads. #4: CLOCK_BOOTTIME is a valid Linux timerfd clock but was rejected with EINVAL. Accept it in the shim and the broker clock mapping, with unit and end-to-end coverage. The *_ALARM clocks stay rejected: they require CAP_WAKE_ALARM and can wake a suspended system, outside sandbox authority. Addresses review findings #1 and #4. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ea6a6c0d-6bb9-4eed-b8f6-5059a53e326d
Thanks Weidong Cui (@wdcui) — the multi-agent pass was genuinely useful. Pushed fixes for the three timer-specific findings; the rest look shared with the socket reactor (details below). Fixed
Shared with the socket reactor — proposing to keep as-is or fix holistically
Glad to take any of the shared ones in this PR if you’d like them addressed here. |
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.