Skip to content

Conversation

@rosa
Copy link
Member

@rosa rosa commented Dec 20, 2025

This tries to address an issue we ran into when running ActionPushNative::NotificationJob jobs in several threads (with Solid Queue's multiple thread option). I described it in #25 (comment)

Even though we were running these in 3 threads per process (worker), we'd get frequent HTTPX::PoolTimeoutError like this:

HTTPX::Pool#checkout_connection': Timed out after 5 seconds while waiting for a connection (HTTPX::PoolTimeoutError)
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/pool.rb:50:in 'Thread::Mutex#synchronize'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/pool.rb:50:in 'HTTPX::Pool#checkout_connection'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:186:in 'HTTPX::Session#find_connection'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:257:in 'block in HTTPX::Session#send_request'

even though the max number of connections is 5 by default, so more connections than threads. We increased the number of connections to 10 and the errors decreased, but still happened from time to time. They disappeared when we incremented the number from 10 to 20.

However, things weren't working fine still. The jobs were running mixed with other jobs, all processed by the same workers. We noticed that certain jobs would have a big delay in all queries, from 100ms to 800ms extra, in every query. We also saw that the servers where these jobs were running got frequent CPU spikes that didn't happen before. All of this was really strange and only fixed itself when we changed these workers to run with a single thread. In that way, nothing would go wrong. We could return to the default max connections of 5, and there wouldn't be any problems.

I left it that way, running in a single thread, as it was working fine, until I finally had time to investigate properly. I started looking into it last week, first by trying to reproduce it in a single VM, with 3 threads, some ActionPushNative::NotificationJob jobs mixed with other not-user-facing jobs, and HTTPX_DEBUG set to 1. This was a very small number of ActionPushNative::NotificationJob, and the error didn't happen. Then, I just set all jobs to run with 3 threads, mixed with those not-user-facing-jobs. The HTTPX::PoolTimeoutError happened right away, so we could get a bunch of logs to investigate. Here's an extract from those logs:

Logs
615: <- HEADER: apns-id: xxxx
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177832) emit :response_started callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#133112) emit :response callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177832) emit :response callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177832) emit :complete callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177856) emit :headers callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#134080)
541: -> HEADER: apns-push-type: alert
541: -> HEADER: apns-id: xxxx
541: -> HEADER: apns-priority: 10
541: -> HEADER: apns-topic: com.hey.app.ios
541: -> HEADER: authorization: Bearer xxxx
541: -> HEADER: user-agent: httpx.rb/1.6.2
541: -> HEADER: accept: */*
541: -> HEADER: accept-encoding: gzip, deflate
541: -> HEADER: content-type: application/json; charset=utf-8
541: -> HEADER: content-length: 1421
541: -> HEADER: traceparent: 00-3e4f75bb2a8f11e4360362d174d49170-a48c9218d75674aa-00
541: -> HEADER: :scheme: https
541: -> HEADER: :method: POST
541: -> HEADER: :path: /3/device/xxxx
541: -> HEADER: :authority: api.push.apple.com
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177856) emit :body callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177856) emit :body_chunk callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#134080) 541: -> DATA: 1421 bytes...
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177856) emit :trailers callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177856) emit :done callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::SSL#134024) WRITE: 1579 bytes...
(pid:93, tid:175520, fid:175528, self:HTTPX::SSL#134024) READ: 49 bytes...
(pid:93, tid:175520, fid:175528, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#134080) 541: <- HEADER: :status: 200
541: <- HEADER: apns-id: xxxx
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177856) emit :response_started callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#134080) emit :response callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177856) emit :response callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177856) emit :complete callbacks
(pid:93, tid:143944, fid:143960, self:HTTPX::SSL#148744) WRITE: 17 bytes...
(pid:93, tid:143944, fid:143960, self:HTTPX::SSL#148744) READ: 49 bytes...
(pid:93, tid:143944, fid:143960, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#175456) 13: <- HEADER: :status: 200
13: <- HEADER: apns-id: xxxx
(pid:93, tid:143944, fid:143960, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177304) emit :response_started callbacks
(pid:93, tid:143944, fid:143960, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#175456) emit :response callbacks
(pid:93, tid:143944, fid:143960, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177304) emit :response callbacks
(pid:93, tid:143944, fid:143960, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177880) emit :headers callbacks
(pid:93, tid:143944, fid:143960, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#175456)
15: -> HEADER: apns-push-type: background
15: -> HEADER: apns-id: xxxx
15: -> HEADER: apns-priority: 5
15: -> HEADER: apns-topic: com.hey.calendar.ios
15: -> HEADER: authorization: Bearer xxxx
15: -> HEADER: user-agent: httpx.rb/1.6.2
15: -> HEADER: accept: */*
15: -> HEADER: accept-encoding: gzip, deflate
15: -> HEADER: content-type: application/json; charset=utf-8
15: -> HEADER: content-length: 123
15: -> HEADER: traceparent: 00-54e60b686acf1e97b0ebc820ab6ee114-70e363bd13a8f83d-00
15: -> HEADER: :scheme: https
15: -> HEADER: :method: POST
15: -> HEADER: :path: /3/device/xxx
15: -> HEADER: :authority: api.push.apple.com
(pid:93, tid:143944, fid:143960, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177880) emit :body callbacks
(pid:93, tid:143944, fid:143960, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177880) emit :body_chunk callbacks
(pid:93, tid:143944, fid:143960, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#175456) 15: -> DATA: 123 bytes...
(pid:93, tid:143944, fid:143960, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177880) emit :trailers callbacks
(pid:93, tid:143944, fid:143960, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177880) emit :done callbacks
(pid:93, tid:143944, fid:143960, self:HTTPX::SSL#148744) WRITE: 279 bytes...
(pid:93, tid:143944, fid:143960, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177304) emit :complete callbacks
172.18.0.1 - - [17/Dec/2025:15:50:07 +0000] "GET /metrics HTTP/1.1" 200 - 0.4648
(pid:93, tid:175520, fid:175528, self:HTTPX::ErrorResponse#177904) /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/pool.rb:65:in 'block in HTTPX::Pool#checkout_connection': Timed out after 5 seconds while waiting for a connection (HTTPX::PoolTimeoutError)
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/pool.rb:50:in 'Thread::Mutex#synchronize'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/pool.rb:50:in 'HTTPX::Pool#checkout_connection'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:186:in 'HTTPX::Session#find_connection'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:257:in 'block in HTTPX::Session#send_request'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:256:in 'Kernel#catch'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:256:in 'HTTPX::Session#send_request'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/plugins/fiber_concurrency.rb:24:in 'HTTPX::Plugins::FiberConcurrency::InstanceMethods#send_request'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:326:in 'block in HTTPX::Session#_send_requests'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:325:in 'Array#each'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:325:in 'HTTPX::Session#_send_requests'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:310:in 'HTTPX::Session#send_requests'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:102:in 'HTTPX::Session#request'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/chainable.rb:10:in 'HTTPX::Chainable#post'
        from /usr/local/bundle/ruby/3.4.0/gems/action_push_native-0.2.1/lib/action_push_native/service/apns/httpx_session.rb:21:in 'ActionPushNative::Service::Apns::HttpxSession#post'
        from /usr/local/bundle/ruby/3.4.0/gems/action_push_native-0.2.1/lib/action_push_native/service/apns.rb:20:in 'ActionPushNative::Service::Apns#push'
        from /rails/app/models/device.rb:18:in 'Device#push'
        from /usr/local/bundle/ruby/3.4.0/gems/action_push_native-0.2.1/lib/action_push_native/notification.rb:62:in 'block in ActionPushNative::Notification#deliver_to'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/callbacks.rb:110:in 'ActiveSupport::Callbacks#run_callbacks'
        from /usr/local/bundle/ruby/3.4.0/gems/action_push_native-0.2.1/lib/action_push_native/notification.rb:62:in 'ActionPushNative::Notification#deliver_to'
        from /usr/local/bundle/ruby/3.4.0/gems/action_push_native-0.2.1/app/jobs/action_push_native/notification_job.rb:55:in 'ActionPushNative::NotificationJob#perform'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activejob/lib/active_job/execution.rb:68:in 'block in ActiveJob::Execution#_perform_job'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/callbacks.rb:110:in 'ActiveSupport::Callbacks#run_callbacks'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activejob/lib/active_job/execution.rb:67:in 'ActiveJob::Execution#_perform_job'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activejob/lib/active_job/instrumentation.rb:44:in 'ActiveJob::Instrumentation#_perform_job'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activejob/lib/active_job/execution.rb:51:in 'ActiveJob::Execution#perform_now'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activejob/lib/active_job/instrumentation.rb:26:in 'block in ActiveJob::Instrumentation#perform_now'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activerecord/lib/active_record/railties/job_runtime.rb:12:in 'block in ActiveRecord::Railties::JobRuntime#instrument'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activejob/lib/active_job/instrumentation.rb:34:in 'block in ActiveJob::Instrumentation#instrument'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/notifications.rb:210:in 'block in ActiveSupport::Notifications.instrument'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/notifications/instrumenter.rb:58:in 'ActiveSupport::Notifications::Instrumenter#instrument'
        from /usr/local/bundle/ruby/3.4.0/gems/sentry-rails-5.21.0/lib/sentry/rails/tracing.rb:56:in 'Sentry::Rails::Tracing::SentryNotificationExtension#instrument'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/notifications.rb:210:in 'ActiveSupport::Notifications.instrument'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activejob/lib/active_job/instrumentation.rb:33:in 'ActiveJob::Instrumentation#instrument'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activerecord/lib/active_record/railties/job_runtime.rb:10:in 'ActiveRecord::Railties::JobRuntime#instrument'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activejob/lib/active_job/instrumentation.rb:26:in 'ActiveJob::Instrumentation#perform_now'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activejob/lib/active_job/logging.rb:32:in 'block in ActiveJob::Logging#perform_now'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/tagged_logging.rb:143:in 'block in ActiveSupport::TaggedLogging#tagged'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/tagged_logging.rb:38:in 'ActiveSupport::TaggedLogging::Formatter#tagged'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/tagged_logging.rb:143:in 'ActiveSupport::TaggedLogging#tagged'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/broadcast_logger.rb:228:in 'ActiveSupport::BroadcastLogger#method_missing'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/delegation.rb:207:in 'Kernel#public_send'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/delegation.rb:207:in 'RailsStructuredLogging::Proxy#method_missing'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activejob/lib/active_job/logging.rb:39:in 'ActiveJob::Logging#tag_logger'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activejob/lib/active_job/logging.rb:32:in 'ActiveJob::Logging#perform_now'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activejob/lib/active_job/execution_state.rb:7:in 'block (2 levels) in ActiveJob::ExecutionState#perform_now'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/core_ext/time/zones.rb:65:in 'Time.use_zone'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activejob/lib/active_job/execution_state.rb:7:in 'block in ActiveJob::ExecutionState#perform_now'
        from /usr/local/bundle/ruby/3.4.0/gems/i18n-1.14.7/lib/i18n.rb:353:in 'I18n::Base#with_locale'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activejob/lib/active_job/execution_state.rb:6:in 'ActiveJob::ExecutionState#perform_now'
        from /usr/local/bundle/ruby/3.4.0/gems/sentry-rails-5.21.0/lib/sentry/rails/active_job.rb:11:in 'block in Sentry::Rails::ActiveJobExtensions#perform_now'
        from /usr/local/bundle/ruby/3.4.0/gems/sentry-rails-5.21.0/lib/sentry/rails/active_job.rb:43:in 'block in Sentry::Rails::ActiveJobExtensions::SentryReporter.record'
        from /usr/local/bundle/ruby/3.4.0/gems/sentry-ruby-5.21.0/lib/sentry/hub.rb:59:in 'Sentry::Hub#with_scope'
        from /usr/local/bundle/ruby/3.4.0/gems/sentry-ruby-5.21.0/lib/sentry-ruby.rb:392:in 'Sentry.with_scope'
        from /usr/local/bundle/ruby/3.4.0/gems/sentry-rails-5.21.0/lib/sentry/rails/active_job.rb:26:in 'Sentry::Rails::ActiveJobExtensions::SentryReporter.record'
        from /usr/local/bundle/ruby/3.4.0/gems/sentry-rails-5.21.0/lib/sentry/rails/active_job.rb:10:in 'Sentry::Rails::ActiveJobExtensions#perform_now'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activejob/lib/active_job/execution.rb:29:in 'block in ActiveJob::Execution::ClassMethods#execute'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/callbacks.rb:121:in 'block in ActiveSupport::Callbacks#run_callbacks'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activejob/lib/active_job/railtie.rb:86:in 'block (4 levels) in <class:Railtie>'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/reloader.rb:77:in 'block in ActiveSupport::Reloader.wrap'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/execution_wrapper.rb:87:in 'ActiveSupport::ExecutionWrapper.wrap'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/reloader.rb:74:in 'ActiveSupport::Reloader.wrap'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activejob/lib/active_job/railtie.rb:85:in 'block (3 levels) in <class:Railtie>'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/callbacks.rb:130:in 'BasicObject#instance_exec'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/callbacks.rb:130:in 'block in ActiveSupport::Callbacks#run_callbacks'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/callbacks.rb:141:in 'ActiveSupport::Callbacks#run_callbacks'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activejob/lib/active_job/execution.rb:27:in 'ActiveJob::Execution::ClassMethods#execute'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/solid_queue-be1a5bc700a3/app/models/solid_queue/claimed_execution.rb:102:in 'SolidQueue::ClaimedExecution#execute'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/solid_queue-be1a5bc700a3/app/models/solid_queue/claimed_execution.rb:64:in 'SolidQueue::ClaimedExecution#perform'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/solid_queue-be1a5bc700a3/lib/solid_queue/pool.rb:23:in 'block (2 levels) in SolidQueue::Pool#post'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/rails-1a02651ac37f/activesupport/lib/active_support/execution_wrapper.rb:91:in 'ActiveSupport::ExecutionWrapper.wrap'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/solid_queue-be1a5bc700a3/lib/solid_queue/app_executor.rb:7:in 'SolidQueue::AppExecutor#wrap_in_app_executor'
        from /usr/local/bundle/ruby/3.4.0/bundler/gems/solid_queue-be1a5bc700a3/lib/solid_queue/pool.rb:22:in 'block in SolidQueue::Pool#post'
        from /usr/local/bundle/ruby/3.4.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/promises.rb:1593:in 'Concurrent::Promises::AbstractPromise#evaluate_to'
        from /usr/local/bundle/ruby/3.4.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/promises.rb:1776:in 'block in Concurrent::Promises::ChainPromise#on_resolvable'
        from /usr/local/bundle/ruby/3.4.0/gems/opentelemetry-instrumentation-concurrent_ruby-0.21.4/lib/opentelemetry/instrumentation/concurrent_ruby/patches/thread_pool_executor.rb:20:in 'block (2 levels) in OpenTelemetry::Instrumentation::ConcurrentRuby::Patches::ThreadPoolExecutor#post'
        from /usr/local/bundle/ruby/3.4.0/gems/opentelemetry-api-1.4.0/lib/opentelemetry/context.rb:71:in 'OpenTelemetry::Context.with_current'
        from /usr/local/bundle/ruby/3.4.0/gems/opentelemetry-instrumentation-concurrent_ruby-0.21.4/lib/opentelemetry/instrumentation/concurrent_ruby/patches/thread_pool_executor.rb:19:in 'block in OpenTelemetry::Instrumentation::ConcurrentRuby::Patches::ThreadPoolExecutor#post'
        from /usr/local/bundle/ruby/3.4.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:359:in 'Concurrent::RubyThreadPoolExecutor::Worker#run_task'
        from /usr/local/bundle/ruby/3.4.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:350:in 'block (3 levels) in Concurrent::RubyThreadPoolExecutor::Worker#create_worker'
        from <internal:kernel>:168:in 'Kernel#loop'
        from /usr/local/bundle/ruby/3.4.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:341:in 'block (2 levels) in Concurrent::RubyThreadPoolExecutor::Worker#create_worker'
        from /usr/local/bundle/ruby/3.4.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:340:in 'Kernel#catch'
        from /usr/local/bundle/ruby/3.4.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:340:in 'block in Concurrent::RubyThreadPoolExecutor::Worker#create_worker'

(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177912) emit :response_started callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::RetrieSs#177912) emit :response callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177912) emit :complete callbacks
172.18.0.1 - - [17/Dec/2025:15:50:12 +0000] "GET /metrics HTTP/1.1" 200 - 0.4665
172.18.0.1 - - [17/Dec/2025:15:50:17 +0000] "GET /metrics HTTP/1.1" 200 - 0.5126
(pid:93, tid:175520, fid:175528, self:HTTPX::SSL#133224) WRITE: 17 bytes...
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177952) emit :headers callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#133264)
657: -> HEADER: apns-push-type: alert
657: -> HEADER: apns-id: xxxx
657: -> HEADER: apns-priority: 10
657: -> HEADER: apns-topic: com.hey.app.ios
657: -> HEADER: authorization: Bearer xxxx
657: -> HEADER: user-agent: httpx.rb/1.6.2
657: -> HEADER: accept: */*
657: -> HEADER: accept-encoding: gzip, deflate
657: -> HEADER: content-type: application/json; charset=utf-8
657: -> HEADER: content-length: 1211
657: -> HEADER: traceparent: 00-6a7dcffa58b8000347917986016011cc-42641db3461a2ab2-00
657: -> HEADER: :scheme: https
657: -> HEADER: :method: POST
657: -> HEADER: :path: /3/device/xxxx
657: -> HEADER: :authority: api.push.apple.com
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177952) emit :body callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177952) emit :body_chunk callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#133264) 657: -> DATA: 1211 bytes...
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177952) emit :trailers callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177952) emit :done callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::SSL#133224) WRITE: 1370 bytes...
(pid:93, tid:175520, fid:175528, self:HTTPX::SSL#133224) READ: 17 bytes...
(pid:93, tid:175520, fid:175528, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#133264) emit :pong callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::SSL#133224) READ: 49 bytes...
(pid:93, tid:175520, fid:175528, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#133264) 657: <- HEADER: :status: 200
657: <- HEADER: apns-id: xxxx
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177952) emit :response_started callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#133264) emit :response callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177952) emit :response callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177952) emit :complete callbacks
172.18.0.1 - - [17/Dec/2025:15:50:22 +0000] "GET /metrics HTTP/1.1" 200 - 0.4983
172.18.0.1 - - [17/Dec/2025:15:50:27 +0000] "GET /metrics HTTP/1.1" 200 - 0.4868
(pid:93, tid:175520, fid:175528, self:HTTPX::SSL#133088) WRITE: 17 bytes...
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177976) emit :headers callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#133112)
617: -> HEADER: apns-push-type: alert
617: -> HEADER: apns-id: 78923813-c21c-462b-adae-9287f897dc43
617: -> HEADER: apns-priority: 10
617: -> HEADER: apns-topic: com.hey.app.ios
617: -> HEADER: authorization: Bearer xxxx
617: -> HEADER: user-agent: httpx.rb/1.6.2
617: -> HEADER: accept: */*
617: -> HEADER: accept-encoding: gzip, deflate
617: -> HEADER: content-type: application/json; charset=utf-8
617: -> HEADER: content-length: 1476
617: -> HEADER: traceparent: 00-3aaad1d5703467934795d39a9c4d1da0-1a69c3f41476b1fe-00
617: -> HEADER: :scheme: https
617: -> HEADER: :method: POST
617: -> HEADER: :path: /3/device/xxxx
617: -> HEADER: :authority: api.push.apple.com
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177976) emit :body callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177976) emit :body_chunk callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#133112) 617: -> DATA: 1476 bytes...
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177976) emit :trailers callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177976) emit :done callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::SSL#133088) WRITE: 1700 bytes...
(pid:93, tid:175520, fid:175528, self:HTTPX::SSL#133088) READ: 17 bytes...
(pid:93, tid:175520, fid:175528, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#133112) emit :pong callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::SSL#133088) READ: 49 bytes...
(pid:93, tid:175520, fid:175528, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#133112) 617: <- HEADER: :status: 200
617: <- HEADER: apns-id: xxxx
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177976) emit :response_started callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#133112) emit :response callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177976) emit :response callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#177976) emit :complete callbacks
172.18.0.1 - - [17/Dec/2025:15:50:32 +0000] "GET /metrics HTTP/1.1" 200 - 0.4637
(pid:93, tid:143944, fid:143960, self:HTTPX::SSL#134024) WRITE: 17 bytes...
(pid:93, tid:143944, fid:143960, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#178000) emit :headers callbacks
172.18.0.1 - - [17/Dec/2025:15:50:37 +0000] "GET /metrics HTTP/1.1" 200 - 0.4638
(pid:93, tid:143944, fid:143960, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#134080)
543: -> HEADER: apns-push-type: alert
543: -> HEADER: apns-id: xxxx
543: -> HEADER: apns-priority: 10
543: -> HEADER: apns-topic: com.hey.app.ios
543: -> HEADER: authorization: Bearer xxxx
543: -> HEADER: user-agent: httpx.rb/1.6.2
543: -> HEADER: accept: */*
543: -> HEADER: accept-encoding: gzip, deflate
543: -> HEADER: content-type: application/json; charset=utf-8
543: -> HEADER: content-length: 1477
543: -> HEADER: traceparent: 00-e158c002b64a5a8e93b21db13a822c98-0545b23bcb80e97f-00
543: -> HEADER: :scheme: https
543: -> HEADER: :method: POST
543: -> HEADER: :path: /3/device/xxxx
543: -> HEADER: :authority: api.push.apple.com
(pid:93, tid:143944, fid:143960, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#178000) emit :body callbacks
(pid:93, tid:143944, fid:143960, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#178000) emit :body_chunk callbacks
(pid:93, tid:143944, fid:143960, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#134080) 543: -> DATA: 1477 bytes...
(pid:93, tid:143944, fid:143960, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#178000) emit :trailers callbacks
(pid:93, tid:143944, fid:143960, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#178000) emit :done callbacks
(pid:93, tid:143944, fid:143960, self:HTTPX::SSL#134024) WRITE: 1636 bytes...
(pid:93, tid:143944, fid:143960, self:HTTPX::SSL#134024) READ: 17 bytes...
(pid:93, tid:143944, fid:143960, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#134080) emit :pong callbacks
(pid:93, tid:143944, fid:143960, self:HTTPX::SSL#134024) READ: 49 bytes...
(pid:93, tid:143944, fid:143960, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#134080) 543: <- HEADER: :status: 200
543: <- HEADER: apns-id: xxxx
(pid:93, tid:143944, fid:143960, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#178000) emit :response_started callbacks
(pid:93, tid:143944, fid:143960, self:HTTPX::Connection::HTTP2(plugin)/HTTPX::Plugins::FiberConcurrency#134080) emit :response callbacks
(pid:93, tid:143944, fid:143960, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#178000) emit :response callbacks
(pid:93, tid:143944, fid:143960, self:HTTPX::Request(plugin)/HTTPX::Plugins::Retries#178000) emit :complete callbacks
(pid:93, tid:175520, fid:175528, self:HTTPX::ErrorResponse#178008) /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/pool.rb:65:in 'block in HTTPX::Pool#checkout_connection': Timed out after 5 seconds while waiting for a connection (HTTPX::PoolTimeoutError)
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/pool.rb:50:in 'Thread::Mutex#synchronize'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/pool.rb:50:in 'HTTPX::Pool#checkout_connection'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:186:in 'HTTPX::Session#find_connection'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:257:in 'block in HTTPX::Session#send_request'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:256:in 'Kernel#catch'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:256:in 'HTTPX::Session#send_request'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/plugins/fiber_concurrency.rb:24:in 'HTTPX::Plugins::FiberConcurrency::InstanceMethods#send_request'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:326:in 'block in HTTPX::Session#_send_requests'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:325:in 'Array#each'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:325:in 'HTTPX::Session#_send_requests'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:310:in 'HTTPX::Session#send_requests'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/session.rb:102:in 'HTTPX::Session#request'
        from /usr/local/bundle/ruby/3.4.0/gems/httpx-1.6.2/lib/httpx/chainable.rb:10:in 'HTTPX::Chainable#post'
        from /usr/local/bundle/ruby/3.4.0/gems/action_push_native-0.2.1/lib/action_push_native/service/apns/httpx_session.rb:21:in 'Actio

Some things to note from the logs and from the usage of HTTPX::Session:

Now, looking at the logs in more detail:

  • Thread A: tid:175520, fid:175528
  • Thread B: tid:143944, fid:143960
  15:50:02-ish - Thread A completes on Connection #134080:
  (pid:93, tid:175520, ..., self:HTTPX::Connection::HTTP2...#134080)
  541: -> HEADER: apns-push-type: alert
  ...
  541: <- HEADER: :status: 200
  (pid:93, tid:175520, ...) emit :complete callbacks
  Thread A's request finishes. Connection #134080 should be available.

  Meanwhile - Thread B working on Connection #175456:
  (pid:93, tid:143944, ..., self:HTTPX::Connection::HTTP2...#175456)
  13: <- HEADER: :status: 200
  ...
  15: -> HEADER: apns-push-type: background  (new request sent)
  Thread B receives a response, sends another request (stream 15) on its connection.

  15:50:07 - Thread A hits PoolTimeoutError:
  (pid:93, tid:175520, ..., self:HTTPX::ErrorResponse#177904)
  Timed out after 5 seconds while waiting for a connection (HTTPX::PoolTimeoutError)

  After timeout - Thread A continues with other connections:
  (pid:93, tid:175520, ..., self:HTTPX::Connection::HTTP2...#133264)
  657: -> HEADER: ...

  (pid:93, tid:175520, ..., self:HTTPX::Connection::HTTP2...#133112)
  617: -> HEADER: ...

  15:50:37 - Thread B uses Connection #134080:
  (pid:93, tid:143944, ..., self:HTTPX::Connection::HTTP2...#134080)
  543: -> HEADER: apns-push-type: alert

So, the connection #134080 was used by Thread A first (stream 541), then by Thread B later (stream 543). However, Thread A got the pool timeout around 15:50:07, then Thread B successfully used #134080 at ~15:50:37. Why couldn't thread A get it from the pool? 😕

I'm not completely sure about the answer, but I suspect it might be around this:

  • Race conditions around @contexts not being thread-safe.
  • An issue with each thread's individual selector but a shared connection pool: When Thread A creates a connection and registers it in Thread A's Selector:
  def select_connection(connection)
    selector = get_current_selector  # Thread A's Selector
    selector.register(connection)
  end

That connection is now in Thread A's Selector only. When Thread B reuses the same connection, Thread B adds its request to @contexts[Thread_B_fiber], but the connection might still only be in Thread A's Selector. Then, when Thread A runs its selector loop and finishes reading the response for its last request, the connection will start being skipped in that loop. At this point, the connection will be in use by Thread B, but it won't be in its selector, so nobody would be reading from that socket.

I'm not completely sure about this, still feel I'm missing some piece of the puzzle here. I also can't explain the consistent slow down for all queries in the other jobs, which I also saw happening. I imagine it might have to do with the selector loop where the state is not what it expects (it'd expect a connection to wait on, but would get nothing, yet it'll have a request waiting), and the GVL.


In any case, it feels like having the permanent session shared between threads when a part of it, the one implemented in the :fiber_concurrency plugin, is not thread-safe, might be the source of trouble here, so this PR just makes the sessions be per-thread (and per service and application). Running this in production, with the same setup as before: 3 threads, 5 max size of the connection pool, and mixed with other jobs, is working perfectly.

We're relying on HTTPX's :persistent plugin, which is designed for
fiber-based concurrency within a single thread, not multi-threaded
access.
@rosa
Copy link
Member Author

rosa commented Dec 20, 2025

From a session with Claude, examining logs of jobs that were running together with the ActionPushNative::NotificationJob:

  Slow Job - Every Single Operation ~100ms

  | Operation                 | Time    |
  |---------------------------|---------|
  | User Load                 | 97.3ms  |
  | Contact Load              | 103.1ms |
  | QueenbeeCache Load        | 95.1ms  |
  | FamilyMembership Load     | 94.3ms  |
  | Kredis EXISTS?            | 100.0ms |
  | Kredis EXISTS?            | 97.9ms  |
  | Kredis GET                | 101.1ms |
  | Kredis GET                | 100.5ms |
  | Identity Load             | 99.7ms  |
  | BackupEmailAddress Load   | 99.4ms  |
  | Kredis GET                | 100.1ms |
  | User Load                 | 101.6ms |
  | Contact Load              | 101.3ms |
  | ... and so on, all ~100ms |         |

  Normal Job - Everything Sub-Millisecond

  | Operation               | Time  |
  |-------------------------|-------|
  | User Load               | 0.4ms |
  | Contact Load            | 0.4ms |
  | QueenbeeCache Load      | 0.4ms |
  | FamilyMembership Load   | 0.3ms |
  | Kredis EXISTS?          | 0.4ms |
  | Kredis EXISTS?          | 0.2ms |
  | Kredis GET              | 0.2ms |
  | Kredis GET              | 0.2ms |
  | Identity Load           | 0.5ms |
  | BackupEmailAddress Load | 0.4ms |
  | Kredis GET              | 0.2ms |
  | User Load               | 0.5ms |
  | Contact Load            | 0.5ms |
  | ... all sub-millisecond |       |

  The ~100ms Is Ruby's Thread Quantum

  Ruby's thread scheduler has a default quantum of 100ms - that's how long a thread can hold the GVL before being forcibly preempted.

  The spinning HTTPX thread holds the GVL doing CPU work (the broken IO.select loop), and Ruby's scheduler preempts it every ~100ms. Every I/O operation in the other thread has to wait for that preemption:

  Thread A (spinning):     |████████████████████|████████████████████|████████████████████|
                                100ms                 100ms                 100ms

  Thread C (audit job):    wait... User Load|wait... Contact Load|wait... Kredis|...
                           ←────100ms────→  ←────100ms────→     ←────100ms────→

  The bigger queries show proportionally larger delays because they span multiple quanta:
  - Account Eager Load: 749ms (slow) vs 47ms (normal) = ~700ms overhead (7 quanta)
  - Account Load: 245ms (slow) vs 43ms (normal) = ~200ms overhead (2 quanta)

  This is textbook GVL starvation. The fix eliminating per-thread sessions stopped the spinning, restored proper IO.select blocking, and let all threads share the GVL fairly.

@trevorturk
Copy link

trevorturk commented Dec 22, 2025

I did a bit of spelunking with Claude as well and this is what "we" came up with in case it's helpful at all:

https://gist.github.com/trevorturk/3b034da90954fa8e41cc063b40da11db

Summary

The Core Issue

PR #80 is correct. The problem is that HTTPX's :persistent plugin auto-loads > :fiber_concurrency, which is designed for fiber-based concurrency within a single > thread, not cross-thread sharing. The selector-per-thread architecture means > connections registered in Thread A are invisible to Thread B, causing:

  • GVL starvation from busy loops
  • HTTPX::PoolTimeoutError
  • CPU spikes
  • "stream closed in another thread" errors

Fiber Storage Consideration

For future-proofing, especially as Rails moves toward fiber-based concurrency (Falcon, > async job processors), the fix could use:

Option 1: Rails' IsolatedExecutionState (recommended)
ActiveSupport::IsolatedExecutionState[HTTPX_SESSIONS_KEY] ||= {}

Option 2: Keep Thread.current (PR #80 as-is)
Thread.current[HTTPX_SESSIONS_KEY] ||= {}

My recommendation: Accept PR #80 as-is for immediate production stability. The > thread-based fix covers Puma and Solid Queue (the dominant deployment). Fiber support > can be a follow-up if Falcon/async adoption grows.

For HTTPX Maintainers

The documentation should clarify that :persistent sessions are not designed to be > shared across threads. The FAQ's "thread-safe" claim refers to pool checkout/checkin, > not connection sharing. Issues #88, #98, and #120 all stem from this architectural > constraint.

The full document includes:

  • Detailed root cause analysis
  • Three solution options with trade-offs
  • Why connection pool configuration alone doesn't help
  • Specific documentation suggestions for HTTPX
  • Complete references to all relevant issues/PRs

It might be worth copying in the Async and HTTPX maintainers at this point? This is an area that's rapidly evolving. That being said, if this gem depends on Rails (IIRC it does) we could probably consider option 2 here, which would leverage the ActiveSupport::IsolatedExecutionState stuff. (Also might be worth copying in the relevant Rails Core people in that case...)

Anyway, hope this is somewhat helpful or at least give us some threads (and/or fibers) to pull on! 🤣

@intrip
Copy link
Collaborator

intrip commented Dec 22, 2025

Great digging @rosa!

@rosa
Copy link
Member Author

rosa commented Dec 22, 2025

Thank you!

Option 1: Rails' IsolatedExecutionState (recommended)
ActiveSupport::IsolatedExecutionState[HTTPX_SESSIONS_KEY] ||= {}

Ohhh, I hadn't thought of this, and I think it makes sense! I'll change to this and will test it out. Basically, this, from the document:

if this gem depends on Rails (IIRC it does) we could probably consider option 2 here, which would leverage the ActiveSupport::IsolatedExecutionState stuff

This gem is basically for Rails only, so yes, I think it makes perfect sense.

So this supports both Fiber and Thread isolation levels. Thanks to
@trevorturk for the idea!
@trevorturk
Copy link

Cool, please do let us know how it goes!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants