-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Leaking database connection issue #20
Comments
Hmm, that's interesting. I believe that Can you tell a little more about your usage: ActiveRecord version, your database setup (multiple databases?), any custom database-related initializers? |
Gem versions:
You're right, More over, guys from Sidekiq added support of BTW, I'm still not 100% sure that |
And a bit more details about how we use this gem. (Almost the same as Sidekiq guys use it)
module Sidekiq
module PostponeSchedulingUntilTransactionCommit
include AfterCommitEverywhere
def raw_push(payloads)
if payloads.first['postpone_scheduling_until_transaction_commit'] == false
super
else
ActiveRecord::Base.connection_pool.with_connection do |conn|
after_commit(connection: conn) { super }
end
end
end
end
end Before the fix which (probably) solved our problem it was: def raw_push(payloads)
if payloads.first['postpone_scheduling_until_transaction_commit'] == false
super
else
after_commit { super }
end
end
require 'sidekiq/postpone_scheduling_until_transaction_commit'
Sidekiq::Client.public_send(:prepend, Sidekiq::PostponeSchedulingUntilTransactionCommit) |
Got it, thanks. I think that it is better not to checkout new connection with I'm probably going to add |
Great! Thank you so much, I'll keep you posted if the issue will be reproduced again (even with my fix). |
Ok, I think the reason why this gem worked well for years and you're literally the first person to complain is that usually all its invocations (whether it happens during serving HTTP request in Rails controller or performing job in Sidekiq worker process) are always done inside Rails executor which checks in any connections back to the connection pool. It seems that sometimes your app enqueues Sidekiq jobs outside of executor context (not within controller action or another Sidekiq job) and in that case connection is being checked out and never checked back in. So after_commit_everywhere should be fixed for such usages and non-Rails apps using ActiveRecord. |
Hm, I'm pretty sure that all jobs are queued eventually from controllers and Sidekiq jobs. But probably this issue may be somehow connected with a combination of monkey patching and usage of If this is true, then Sidekiq will have the same bug in the nearest feature. Need to investigate this deeper. Thanks so much for your help! |
Here is the pull request, feel free to comment: #21 |
…en (#21) Usually all of `after_commit` invocations (whether it happens during serving HTTP request in Rails controller or performing job in Sidekiq worker process) are always done inside Rails executor which checks in any connections back to the connection pool that were checked out inside its block. See https://guides.rubyonrails.org/threading_and_code_execution.html for details However, in cases when a) `after_commit` was called outside of Rails executor (3-rd party gems or non-Rails apps using ActiveRecord) b) **and** database connection hasn't been checked out yet then connection will be checked out by `after_commit` implicitly by call to `ActiveRecord::Base.connection` and not checked in back afterwards causing it to _leak_. But in that case we can be pretty sure that there is no transaction in progress (cause one need to checkout connection and issue `BEGIN` to it), so we don't need to check it out at all and can fast-forward to `without_tx` action. Fixes #20
Fix has been released in version 1.2.1. Thank you very much for reporting! |
As soon as I give up with my workaround and updated gem version – the problem started to appear again :( For example, you can run this code from the rails console:
Ideally, we need to find a way to check if there is any |
Seems I found a way to reproduce it from Rails console. Just run each example in separate rails consoles: 5.times.map do |i|
Thread.new do
sleep i
ActiveRecord::Base.connection_pool.with_connection do |conn|
AfterCommitEverywhere.after_commit(connection: conn) { 'do stuff' }
end
end
end.map(&:join)
ActiveRecord::Base.connection_pool.connections.count # => 1 Here everything works good, the single connection is reused for multiple thread. 5.times.map do |i|
Thread.new do
sleep i
AfterCommitEverywhere.after_commit { 'do_stuff' }
end
end.map(&:join)
ActiveRecord::Base.connection_pool.connections.count # => 0 The process's connections pool is not connected to the database, no new connections were created, OK. ActiveRecord::Base.connection
5.times.map do |i|
Thread.new do
sleep i
AfterCommitEverywhere.after_commit { 'do_stuff' }
end
end.map(&:join)
ActiveRecord::Base.connection_pool.connections.count # => 6
|
Thanks for reproduction steps! I will take another look into it. |
Fixes #20 Method `connected?` checks if there are any connections open, even in a different thread, while `active_connection?` checks for connection owned by current thread. See: - https://api.rubyonrails.org/v5.2.3/classes/ActiveRecord/ConnectionAdapters/ConnectionPool.html#method-i-active_connection-3F - https://api.rubyonrails.org/v5.2.3/classes/ActiveRecord/ConnectionAdapters/ConnectionPool.html#method-i-connected-3F
Follow-up for #21 Fixes #20 (specifically #20 (comment)) Method `connected?` checks if there are any connections open, even in a different thread, while `active_connection?` checks for connection owned by current thread. See: - https://api.rubyonrails.org/v5.2.3/classes/ActiveRecord/ConnectionAdapters/ConnectionPool.html#method-i-active_connection-3F - https://api.rubyonrails.org/v5.2.3/classes/ActiveRecord/ConnectionAdapters/ConnectionPool.html#method-i-connected-3F
One more fix has been released in 1.2.2 (using |
…en (#21) Usually all of `after_commit` invocations (whether it happens during serving HTTP request in Rails controller or performing job in Sidekiq worker process) are always done inside Rails executor which checks in any connections back to the connection pool that were checked out inside its block. See https://guides.rubyonrails.org/threading_and_code_execution.html for details However, in cases when a) `after_commit` was called outside of Rails executor (3-rd party gems or non-Rails apps using ActiveRecord) b) **and** database connection hasn't been checked out yet then connection will be checked out by `after_commit` implicitly by call to `ActiveRecord::Base.connection` and not checked in back afterwards causing it to _leak_. But in that case we can be pretty sure that there is no transaction in progress (cause one need to checkout connection and issue `BEGIN` to it), so we don't need to check it out at all and can fast-forward to `without_tx` action. Fixes Envek/after_commit_everywhere#20
Follow-up for Envek/after_commit_everywhere#21 Fixes Envek/after_commit_everywhere#20 (specifically Envek/after_commit_everywhere#20 (comment)) Method `connected?` checks if there are any connections open, even in a different thread, while `active_connection?` checks for connection owned by current thread. See: - https://api.rubyonrails.org/v5.2.3/classes/ActiveRecord/ConnectionAdapters/ConnectionPool.html#method-i-active_connection-3F - https://api.rubyonrails.org/v5.2.3/classes/ActiveRecord/ConnectionAdapters/ConnectionPool.html#method-i-connected-3F
Thank you so much for this gem. It is a really great thing to have in our code base.
After we added this gem to our project we started getting a lot of these kinds of errors from the Sidekiq:
I may assume that this is somehow relates to usage of
ActiveRecord::Base.connection
instead ofActiveRecord::Base.connection_pool.with_connection { |connection| ... }
.So far we have fixed that issue by wrapping all
after_commit { do_stuff }
instructions with:Probably, it would be nice to have some guard here to check if we have any active connections, if not – we can run the block immediately (absolutely the same check as you have for an opened transaction).
Useful links:
The text was updated successfully, but these errors were encountered: