Skip to content

Make ThreadedPeriodicWorker fork friendly #3087

Description

@sl0thentr0py

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.

Activity

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions