Skip to content

[Code Health] Mailbox dispatch waits should block on a wakeup primitive, not poll #1498

Description

@ChaoWao

Category

Performance / robustness of a hot path

Component

Host runtime (src/common/hierarchical/, python/simpler/worker.py)

Description

Both ends of the parent-child mailbox wait by polling, and neither can be made both low-latency and cheap that way.

side mechanism cost
parent (LocalMailboxEndpoint::run) sleep_for(50us) per TASK_DONE poll up to a 50 us quantum added to every dispatch
child (_run_mailbox_loop) unbounded busy-spin a full core held for the child's whole lifetime

The parent's sleep is on the critical path of every dispatch and dominates the round trip. Measured on aarch64 Linux, 3 sub workers, no-op callables:

sequential run():  median 150 us   p90 152 us   max 176 us
burst (200 subs):  106 us per task

Note sleep_for(50us) does not cost 50 us — time.sleep(50e-6) measures ~102 us on this box, so the quantum is roughly double what the constant says.

A blocking wakeup primitive fixes both sides at once: zero CPU while waiting, single-digit-microsecond wake, and no interval to tune. This is now what codestyle.md rule 5 requires of any dispatch-path wait, so worker_manager.cpp is a recorded deviation rather than a style preference.

Location

Priority

medium

Additional context

Candidate primitive. A pipe per mailbox direction looks better than a PTHREAD_PROCESS_SHARED condvar in the mailbox shm, despite slightly worse wake latency:

pipe pshared condvar
wake latency ~5-20 us (unverified on this box) ~3-10 us
macOS support POSIX, identical both ends supported, but needs ctypes into libc from Python
GIL os.read releases it pthread_cond_wait needs care
peer death poll() with a timeout a child dying while holding the mutex hangs the parent; no robust mutexes on macOS

The repo has no blocking primitive today — futex, sem_wait, PTHREAD_PROCESS_SHARED, eventfd, pthread_cond are all zero hits across src/, python/ and docs/.

Constraint to preserve. The parent currently samples child liveness every 200 poll iterations via waitpid(WNOHANG), so a child that dies without publishing TASK_DONE cannot hang the loop forever. poll() with a timeout keeps that behaviour; a bare blocking read() would lose it.

Measure before implementing. The 5-20 us figure is a general expectation, not measured here. The gating experiment is one number: pipe write-to-wake latency on this host versus the current 50 us quantum. If the gain is not real on this hardware, the design should change before any code does.

What this is not. Not a fix for #1493 (children surviving parent death). With a blocking wait an orphan would cost 0% CPU, but it would still be a leaked process holding fds and shm, so the two are complementary.

An earlier attempt to pace the child's poll with a sleep instead (#1491) was closed: measurement showed it traded 17% of burst dispatch throughput for the CPU saving, which is the trade the rule now forbids.

Metadata

Metadata

Assignees

No one assigned

    Labels

    code healthTechnical debt, robustness, code quality

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions