Merge pull request #12 from ms-ati/add-semaphore-to-mtx

Add Semaphore#to_mtx for a faster but unchecked mutex-like facade
This commit is contained in:
pmahoney 2013-12-31 06:54:33 -08:00
commit fb0da5ee8c
2 changed files with 112 additions and 5 deletions

View File

@ -1,3 +1,5 @@
require 'forwardable'
module ProcessShared
module SynchronizableSemaphore
# Yield the block after decrementing the semaphore, ensuring that
@ -12,5 +14,72 @@ module ProcessShared
post
end
end
# Expose an unchecked mutex-like interface using only this semaphore.
#
# @return [FasterUncheckedMutex] An unchecked mutex facade for this semaphore
def to_mtx
FasterUncheckedMutex.new(self)
end
private
# @api private
#
# Presents a mutex-like facade over a semaphore.
# @see SynchronizableSemaphore#to_mtx
#
# NOTE: Unlocking a locked mutex from a different process or thread than
# that which locked it will result in undefined behavior, whereas with the
# Mutex class, this error will be detected and an exception raised.
#
# It is recommended to develop using the Mutex class, which is checked, and
# to use this unchecked variant only to optimized performance for code paths
# that have been determined to have correct lock/unlock behavior.
class FasterUncheckedMutex
extend Forwardable
def initialize(sem)
@sem = sem
end
# @return [Boolean] +true+ if currently locked
def locked?
acquired = try_lock
unlock if acquired
!acquired
end
# Releases the lock and sleeps timeout seconds if it is given and
# non-nil or forever.
#
# TODO: de-duplicate this from Mutex#sleep
#
# @return [Numeric]
def sleep(timeout = nil)
unlock
begin
timeout ? Kernel.sleep(timeout) : Kernel.sleep
ensure
lock
end
end
# @return [Boolean] +true+ if lock was acquired, +false+ if already locked
def try_lock
@sem.try_wait
true
rescue Errno::EAGAIN
false
end
# delegate to methods with different names
def_delegator :@sem, :wait, :lock
def_delegator :@sem, :post, :unlock
# delegate to methods with the same names
def_delegators :@sem, :synchronize, :close
end
end
end

View File

@ -11,11 +11,7 @@ module ProcessShared
include LockBehavior
before :each do
@lock = Semaphore.new
class << @lock
alias_method :lock, :wait
alias_method :unlock, :post
end
@lock = Semaphore.new.to_mtx
end
after :each do
@ -191,5 +187,47 @@ module ProcessShared
end
end
end
describe '#to_mtx' do
before :each do
@mtx = Semaphore.new.to_mtx
end
# NOTE:
# - #lock / #unlock covered by LockingBehavior above
# - #synchronize covered elsewhere as well?
describe '#locked?' do
it 'returns true when locked' do
@mtx.synchronize { @mtx.locked?.must_equal true }
end
it 'returns false when not locked' do
@mtx.locked?.must_equal false
end
it 'does not itself acquire lock' do
@mtx.locked?.must_equal false
@mtx.locked?.must_equal false # check again to make sure lock not acquired
end
end
describe '#sleep' do
# TODO: add tests for #sleep
end
describe '#try_lock' do
it 'returns true and acquires lock when unlocked' do
@mtx.try_lock.must_equal true
@mtx.locked?.must_equal true
@mtx.unlock
end
it 'returns false when already locked' do
@mtx.synchronize { @mtx.try_lock.must_equal false }
end
end
end
end
end