Repository navigation
Make ThreadedPeriodicWorker fork friendly #3087
Description
Summary
ThreadedPeriodicWorker is not currently fork friendly. After a process fork, only the thread that called fork survives in the child. The child receives copied Ruby objects and state, but the parent worker threads are not recreated.
A child can therefore inherit state such as:
@thread.alive? == false
@running == true
# or
@woken == trueNo child thread may exist to clear the inherited state or broadcast the idle condition.
Concrete failure mode
TelemetryEventBuffer#flush calls wait_until_idle before anything calls ensure_thread. If the parent was running or had a pending wake when the fork happened, the child can wait indefinitely because its inherited worker thread is dead.
add_item commonly calls ensure_thread and may recreate a child worker when @exited is false, but that is not sufficient:
wait_until_idledoes not create a worker.wakedoes not create a worker.@exitedmay be inherited as true, preventing recreation.- Inherited telemetry/session state can be sent by both the parent and child.
Proposed direction
Add fork-aware lifecycle handling for ThreadedPeriodicWorker and its subclasses:
- Detect a process change lazily using
Process.pid(Ruby supports no public equivalent of Python'sos.register_at_forkfor the supported Ruby versions). - Reset inherited worker state in the child before any wait or enqueue operation:
- worker thread reference
@running@woken@exited- worker mutex and condition variables
- Recreate subclass synchronization state as needed.
- Discard inherited telemetry and session buffers in the child so parent-owned data is not sent twice.
- Allow the child to create a fresh worker lazily through
ensure_thread.
The implementation should remain compatible with the current lifecycle decisions: keep Thread#kill, do not add cooperative shutdown or Thread.handle_interrupt, and do not rely on a global monkey-patch of Process.fork.
Acceptance criteria
- Forking while a worker is running does not make child
wait_until_idlehang. - Forking with a pending wake does not make child waits hang.
- Child telemetry/session buffers do not contain inherited parent data.
- The child can add new data and create a fresh worker.
- Parent worker behavior remains unchanged.
- Fork tests use bounded waits and are skipped where
forkis unavailable.
Related context
The normal wake/run/wait protocol and post-exit behavior have already been hardened separately. This issue is specifically for process-fork state and inherited worker/buffer state.
Activity
linear-code commentedon Sep 17, 2026
RUBY-213
sl0thentr0py commentedon Sep 28, 2026
done in #3097