Skip to content

Deadlock in check_pending_migrations with multiple databases and missing connects_to #49689

@bensheldon

Description

@bensheldon

I looked into this with @matthewd and he believed it warranted opening an issue for visibility.

Briefly, I have a Rails Application with multiple databases configured in database.yml (primary and animals). And I have a Rails Engine with a BaseRecord < ActiveRecord::Base that doesn't have a connects_to .

When running the application, for some reason the check_pending_migrations middleware is causing a hang because it seems to be waiting on attempt_to_checkout_all_existing_connections.

In my poking at it, the only connection that seems to be checked out and waiting for that exclusive checkout is the same connection that has been checked out a little earlier in the callstack from with_temporary_connection.

Report generated by `ActionDispatch::DebugLocks`
Thread 0 [0x3c78 sleep]  No lock (yielded share)

/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/3.2.0/monitor.rb:108:in `sleep'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/3.2.0/monitor.rb:108:in `wait'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/3.2.0/monitor.rb:108:in `wait_for_cond'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/3.2.0/monitor.rb:108:in `wait'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb:174:in `wait'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb:119:in `block (2 levels) in wait_poll'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activesupport-7.1.0/lib/active_support/concurrency/share_lock.rb:187:in `yield_shares'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activesupport-7.1.0/lib/active_support/dependencies/interlock.rb:41:in `permit_concurrent_loads'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb:118:in `block in wait_poll'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb:117:in `loop'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb:117:in `wait_poll'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb:78:in `internal_poll'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb:204:in `internal_poll'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb:73:in `block in poll'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/3.2.0/monitor.rb:202:in `synchronize'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/3.2.0/monitor.rb:202:in `mon_synchronize'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb:82:in `synchronize'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb:73:in `poll'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:661:in `acquire_connection'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:355:in `checkout'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:591:in `checkout_for_exclusive_access'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:559:in `block (3 levels) in attempt_to_checkout_all_existing_connections'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/3.2.0/monitor.rb:202:in `synchronize'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/3.2.0/monitor.rb:202:in `mon_synchronize'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:555:in `block (2 levels) in attempt_to_checkout_all_existing_connections'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:554:in `loop'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:554:in `block in attempt_to_checkout_all_existing_connections'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb:185:in `with_a_bias_for'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:553:in `attempt_to_checkout_all_existing_connections'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:539:in `block in with_exclusively_acquired_all_connections'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:615:in `with_new_connections_blocked'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:538:in `with_exclusively_acquired_all_connections'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:261:in `disconnect'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:283:in `disconnect!'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/pool_config.rb:56:in `block in disconnect!'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/3.2.0/mutex_m.rb:79:in `synchronize'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/3.2.0/mutex_m.rb:79:in `mu_synchronize'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/pool_config.rb:52:in `disconnect!'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_handler.rb:308:in `disconnect_pool_from_pool_manager'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/connection_adapters/abstract/connection_handler.rb:155:in `establish_connection'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/tasks/database_tasks.rb:516:in `with_temporary_pool'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/tasks/database_tasks.rb:500:in `with_temporary_connection'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/tasks/database_tasks.rb:494:in `block in with_temporary_connection_for_each'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/tasks/database_tasks.rb:493:in `each'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/tasks/database_tasks.rb:493:in `with_temporary_connection_for_each'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/migration.rb:771:in `pending_migrations'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/migration.rb:750:in `check_pending_migrations'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/migration.rb:642:in `block (2 levels) in call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activesupport-7.1.0/lib/active_support/file_update_checker.rb:85:in `execute'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/migration.rb:647:in `block in call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/migration.rb:639:in `synchronize'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.1.0/lib/active_record/migration.rb:639:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/actionpack-7.1.0/lib/action_dispatch/middleware/callbacks.rb:29:in `block in call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activesupport-7.1.0/lib/active_support/callbacks.rb:101:in `run_callbacks'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/actionpack-7.1.0/lib/action_dispatch/middleware/callbacks.rb:28:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/actionpack-7.1.0/lib/action_dispatch/middleware/executor.rb:14:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/actionpack-7.1.0/lib/action_dispatch/middleware/actionable_exceptions.rb:16:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/actionpack-7.1.0/lib/action_dispatch/middleware/debug_exceptions.rb:29:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/web-console-4.2.1/lib/web_console/middleware.rb:132:in `call_app'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/web-console-4.2.1/lib/web_console/middleware.rb:28:in `block in call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/web-console-4.2.1/lib/web_console/middleware.rb:17:in `catch'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/web-console-4.2.1/lib/web_console/middleware.rb:17:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/actionpack-7.1.0/lib/action_dispatch/middleware/show_exceptions.rb:31:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/railties-7.1.0/lib/rails/rack/logger.rb:37:in `call_app'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/railties-7.1.0/lib/rails/rack/logger.rb:24:in `block in call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activesupport-7.1.0/lib/active_support/tagged_logging.rb:135:in `block in tagged'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activesupport-7.1.0/lib/active_support/tagged_logging.rb:39:in `tagged'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activesupport-7.1.0/lib/active_support/tagged_logging.rb:135:in `tagged'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activesupport-7.1.0/lib/active_support/broadcast_logger.rb:212:in `method_missing'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/railties-7.1.0/lib/rails/rack/logger.rb:24:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/sprockets-rails-3.4.2/lib/sprockets/rails/quiet_assets.rb:13:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/actionpack-7.1.0/lib/action_dispatch/middleware/remote_ip.rb:92:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/actionpack-7.1.0/lib/action_dispatch/middleware/request_id.rb:28:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/rack-3.0.8/lib/rack/method_override.rb:28:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/rack-3.0.8/lib/rack/runtime.rb:24:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/activesupport-7.1.0/lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/actionpack-7.1.0/lib/action_dispatch/middleware/server_timing.rb:59:in `block in call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/actionpack-7.1.0/lib/action_dispatch/middleware/server_timing.rb:24:in `collect_events'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/actionpack-7.1.0/lib/action_dispatch/middleware/server_timing.rb:58:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/actionpack-7.1.0/lib/action_dispatch/middleware/executor.rb:14:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/actionpack-7.1.0/lib/action_dispatch/middleware/static.rb:25:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/rack-3.0.8/lib/rack/sendfile.rb:114:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/actionpack-7.1.0/lib/action_dispatch/middleware/host_authorization.rb:141:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/railties-7.1.0/lib/rails/engine.rb:529:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/puma-6.4.0/lib/puma/configuration.rb:272:in `call'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/puma-6.4.0/lib/puma/request.rb:100:in `block in handle_request'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/puma-6.4.0/lib/puma/thread_pool.rb:378:in `with_force_shutdown'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/puma-6.4.0/lib/puma/request.rb:99:in `handle_request'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/puma-6.4.0/lib/puma/server.rb:443:in `process_client'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/puma-6.4.0/lib/puma/server.rb:241:in `block in run'
/Users/bensheldon/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/puma-6.4.0/lib/puma/thread_pool.rb:155:in `block in spawn_thread'

Our thinking is that by not having a connects_to on the model, the model is using the connection that’s used on ActiveRecord::Base. But the migration checker also uses the connection on ActiveRecord::Base to do the migration. When the temporary connection tries to disconnect and clean up, it gets stuck waiting for the currently outstanding connection to be returned to the pool. Which it can’t because that thread is stuck waiting for it to be returned to the pool.

And a thought was to fix it with with_temporary_connection_for_each so that it is ok for there to be an existing pool that can be set aside when running the migration-related queries.

Initially reported in bensheldon/good_job#1103

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions