From f6721017aca6a1c807725ddf922a38a9b4abc87e Mon Sep 17 00:00:00 2001 From: Joshua Young Date: Thu, 24 Jul 2025 16:28:37 +1000 Subject: [PATCH] Mark sender thread as fork safe for Puma --- lib/datadog/statsd/sender.rb | 2 ++ spec/statsd/sender_spec.rb | 11 +++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/datadog/statsd/sender.rb b/lib/datadog/statsd/sender.rb index 9f50cc8f..85394d5e 100644 --- a/lib/datadog/statsd/sender.rb +++ b/lib/datadog/statsd/sender.rb @@ -104,6 +104,8 @@ def start # start background thread @sender_thread = @thread_class.new(&method(:send_loop)) @sender_thread.name = "Statsd Sender" unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3') + # advise multi-threaded app servers to ignore this thread for the purposes of fork safety warnings + @sender_thread.thread_variable_set(:fork_safe, true) rescue ThreadError => e @logger.debug { "Statsd: Failed to start sender thread: #{e.message}" } if @logger @mx.synchronize { @done = true } diff --git a/spec/statsd/sender_spec.rb b/spec/statsd/sender_spec.rb index 95c1f015..b0bf85d1 100644 --- a/spec/statsd/sender_spec.rb +++ b/spec/statsd/sender_spec.rb @@ -48,6 +48,13 @@ |thds| thds.any? { |t| t.name == "Statsd Sender" } } end + + it 'marks the sender thread as fork safe' do + subject.start + expect(Thread.list).to satisfy { + |thds| thds.any? { |t| t.name == "Statsd Sender" && t.thread_variable_get(:fork_safe) } + } + end end context 'when the sender is started' do @@ -204,9 +211,9 @@ let(:thread_class) do if Thread.instance_methods.include?(:name=) - fake_thread = instance_double(Thread, { "alive?" => true, "name=" => true, "join" => true }) + fake_thread = instance_double(Thread, { "alive?" => true, "name=" => true, "join" => true, "thread_variable_set" => true }) else - fake_thread = instance_double(Thread, { "alive?" => true, "join" => true }) + fake_thread = instance_double(Thread, { "alive?" => true, "join" => true, "thread_variable_set" => true }) end class_double(Thread, new: fake_thread) end