178 lines
3.7 KiB
Ruby
178 lines
3.7 KiB
Ruby
# -*- coding: binary -*-
|
|
require 'thread'
|
|
|
|
module Rex
|
|
|
|
###
|
|
#
|
|
# This class implements a read/write lock synchronization
|
|
# primitive. It is meant to allow for more efficient access to
|
|
# resources that are more often read from than written to and many
|
|
# times can have concurrent reader threads. By allowing the reader
|
|
# threads to lock the resource concurrently rather than serially,
|
|
# a large performance boost can be seen. Acquiring a write lock
|
|
# results in exclusive access to the resource and thereby prevents
|
|
# any read operations during the time that a write lock is acquired.
|
|
# Only one write lock may be acquired at a time.
|
|
#
|
|
###
|
|
class ReadWriteLock
|
|
|
|
#
|
|
# Initializes a reader/writer lock instance.
|
|
#
|
|
def initialize
|
|
@read_sync_mutex = Mutex.new
|
|
@write_sync_mutex = Mutex.new
|
|
@exclusive_mutex = Mutex.new
|
|
@readers = 0
|
|
@writer = false
|
|
end
|
|
|
|
#
|
|
# Acquires the read lock for the calling thread.
|
|
#
|
|
def lock_read
|
|
read_sync_mutex.lock
|
|
|
|
begin
|
|
# If there are a non-zero number of readers and a
|
|
# writer is waiting to acquire the exclusive lock,
|
|
# free up the sync mutex temporarily and lock/unlock
|
|
# the exclusive lock. This is to give the writer
|
|
# thread a chance to acquire the lock and prevents
|
|
# it from being constantly starved.
|
|
if ((@readers > 0) and
|
|
(@writer))
|
|
read_sync_mutex.unlock
|
|
exclusive_mutex.lock
|
|
exclusive_mutex.unlock
|
|
read_sync_mutex.lock
|
|
end
|
|
|
|
# Increment the active reader count
|
|
@readers += 1
|
|
|
|
# If we now have just one reader, acquire the exclusive
|
|
# lock. Track the thread owner so that we release the
|
|
# lock from within the same thread context later on.
|
|
if (@readers == 1)
|
|
exclusive_mutex.lock
|
|
|
|
@owner = Thread.current
|
|
end
|
|
ensure
|
|
read_sync_mutex.unlock
|
|
end
|
|
end
|
|
|
|
#
|
|
# Releases the read lock for the calling thread.
|
|
#
|
|
def unlock_read
|
|
read_sync_mutex.lock
|
|
|
|
begin
|
|
unlocked = false
|
|
|
|
# Keep looping until we've lost this thread's reader
|
|
# lock
|
|
while (!unlocked)
|
|
# If there are no more readers left after this one
|
|
if (@readers - 1 == 0)
|
|
# If the calling thread is the owner of the exclusive
|
|
# reader lock, then let's release it
|
|
if (Thread.current == @owner)
|
|
@owner = nil
|
|
|
|
exclusive_mutex.unlock
|
|
end
|
|
# If there is more than one reader left and this thread is
|
|
# the owner of the exclusive lock, then keep looping so that
|
|
# we can eventually unlock the exclusive mutex in this thread's
|
|
# context
|
|
elsif (Thread.current == @owner)
|
|
read_sync_mutex.unlock
|
|
|
|
next
|
|
end
|
|
|
|
# Unlocked!
|
|
unlocked = true
|
|
|
|
# Decrement the active reader count
|
|
@readers -= 1
|
|
end
|
|
ensure
|
|
read_sync_mutex.unlock
|
|
end
|
|
end
|
|
|
|
#
|
|
# Acquire the exclusive write lock.
|
|
#
|
|
def lock_write
|
|
write_sync_mutex.lock
|
|
|
|
begin
|
|
@writer = true
|
|
|
|
exclusive_mutex.lock
|
|
|
|
@owner = Thread.current
|
|
ensure
|
|
write_sync_mutex.unlock
|
|
end
|
|
end
|
|
|
|
#
|
|
# Release the exclusive write lock.
|
|
#
|
|
def unlock_write
|
|
# If the caller is not the owner of the write lock, then someone is
|
|
# doing something broken, let's let them know.
|
|
if (Thread.current != @owner)
|
|
raise RuntimeError, "Non-owner calling thread attempted to release write lock", caller
|
|
end
|
|
|
|
# Otherwise, release the exclusive write lock
|
|
@writer = false
|
|
|
|
exclusive_mutex.unlock
|
|
end
|
|
|
|
#
|
|
# Synchronize a block for read access.
|
|
#
|
|
def synchronize_read
|
|
lock_read
|
|
begin
|
|
yield
|
|
ensure
|
|
unlock_read
|
|
end
|
|
end
|
|
|
|
#
|
|
# Synchronize a block for write access.
|
|
#
|
|
def synchronize_write
|
|
lock_write
|
|
begin
|
|
yield
|
|
ensure
|
|
unlock_write
|
|
end
|
|
end
|
|
|
|
protected
|
|
|
|
attr_accessor :read_sync_mutex # :nodoc:
|
|
attr_accessor :write_sync_mutex # :nodoc:
|
|
attr_accessor :exclusive_mutex # :nodoc:
|
|
|
|
end
|
|
|
|
end
|
|
|