From c37d47e30897762145835e66ae752cce924af01d Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Tue, 30 Sep 2014 01:32:42 +0930 Subject: Soften the lock requirements when eager_load is disabled We don't need to fully disable concurrent requests: just ensure that loads are performed in isolation. --- .../lib/active_support/concurrency/share_lock.rb | 118 +++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 activesupport/lib/active_support/concurrency/share_lock.rb (limited to 'activesupport/lib/active_support/concurrency') diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb new file mode 100644 index 0000000000..e03f2cfc7a --- /dev/null +++ b/activesupport/lib/active_support/concurrency/share_lock.rb @@ -0,0 +1,118 @@ +require 'thread' +require 'monitor' + +module ActiveSupport + module Concurrency + class ShareLock + include 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. + + + # If +loose_upgrades+ is false (the default), then a thread that + # is waiting on an Exclusive lock will continue to hold any Share + # lock that it has already established. This is safer, but can + # lead to deadlock. + # + # If +loose_upgrades+ is true, a thread waiting on an Exclusive + # lock will temporarily relinquish its Share lock. Being less + # strict, this behavior 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. + attr_reader :loose_upgrades + + def initialize(loose_upgrades = false) + @loose_upgrades = loose_upgrades + + super() + + @cv = new_cond + + @sharing = Hash.new(0) + @exclusive_thread = nil + @exclusive_depth = 0 + end + + def start_exclusive(no_wait=false) + synchronize do + unless @exclusive_thread == Thread.current + return false if no_wait && busy? + + loose_shares = nil + if @loose_upgrades + loose_shares = @sharing.delete(Thread.current) + end + + @cv.wait_while { busy? } if busy? + + @exclusive_thread = Thread.current + @sharing[Thread.current] = loose_shares if loose_shares + end + @exclusive_depth += 1 + + true + end + end + + def stop_exclusive + synchronize do + raise "invalid unlock" if @exclusive_thread != Thread.current + + @exclusive_depth -= 1 + if @exclusive_depth == 0 + @exclusive_thread = nil + @cv.broadcast + end + end + end + + def start_sharing + synchronize do + if @exclusive_thread && @exclusive_thread != Thread.current + @cv.wait_while { @exclusive_thread } + end + @sharing[Thread.current] += 1 + end + end + + def stop_sharing + synchronize do + if @sharing[Thread.current] > 1 + @sharing[Thread.current] -= 1 + else + @sharing.delete Thread.current + @cv.broadcast + end + end + end + + def exclusive(no_wait=false) + if start_exclusive(no_wait) + begin + yield + ensure + stop_exclusive + end + end + end + + def sharing + start_sharing + begin + yield + ensure + stop_sharing + end + end + + private + + def busy? + (@exclusive_thread && @exclusive_thread != Thread.current) || + @sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0) + end + end + end +end -- cgit v1.2.3