Skip to content

Are all threads fork safe? #4825

@joshuay03

Description

@joshuay03

Tracer Version(s)

2.18.0

Ruby Version(s)

ruby 3.4.5 (2025-07-16 revision 20cda200d3) +YJIT +PRISM [x86_64-linux]

Relevent Library and Version(s)

No response

Bug Report

We're in the process of enabling Puma's preload_app! config at @buildkite. While auditing threads spawned during boot, Puma flagged a timeout thread that appears to be spawned by a thread in this gem. The callstack only goes so far, but I suspect it's the runtime metrics thread.

callstack
["/usr/local/bundle/gems/timeout-0.4.3/lib/timeout.rb:131:in 'block in Timeout.ensure_timeout_thread_created'",
"/usr/local/bundle/gems/timeout-0.4.3/lib/timeout.rb:129:in 'Thread::Mutex#synchronize'",
"/usr/local/bundle/gems/timeout-0.4.3/lib/timeout.rb:129:in 'Timeout.ensure_timeout_thread_created'",
"/usr/local/bundle/gems/timeout-0.4.3/lib/timeout.rb:180:in 'Timeout.timeout'",
"/usr/local/lib/ruby/3.4.0/net/http.rb:1657:in 'Net::HTTP#connect'",
"/usr/local/lib/ruby/3.4.0/net/http.rb:1636:in 'Net::HTTP#do_start'",
"/usr/local/lib/ruby/3.4.0/net/http.rb:1625:in 'Net::HTTP#start'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/transport/http/adapters/net.rb:39:in 'Datadog::Core::Transport::HTTP::Adapters::Net#open'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/transport/http/adapters/net.rb:77:in 'Datadog::Core::Transport::HTTP::Adapters::Net#post'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/transport/http/adapters/net.rb:44:in 'Datadog::Core::Transport::HTTP::Adapters::Net#call'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/transport/http/api/instance.rb:47:in 'Datadog::Core::Transport::HTTP::API::Instance#call'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/telemetry/transport/http/telemetry.rb:29:in 'block in Datadog::Core::Telemetry::Transport::HTTP::Telemetry::API::Instance#send_telemetry'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/transport/http/api/endpoint.rb:24:in 'Datadog::Core::Transport::HTTP::API::Endpoint#call'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/telemetry/transport/http/telemetry.rb:64:in 'Datadog::Core::Telemetry::Transport::HTTP::Telemetry::API::Endpoint#call'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/telemetry/transport/http/telemetry.rb:40:in 'Datadog::Core::Telemetry::Transport::HTTP::Telemetry::API::Spec#send_telemetry'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/telemetry/transport/http/telemetry.rb:28:in 'Datadog::Core::Telemetry::Transport::HTTP::Telemetry::API::Instance#send_telemetry'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/telemetry/transport/http/telemetry.rb:18:in 'block in Datadog::Core::Telemetry::Transport::HTTP::Telemetry::Client#send_telemetry_payload'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/telemetry/transport/http/client.rb:30:in 'Datadog::Core::Telemetry::Transport::HTTP::Client#send_request'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/telemetry/transport/http/telemetry.rb:17:in 'Datadog::Core::Telemetry::Transport::HTTP::Telemetry::Client#send_telemetry_payload'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/telemetry/transport/telemetry.rb:43:in 'Datadog::Core::Telemetry::Transport::Telemetry::Transport#send_telemetry'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/telemetry/emitter.rb:33:in 'Datadog::Core::Telemetry::Emitter#request'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/telemetry/worker.rb:208:in 'Datadog::Core::Telemetry::Worker#send_event'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/telemetry/worker.rb:183:in 'block in Datadog::Core::Telemetry::Worker#started!'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/utils/only_once_successful.rb:44:in 'block in Datadog::Core::Utils::OnlyOnceSuccessful#run'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/utils/only_once_successful.rb:41:in 'Thread::Mutex#synchronize'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/utils/only_once_successful.rb:41:in 'Datadog::Core::Utils::OnlyOnceSuccessful#run'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/telemetry/worker.rb:182:in 'Datadog::Core::Telemetry::Worker#started!'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/telemetry/worker.rb:139:in 'Datadog::Core::Telemetry::Worker#perform'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/workers/queue.rb:16:in 'Datadog::Core::Workers::Queue::PrependedMethods#perform'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/workers/interval_loop.rb:27:in 'block in Datadog::Core::Workers::IntervalLoop::PrependedMethods#perform'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/workers/interval_loop.rb:116:in 'block in Datadog::Core::Workers::IntervalLoop#perform_loop'",
"<internal:kernel>:168:in 'Kernel#loop'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/workers/interval_loop.rb:112:in 'Datadog::Core::Workers::IntervalLoop#perform_loop'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/workers/interval_loop.rb:24:in 'Datadog::Core::Workers::IntervalLoop::PrependedMethods#perform'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/workers/async.rb:27:in 'block in Datadog::Core::Workers::Async::Thread::PrependedMethods#perform'",
"/usr/local/bundle/gems/datadog-2.18.0/lib/datadog/core/workers/async.rb:157:in 'block in Datadog::Core::Workers::Async::Thread#start_worker'"]

When I investigated whether the threads in this gem are fork-safe, I discovered that some are and some aren't, based on their configured fork policy. Additionally, this recent issue suggests that the runtime metrics thread isn't fork-safe, but at the same time, it appears that all of them have been marked as fork-safe for Puma's reporting.

What I'm trying to determine is:

  1. Are all threads actually fork-safe?
  2. If not, should the non-fork-safe threads be unmarked as such so users are directed here to learn how to make them fork-safe?
  3. How can users ensure all threads are fork-safe? Is calling Datadog.configure { } after fork/before boot in the worker still the recommended approach?

Thanks!

Reproduction Code

No response

Configuration Block

No response

Error Logs

No response

Operating System

No response

How does Datadog help you?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    communityWas opened by a community memberquestionGeneral inquiry that may or may not involve changes

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions