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 == true
No 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_idle does not create a worker.
wake does not create a worker.
@exited may 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's os.register_at_fork for 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_idle hang.
- 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
fork is 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.
Summary
ThreadedPeriodicWorkeris not currently fork friendly. After a process fork, only the thread that calledforksurvives 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:
No child thread may exist to clear the inherited state or broadcast the idle condition.
Concrete failure mode
TelemetryEventBuffer#flushcallswait_until_idlebefore anything callsensure_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_itemcommonly callsensure_threadand may recreate a child worker when@exitedis 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.Proposed direction
Add fork-aware lifecycle handling for
ThreadedPeriodicWorkerand its subclasses:Process.pid(Ruby supports no public equivalent of Python'sos.register_at_forkfor the supported Ruby versions).@running@woken@exitedensure_thread.The implementation should remain compatible with the current lifecycle decisions: keep
Thread#kill, do not add cooperative shutdown orThread.handle_interrupt, and do not rely on a global monkey-patch ofProcess.fork.Acceptance criteria
wait_until_idlehang.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.