aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/concurrency
diff options
context:
space:
mode:
authorMatthew Draper <matthew@trebex.net>2016-02-02 01:42:20 +1030
committerMatthew Draper <matthew@trebex.net>2016-02-02 03:21:03 +1030
commitf836630f8cdf53a06259cc22ac842bbfa6376f65 (patch)
treee870b2cc12783ca8b436bc28ca22a2d9a960306d /activesupport/lib/active_support/concurrency
parentf02bd2a92c67f0d4190853521d3580766e829044 (diff)
downloadrails-f836630f8cdf53a06259cc22ac842bbfa6376f65.tar.gz
rails-f836630f8cdf53a06259cc22ac842bbfa6376f65.tar.bz2
rails-f836630f8cdf53a06259cc22ac842bbfa6376f65.zip
After completing a load, give other threads a chance too
While we know no user code is running, we should do as much loading as we can. That way, all the threads will then be able to resume running user code together. Otherwise, only the last arriving thread would get to do its load, and would then return to userspace, leaving the others still blocked.
Diffstat (limited to 'activesupport/lib/active_support/concurrency')
-rw-r--r--activesupport/lib/active_support/concurrency/share_lock.rb38
1 files changed, 26 insertions, 12 deletions
diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb
index fa5d9bfdd7..8e4ca272ba 100644
--- a/activesupport/lib/active_support/concurrency/share_lock.rb
+++ b/activesupport/lib/active_support/concurrency/share_lock.rb
@@ -51,14 +51,8 @@ module ActiveSupport
if busy_for_exclusive?(purpose)
return false if no_wait
- loose_shares = @sharing.delete(Thread.current)
- @waiting[Thread.current] = compatible if loose_shares
-
- begin
+ yield_shares(purpose, compatible) do
@cv.wait_while { busy_for_exclusive?(purpose) }
- ensure
- @waiting.delete Thread.current
- @sharing[Thread.current] = loose_shares if loose_shares
end
end
@exclusive_thread = Thread.current
@@ -71,14 +65,18 @@ module ActiveSupport
# Relinquish the exclusive lock. Must only be called by the thread
# that called start_exclusive (and currently holds the lock).
- def stop_exclusive
+ def stop_exclusive(compatible: [])
synchronize do
raise "invalid unlock" if @exclusive_thread != Thread.current
@exclusive_depth -= 1
if @exclusive_depth == 0
@exclusive_thread = nil
- @cv.broadcast
+
+ yield_shares(nil, compatible) do
+ @cv.broadcast
+ @cv.wait_while { @exclusive_thread || eligible_waiters?(compatible) }
+ end
end
end
end
@@ -109,12 +107,12 @@ module ActiveSupport
# the block.
#
# See +start_exclusive+ for other options.
- def exclusive(purpose: nil, compatible: [], no_wait: false)
+ def exclusive(purpose: nil, compatible: [], after_compatible: [], no_wait: false)
if start_exclusive(purpose: purpose, compatible: compatible, no_wait: no_wait)
begin
yield
ensure
- stop_exclusive
+ stop_exclusive(compatible: after_compatible)
end
end
end
@@ -139,7 +137,23 @@ module ActiveSupport
def busy_for_sharing?(purpose)
(@exclusive_thread && @exclusive_thread != Thread.current) ||
- @waiting.any? { |k, v| k != Thread.current && !v.include?(purpose) }
+ @waiting.any? { |t, (_, c)| t != Thread.current && !c.include?(purpose) }
+ end
+
+ def eligible_waiters?(compatible)
+ @waiting.any? { |t, (p, _)| compatible.include?(p) && @waiting.all? { |t2, (_, c2)| t == t2 || c2.include?(p) } }
+ end
+
+ def yield_shares(purpose, compatible)
+ loose_shares = @sharing.delete(Thread.current)
+ @waiting[Thread.current] = [purpose, compatible] if loose_shares
+
+ begin
+ yield
+ ensure
+ @waiting.delete Thread.current
+ @sharing[Thread.current] = loose_shares if loose_shares
+ end
end
end
end