A share/exclusive lock, otherwise known as a read/write lock.
Methods
Included Modules
- MonitorMixin
Class Public methods
new()
π Source code
# File activesupport/lib/active_support/concurrency/share_lock.rb, line 49
def initialize
super()
@cv = new_cond
@sharing = Hash.new(0)
@waiting = {}
@sleeping = {}
@exclusive_thread = nil
@exclusive_depth = 0
end
π See on GitHub
Instance Public methods
exclusive(purpose: nil, compatible: [], after_compatible: [], no_wait: false)
Execute the supplied block while holding the Exclusive lock. If no_wait
is set and the lock is not immediately available, returns nil
without yielding. Otherwise, returns the result of the block.
See start_exclusive
for other options.
π Source code
# File activesupport/lib/active_support/concurrency/share_lock.rb, line 147
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(compatible: after_compatible)
end
end
end
π See on GitHub
sharing()
Execute the supplied block while holding the Share lock.
π Source code
# File activesupport/lib/active_support/concurrency/share_lock.rb, line 158
def sharing
start_sharing
begin
yield
ensure
stop_sharing
end
end
π See on GitHub
start_exclusive(purpose: nil, compatible: [], no_wait: false)
Returns false if no_wait
is set and the lock is not immediately available. Otherwise, returns true after the lock has been acquired.
purpose
and compatible
work together; while this thread is waiting for the exclusive lock, it will yield its share (if any) to any other attempt whose purpose
appears in this attemptβs compatible
list. This allows a βlooseβ upgrade, which, being less strict, prevents some classes of deadlocks.
For many resources, loose upgrades are sufficient: if a thread is awaiting a lock, it is not running any other code. With purpose
matching, it is possible to yield only to other threads whose activity will not interfere.
π Source code
# File activesupport/lib/active_support/concurrency/share_lock.rb, line 75
def start_exclusive(purpose: nil, compatible: [], no_wait: false)
synchronize do
unless @exclusive_thread == Thread.current
if busy_for_exclusive?(purpose)
return false if no_wait
yield_shares(purpose: purpose, compatible: compatible, block_share: true) do
wait_for(:start_exclusive) { busy_for_exclusive?(purpose) }
end
end
@exclusive_thread = Thread.current
end
@exclusive_depth += 1
true
end
end
π See on GitHub
start_sharing()
π Source code
# File activesupport/lib/active_support/concurrency/share_lock.rb, line 113
def start_sharing
synchronize do
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
wait_for(:start_sharing) { @exclusive_thread }
else
# This is an initial / outermost share call: any outstanding
# requests for an exclusive lock get to go first
wait_for(:start_sharing) { busy_for_sharing?(false) }
end
@sharing[Thread.current] += 1
end
end
π See on GitHub
stop_exclusive(compatible: [])
Relinquish the exclusive lock. Must only be called by the thread that called start_exclusive
(and currently holds the lock).
π Source code
# File activesupport/lib/active_support/concurrency/share_lock.rb, line 95
def stop_exclusive(compatible: [])
synchronize do
raise "invalid unlock" if @exclusive_thread != Thread.current
@exclusive_depth -= 1
if @exclusive_depth == 0
@exclusive_thread = nil
if eligible_waiters?(compatible)
yield_shares(compatible: compatible, block_share: true) do
wait_for(:stop_exclusive) { @exclusive_thread || eligible_waiters?(compatible) }
end
end
@cv.broadcast
end
end
end
π See on GitHub
stop_sharing()
π Source code
# File activesupport/lib/active_support/concurrency/share_lock.rb, line 130
def stop_sharing
synchronize do
if @sharing[Thread.current] > 1
@sharing[Thread.current] -= 1
else
@sharing.delete Thread.current
@cv.broadcast
end
end
end
π See on GitHub
yield_shares(purpose: nil, compatible: [], block_share: false)
Temporarily give up all held Share locks while executing the supplied block, allowing any compatible
exclusive lock request to proceed.
π Source code
# File activesupport/lib/active_support/concurrency/share_lock.rb, line 170
def yield_shares(purpose: nil, compatible: [], block_share: false)
loose_shares = previous_wait = nil
synchronize do
if loose_shares = @sharing.delete(Thread.current)
if previous_wait = @waiting[Thread.current]
purpose = nil unless purpose == previous_wait[0]
compatible &= previous_wait[1]
end
compatible |= [false] unless block_share
@waiting[Thread.current] = [purpose, compatible]
end
@cv.broadcast
end
begin
yield
ensure
synchronize do
wait_for(:yield_shares) { @exclusive_thread && @exclusive_thread != Thread.current }
if previous_wait
@waiting[Thread.current] = previous_wait
else
@waiting.delete Thread.current
end
@sharing[Thread.current] = loose_shares if loose_shares
end
end
end
π See on GitHub