diff --git a/History.md b/History.md index 38e1004d97..6a2c1e3ae0 100644 --- a/History.md +++ b/History.md @@ -7,6 +7,7 @@ * Bugfixes * Your bugfix goes here (#Github Number) + * Ensure no segfaults when accessing thread-local variables on Ruby < 2.7.0 (#2567) * Don't close systemd activated socket on pumactl restart (#2563, #2504) ## 5.2.2 / 2021-02-22 diff --git a/lib/puma/cluster.rb b/lib/puma/cluster.rb index d31e8f76e7..cf393b40bc 100644 --- a/lib/puma/cluster.rb +++ b/lib/puma/cluster.rb @@ -332,16 +332,22 @@ def run # This is aligned with the output from Runner, see Runner#output_header log "* Workers: #{@options[:workers]}" - # Threads explicitly marked as fork safe will be ignored. - # Used in Rails, but may be used by anyone. - before = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) } - if preload? + # Threads explicitly marked as fork safe will be ignored. Used in Rails, + # but may be used by anyone. Note that we need to explicit + # Process::Waiter check here because there's a bug in Ruby 2.6 and below + # where calling thread_variable_get on a Process::Waiter will segfault. + # We can drop that clause once those versions of Ruby are no longer + # supported. + fork_safe = ->(t) { !t.is_a?(Process::Waiter) && t.thread_variable_get(:fork_safe) } + + before = Thread.list.reject(&fork_safe) + log "* Restarts: (\u2714) hot (\u2716) phased" log "* Preloading application" load_and_bind - after = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) } + after = Thread.list.reject(&fork_safe) if after.size > before.size threads = (after - before)