class Garcon::ReadWriteLock

read-write Lock for cooking safety.

Allows any number of concurrent readers, but only one concurrent writer (And if the “write” lock is taken, any readers who come along will have to wait). If readers are already active when a writer comes along, the writer will wait for all the readers to finish before going ahead. Any additional readers that come when the writer is already waiting, will also wait (so writers are not starved).

@example

lock = Garcon::ReadWriteLock.new
lock.with_read_lock  { data.retrieve }
lock.with_write_lock { data.modify! }

@note

Do **not** try to acquire the write lock while already holding a read lock
**or** try to acquire the write lock while you already have it.
This will lead to deadlock

Constants

MAX_READERS

@!visibility private

MAX_WRITERS

@!visibility private

RUNNING_WRITER

@!visibility private

WAITING_WRITER

@!visibility private

Public Class Methods

new() click to toggle source

Create a new ‘ReadWriteLock` in the unlocked state.

# File lib/garcon/task/read_write_lock.rb, line 72
def initialize
  @counter      = AtomicMutex.new(0)    # represents lock state
  @reader_q     = ConditionVariable.new # queue for waiting readers
  @reader_mutex = Mutex.new             # to protect reader queue
  @writer_q     = ConditionVariable.new # queue for waiting writers
  @writer_mutex = Mutex.new             # to protect writer queue
end

Public Instance Methods

acquire_read_lock() click to toggle source

Acquire a read lock. If a write lock has been acquired will block until it is released. Will not block if other read locks have been acquired.

@return [Boolean]

True if the lock is successfully acquired.

@raise [Garcon::ResourceLimitError]

If the maximum number of readers is exceeded.
# File lib/garcon/task/read_write_lock.rb, line 134
def acquire_read_lock
  while(true)
    c = @counter.value
    raise ResourceLimitError, 'Too many reader threads' if max_readers?(c)

    if waiting_writer?(c)
      @reader_mutex.synchronize do
        @reader_q.wait(@reader_mutex) if waiting_writer?
      end

      while(true)
        c = @counter.value
        if running_writer?(c)
          @reader_mutex.synchronize do
            @reader_q.wait(@reader_mutex) if running_writer?
          end
        else
          return if @counter.compare_and_swap(c,c+1)
        end
      end
    else
      break if @counter.compare_and_swap(c,c+1)
    end
  end
  true
end
acquire_write_lock() click to toggle source

Acquire a write lock. Will block and wait for all active readers and writers.

@return [Boolean]

True if the lock is successfully acquired.

@raise [Garcon::ResourceLimitError]

If the maximum number of writers is exceeded.
# File lib/garcon/task/read_write_lock.rb, line 188
def acquire_write_lock
  while(true)
    c = @counter.value
    raise ResourceLimitError, 'Too many writer threads' if max_writers?(c)

    if c == 0
      break if @counter.compare_and_swap(0,RUNNING_WRITER)
    elsif @counter.compare_and_swap(c,c+WAITING_WRITER)
      while(true)
        @writer_mutex.synchronize do
          c = @counter.value
          if running_writer?(c) || running_readers?(c)
            @writer_q.wait(@writer_mutex)
          end
        end

        c = @counter.value
        break if !running_writer?(c) && !running_readers?(c) &&
          @counter.compare_and_swap(c,c+RUNNING_WRITER-WAITING_WRITER)
      end
      break
    end
  end
  true
end
has_waiters?() click to toggle source

Queries whether any threads are waiting to acquire the read or write lock.

@return [Boolean]

True if any threads are waiting for a lock else false.
# File lib/garcon/task/read_write_lock.rb, line 263
def has_waiters?
  waiting_writer?(@counter.value)
end
release_read_lock() click to toggle source

Release a previously acquired read lock.

@return [Boolean]

True if the lock is successfully released.
# File lib/garcon/task/read_write_lock.rb, line 166
def release_read_lock
  while(true)
    c = @counter.value
    if @counter.compare_and_swap(c,c-1)
      if waiting_writer?(c) && running_readers(c) == 1
        @writer_mutex.synchronize { @writer_q.signal }
      end
      break
    end
  end
  true
end
release_write_lock() click to toggle source

Release a previously acquired write lock.

@return [Boolean]

True if the lock is successfully released.
# File lib/garcon/task/read_write_lock.rb, line 219
def release_write_lock
  while(true)
    c = @counter.value
    if @counter.compare_and_swap(c,c-RUNNING_WRITER)
      @reader_mutex.synchronize { @reader_q.broadcast }
      if waiting_writers(c) > 0
        @writer_mutex.synchronize { @writer_q.signal }
      end
      break
    end
  end
  true
end
to_s() click to toggle source

Returns a string representing obj. Includes the current reader and writer counts.

# File lib/garcon/task/read_write_lock.rb, line 236
def to_s
  c = @counter.value
  s = if running_writer?(c)
        "1 writer running, "
      elsif running_readers(c) > 0
        "#{running_readers(c)} readers running, "
      else
        ""
      end

  "#<ReadWriteLock:#{object_id.to_s(16)} #{s}#{waiting_writers(c)} writers waiting>"
end
with_read_lock() { || ... } click to toggle source

Execute a block operation within a read lock.

@return [Object]

The result of the block operation.

@yield the task to be performed within the lock.

@raise [ArgumentError]

When no block is given.

@raise [Garcon::ResourceLimitError]

If the maximum number of readers is exceeded.
# File lib/garcon/task/read_write_lock.rb, line 93
def with_read_lock
  raise ArgumentError, 'no block given' unless block_given?
  acquire_read_lock
  begin
    yield
  ensure
    release_read_lock
  end
end
with_write_lock() { || ... } click to toggle source

Execute a block operation within a write lock.

@return [Object] the result of the block operation.

@yield the task to be performed within the lock.

@raise [ArgumentError]

When no block is given.

@raise [Garcon::ResourceLimitError]

If the maximum number of readers is exceeded.
# File lib/garcon/task/read_write_lock.rb, line 115
def with_write_lock
  raise ArgumentError, 'no block given' unless block_given?
  acquire_write_lock
  begin
    yield
  ensure
    release_write_lock
  end
end
write_locked?() click to toggle source

Queries if the write lock is held by any thread.

@return [Boolean]

True if the write lock is held else false.
# File lib/garcon/task/read_write_lock.rb, line 254
def write_locked?
  @counter.value >= RUNNING_WRITER
end