aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/concurrency
diff options
context:
space:
mode:
authorMatthew Draper <matthew@trebex.net>2016-02-08 00:36:21 +1030
committerMatthew Draper <matthew@trebex.net>2016-02-08 00:36:21 +1030
commitd2c671e3cffc60ef00df63a28cc9ae0eb89f1b31 (patch)
treef509422b52fcb00c4beeff75bad00fc66eb729ac /activesupport/lib/active_support/concurrency
parent9f2df7856d0515f097d517395f944e6faf414d6d (diff)
downloadrails-d2c671e3cffc60ef00df63a28cc9ae0eb89f1b31.tar.gz
rails-d2c671e3cffc60ef00df63a28cc9ae0eb89f1b31.tar.bz2
rails-d2c671e3cffc60ef00df63a28cc9ae0eb89f1b31.zip
Eagerly reacquire when start_sharing is nested inside yield_shares
A full write-preferring wait can lead to deadlock.
Diffstat (limited to 'activesupport/lib/active_support/concurrency')
-rw-r--r--activesupport/lib/active_support/concurrency/share_lock.rb29
1 files changed, 18 insertions, 11 deletions
diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb
index 2ac278a2f1..ed3345d4dc 100644
--- a/activesupport/lib/active_support/concurrency/share_lock.rb
+++ b/activesupport/lib/active_support/concurrency/share_lock.rb
@@ -6,12 +6,6 @@ module ActiveSupport
# A share/exclusive lock, otherwise known as a read/write lock.
#
# https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock
- #--
- # Note that a pending Exclusive lock attempt does not block incoming
- # Share requests (i.e., we are "read-preferring"). That seems
- # consistent with the behavior of "loose" upgrades, but may be the
- # wrong choice otherwise: it nominally reduces the possibility of
- # deadlock by risking starvation instead.
class ShareLock
include MonitorMixin
@@ -73,8 +67,10 @@ module ActiveSupport
if @exclusive_depth == 0
@exclusive_thread = nil
- yield_shares(compatible: compatible) do
- @cv.wait_while { @exclusive_thread || eligible_waiters?(compatible) }
+ if eligible_waiters?(compatible)
+ yield_shares(compatible: compatible) do
+ @cv.wait_while { @exclusive_thread || eligible_waiters?(compatible) }
+ end
end
@cv.broadcast
end
@@ -83,7 +79,15 @@ module ActiveSupport
def start_sharing(purpose: :share)
synchronize do
- if @sharing[Thread.current] == 0 && @exclusive_thread != Thread.current && busy_for_sharing?(purpose)
+ if @sharing[Thread.current] > 0 || @exclusive_thread == Thread.current
+ # We already hold a lock; nothing to wait for
+ elsif @waiting[Thread.current]
+ # We're nested inside a +yield_shares+ call: we'll resume as
+ # soon as there isn't an exclusive lock in our way
+ @cv.wait_while { @exclusive_thread }
+ else
+ # This is an initial / outermost share call: any outstanding
+ # requests for an exclusive lock get to go first
@cv.wait_while { busy_for_sharing?(purpose) }
end
@sharing[Thread.current] += 1
@@ -127,6 +131,9 @@ module ActiveSupport
end
end
+ # Temporarily give up all held Share locks while executing the
+ # supplied block, allowing any +compatible+ exclusive lock request
+ # to proceed.
def yield_shares(purpose: nil, compatible: [])
loose_shares = previous_wait = nil
synchronize do
@@ -136,9 +143,9 @@ module ActiveSupport
compatible &= previous_wait[1]
end
@waiting[Thread.current] = [purpose, compatible]
- end
- @cv.broadcast
+ @cv.broadcast
+ end
end
begin