Skip to content

Run inline jobs in separate threads#37568

Merged
gmcgibbon merged 1 commit intorails:masterfrom
gmcgibbon:run_inline_jobs_in_theor_own_thread
Nov 2, 2019
Merged

Run inline jobs in separate threads#37568
gmcgibbon merged 1 commit intorails:masterfrom
gmcgibbon:run_inline_jobs_in_theor_own_thread

Conversation

@gmcgibbon
Copy link
Member

Summary

Closes #37526.

Runs inline jobs in separate threads so that thread locals (eg. current attributes) are properly scoped and reset when running jobs inline.

@rails-bot rails-bot bot added the activejob label Oct 25, 2019
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test needs 2 threads to test that threading is working correctly. Otherwise you just have one thread and it will always pass regardless of the Thread.new block

Copy link
Member Author

@gmcgibbon gmcgibbon Oct 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree this test could be better. I tried inspecting the thread spun up by the inline adapter, but Thread.list doesn't show it because it dies immediately after joining. How can I use two threads to improve this test?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should do it

diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb
index 7c92496331..a7d3865c42 100644
--- a/activejob/test/integration/queuing_test.rb
+++ b/activejob/test/integration/queuing_test.rb
@@ -150,8 +150,15 @@ class QueuingTest < ActiveSupport::TestCase
   test "inline jobs run on separate threads" do
     skip unless adapter_is?(:inline)

-    assert_nil Thread.current[:job_ran]
+    t1 = Thread.new do
+      ThreadJob::LatchManager.latch1.wait
+      assert_nil Thread.current[:job_ran]
+      assert(ThreadJob::LatchManager.job_thread[:job_ran])
+      ThreadJob::LatchManager.latch2.count_down
+    end
+
     ThreadJob.perform_later
+    t1.join
     assert_nil Thread.current[:job_ran]
   end
 end
diff --git a/activejob/test/jobs/thread_job.rb b/activejob/test/jobs/thread_job.rb
index 804d18b4a9..27218760a0 100644
--- a/activejob/test/jobs/thread_job.rb
+++ b/activejob/test/jobs/thread_job.rb
@@ -1,7 +1,24 @@
 # frozen_string_literal: true

 class ThreadJob < ActiveJob::Base
+  class LatchManager
+    class << self
+      attr_accessor :job_thread
+
+      def latch1
+        @@latch1 ||= Concurrent::CountDownLatch.new
+      end
+
+      def latch2
+        @@latch2 ||= Concurrent::CountDownLatch.new
+      end
+    end
+  end
+
   def perform
     Thread.current[:job_ran] = true
+    LatchManager.job_thread = Thread.current
+    LatchManager.latch1.count_down
+    LatchManager.latch2.wait
   end
 end

@gmcgibbon gmcgibbon force-pushed the run_inline_jobs_in_theor_own_thread branch 3 times, most recently from baa21e2 to b0adf6d Compare November 1, 2019 23:31
@sedubois
Copy link
Contributor

sedubois commented Nov 6, 2020

This seems to cause newly persisted resources not to be visible to my app's inline jobs (can be workarounded by switching to async queue adapter):

#40552 (comment)

jonathanhefner added a commit to jonathanhefner/rails that referenced this pull request Nov 15, 2020
Before rails#34953, when using the `:async` Active Job queue adapter, jobs
enqueued in `db/seeds.rb`, such as Active Storage analysis jobs, would
cause a hang (see rails#34939).  Therefore, rails#34953 changed all jobs enqueued
in `db/seeds.rb` to use the `:inline` queue adapter instead.  (This
behavior was later limited to only take effect when the `:async` adapter
was configured, see rails#35905.)  However, inline jobs in `db/seeds.rb`
cleared `CurrentAttributes` values (see rails#37526).  Therefore, rails#37568
changed the `:inline` adapter to wrap each job in its own thread, for
isolation.  However, wrapping a job in its own thread affects which
database connection it uses.  Thus inline jobs can no longer execute
within the calling thread's database transaction, including seeing any
uncommitted changes.  Additionally, if the calling thread is not wrapped
with the executor, the inline job thread (which is wrapped with the
executor) can deadlock on the load interlock.  And when testing (with
`connection_pool.lock_thread = true`), the inline job thread can
deadlock on one of the locks added by rails#28083.

Therefore, this commit reverts the solutions of rails#34953 and rails#37568, and
instead wraps evaluation of `db/seeds.rb` with the executor.  This
eliminates the original hang from rails#34939, which was also due to running
multiple threads and not wrapping all of them with the executor.  And,
because nested calls to `executor.wrap` are ignored, any inline jobs in
`db/seeds.rb` will not clear `CurrentAttributes` values.

Alternative fix for rails#34939.
Reverts rails#34953.
Reverts rails#35905.
Partially reverts rails#35896.

Alternative fix for rails#37526.
Reverts rails#37568.

Fixes rails#40552.
jonathanhefner added a commit to jonathanhefner/rails that referenced this pull request Nov 18, 2020
Before rails#34953, when using the `:async` Active Job queue adapter, jobs
enqueued in `db/seeds.rb`, such as Active Storage analysis jobs, would
cause a hang (see rails#34939).  Therefore, rails#34953 changed all jobs enqueued
in `db/seeds.rb` to use the `:inline` queue adapter instead.  (This
behavior was later limited to only take effect when the `:async` adapter
was configured, see rails#35905.)  However, inline jobs in `db/seeds.rb`
cleared `CurrentAttributes` values (see rails#37526).  Therefore, rails#37568
changed the `:inline` adapter to wrap each job in its own thread, for
isolation.  However, wrapping a job in its own thread affects which
database connection it uses.  Thus inline jobs can no longer execute
within the calling thread's database transaction, including seeing any
uncommitted changes.  Additionally, if the calling thread is not wrapped
with the executor, the inline job thread (which is wrapped with the
executor) can deadlock on the load interlock.  And when testing (with
`connection_pool.lock_thread = true`), the inline job thread can
deadlock on one of the locks added by rails#28083.

Therefore, this commit reverts the solutions of rails#34953 and rails#37568, and
instead wraps evaluation of `db/seeds.rb` with the executor.  This
eliminates the original hang from rails#34939, which was also due to running
multiple threads and not wrapping all of them with the executor.  And,
because nested calls to `executor.wrap` are ignored, any inline jobs in
`db/seeds.rb` will not clear `CurrentAttributes` values.

Alternative fix for rails#34939.
Reverts rails#34953.
Reverts rails#35905.
Partially reverts rails#35896.

Alternative fix for rails#37526.
Reverts rails#37568.

Fixes rails#40552.
jonathanhefner added a commit to jonathanhefner/rails that referenced this pull request Dec 2, 2020
Before rails#34953, when using the `:async` Active Job queue adapter, jobs
enqueued in `db/seeds.rb`, such as Active Storage analysis jobs, would
cause a hang (see rails#34939).  Therefore, rails#34953 changed all jobs enqueued
in `db/seeds.rb` to use the `:inline` queue adapter instead.  (This
behavior was later limited to only take effect when the `:async` adapter
was configured, see rails#35905.)  However, inline jobs in `db/seeds.rb`
cleared `CurrentAttributes` values (see rails#37526).  Therefore, rails#37568
changed the `:inline` adapter to wrap each job in its own thread, for
isolation.  However, wrapping a job in its own thread affects which
database connection it uses.  Thus inline jobs can no longer execute
within the calling thread's database transaction, including seeing any
uncommitted changes.  Additionally, if the calling thread is not wrapped
with the executor, the inline job thread (which is wrapped with the
executor) can deadlock on the load interlock.  And when testing (with
`connection_pool.lock_thread = true`), the inline job thread can
deadlock on one of the locks added by rails#28083.

Therefore, this commit reverts the solutions of rails#34953 and rails#37568, and
instead wraps evaluation of `db/seeds.rb` with the executor.  This
eliminates the original hang from rails#34939, which was also due to running
multiple threads and not wrapping all of them with the executor.  And,
because nested calls to `executor.wrap` are ignored, any inline jobs in
`db/seeds.rb` will not clear `CurrentAttributes` values.

Alternative fix for rails#34939.
Reverts rails#34953.
Reverts rails#35905.
Partially reverts rails#35896.

Alternative fix for rails#37526.
Reverts rails#37568.

Fixes rails#40552.

(cherry picked from commit 648da12)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inline ActiveJob clears CurrentAttributes erroneously

5 participants