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:
commit
fb0da5ee8c
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue