Skip to content

Commit 62517fd

Browse files
committed
Don't clear ActiveRecord connections when in nested job
1 parent e675fee commit 62517fd

File tree

2 files changed

+53
-11
lines changed

2 files changed

+53
-11
lines changed

lib/que/active_record/connection.rb

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
module Que
44
module ActiveRecord
55
class << self
6+
def active_rails_executor?
7+
defined?(::Rails.application.executor) && ::Rails.application.executor.active?
8+
end
9+
610
def wrap_in_rails_executor(&block)
711
if defined?(::Rails.application.executor)
812
::Rails.application.executor.wrap(&block)
@@ -39,17 +43,36 @@ def call(job)
3943
yield
4044
end
4145

42-
# ActiveRecord will check out connections to the current thread when
43-
# queries are executed and not return them to the pool until
44-
# explicitly requested to. I'm not wild about this API design, and
45-
# it doesn't pose a problem for the typical case of workers using a
46-
# single PG connection (since we ensure that connection is checked
47-
# in and checked out responsibly), but since ActiveRecord supports
48-
# connections to multiple databases, it's easy for people using that
49-
# feature to unknowingly leak connections to other databases. So,
50-
# take the additional step of telling ActiveRecord to check in all
51-
# of the current thread's connections after each job is run.
52-
::ActiveRecord::Base.clear_active_connections! unless job.class.resolve_que_setting(:run_synchronously)
46+
clear_active_connections_if_needed!(job)
47+
end
48+
49+
private
50+
51+
# ActiveRecord will check out connections to the current thread when
52+
# queries are executed and not return them to the pool until
53+
# explicitly requested to. I'm not wild about this API design, and
54+
# it doesn't pose a problem for the typical case of workers using a
55+
# single PG connection (since we ensure that connection is checked
56+
# in and checked out responsibly), but since ActiveRecord supports
57+
# connections to multiple databases, it's easy for people using that
58+
# feature to unknowingly leak connections to other databases. So,
59+
# take the additional step of telling ActiveRecord to check in all
60+
# of the current thread's connections after each job is run.
61+
def clear_active_connections_if_needed!(job)
62+
# don't clean in synchronous mode
63+
# see https://github.com/que-rb/que/pull/393
64+
return if job.class.resolve_que_setting(:run_synchronously)
65+
66+
# don't clear connections in nested jobs,
67+
# i.e. clear only if this is the outermost instance of
68+
# Que::ActiveRecord::Connection::JobMiddleware
69+
return if Que::ActiveRecord.active_rails_executor?
70+
71+
if ::ActiveRecord.version >= Gem::Version.new('7.1')
72+
::ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
73+
else
74+
::ActiveRecord::Base.clear_active_connections!
75+
end
5376
end
5477
end
5578
end

spec/que/active_record/connection_spec.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,24 @@ def run(*args)
6262

6363
assert SecondDatabaseModel.connection_handler.active_connections?
6464
end
65+
66+
it "shouldn't clear connections to secondary DBs if within an active rails executor" do
67+
# This is a hacky spec, but it's better than requiring Rails.
68+
rails, application, executor = 3.times.map { Object.new }
69+
application.define_singleton_method(:executor) { executor }
70+
rails.define_singleton_method(:application) { application }
71+
executor.define_singleton_method(:wrap) { |&block| block.call }
72+
executor.define_singleton_method(:active?) { true }
73+
74+
refute defined?(::Rails)
75+
::Rails = rails
76+
77+
SecondDatabaseModelJob.run
78+
79+
assert SecondDatabaseModel.connection_handler.active_connections?
80+
81+
Object.send :remove_const, :Rails
82+
refute defined?(::Rails)
83+
end
6584
end
6685
end

0 commit comments

Comments
 (0)