A share/exclusive lock, otherwise known as a read/write lock.
- E
- N
- S
- Y
- MonitorMixin
We track Thread objects, instead of just using counters, because we need exclusive locks to be reentrant, and we need to be able to upgrade share locks to exclusive.
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.
# File activesupport/lib/active_support/concurrency/share_lock.rb, line 114 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
Execute the supplied block while holding the Share lock.
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.
# File activesupport/lib/active_support/concurrency/share_lock.rb, line 42 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 @cv.wait_while { busy_for_exclusive?(purpose) } end end @exclusive_thread = Thread.current end @exclusive_depth += 1 true end end
# File activesupport/lib/active_support/concurrency/share_lock.rb, line 80 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 @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?(false) } end @sharing[Thread.current] += 1 end end
Relinquish the exclusive lock. Must only be called by the thread that called #start_exclusive (and currently holds the lock).
# File activesupport/lib/active_support/concurrency/share_lock.rb, line 62 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 @cv.wait_while { @exclusive_thread || eligible_waiters?(compatible) } end end @cv.broadcast end end end