class ReadWriteLock

Constants

MAX_READERS
MAX_WRITERS
RUNNING_WRITER
WAITING_WRITER

Public Class Methods

new() click to toggle source
# File lib/volt/utils/read_write_lock.rb, line 43
def initialize
  @counter      = Concurrent::Atomic.new(0)         # single integer which 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
# File lib/volt/utils/read_write_lock.rb, line 70
def acquire_read_lock
  loop do
    c = @counter.value
    fail 'Too many reader threads!' if (c & MAX_READERS) == MAX_READERS

    # If a writer is waiting when we first queue up, we need to wait
    if c >= WAITING_WRITER
      # But it is possible that the writer could finish and decrement @counter right here...
      @reader_mutex.synchronize do
        # So check again inside the synchronized section
        @reader_q.wait(@reader_mutex) if @counter.value >= WAITING_WRITER
      end

      # after a reader has waited once, they are allowed to "barge" ahead of waiting writers
      # but if a writer is *running*, the reader still needs to wait (naturally)
      loop do
        c = @counter.value
        if c >= RUNNING_WRITER
          @reader_mutex.synchronize do
            @reader_q.wait(@reader_mutex) if @counter.value >= 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
end
acquire_write_lock() click to toggle source
# File lib/volt/utils/read_write_lock.rb, line 114
def acquire_write_lock
  loop do
    c = @counter.value
    fail 'Too many writers!' if (c & MAX_WRITERS) == MAX_WRITERS

    if c == 0 # no readers OR writers running
      # if we successfully swap the RUNNING_WRITER bit on, then we can go ahead
      break if @counter.compare_and_swap(0, RUNNING_WRITER)
    elsif @counter.compare_and_swap(c, c + WAITING_WRITER)
      loop do
        # Now we have successfully incremented, so no more readers will be able to increment
        #   (they will wait instead)
        # However, readers OR writers could decrement right here, OR another writer could increment
        @writer_mutex.synchronize do
          # So we have to do another check inside the synchronized section
          # If a writer OR reader is running, then go to sleep
          c = @counter.value
          @writer_q.wait(@writer_mutex) if (c >= RUNNING_WRITER) || ((c & MAX_READERS) > 0)
        end

        # We just came out of a wait
        # If we successfully turn the RUNNING_WRITER bit on with an atomic swap,
        # Then we are OK to stop waiting and go ahead
        # Otherwise go back and wait again
        c = @counter.value
        break if (c < RUNNING_WRITER) &&
                 ((c & MAX_READERS) == 0) &&
                 @counter.compare_and_swap(c, c + RUNNING_WRITER - WAITING_WRITER)
      end
      break
    end
  end
end
release_read_lock() click to toggle source
# File lib/volt/utils/read_write_lock.rb, line 101
def release_read_lock
  loop do
    c = @counter.value
    if @counter.compare_and_swap(c, c - 1)
      # If one or more writers were waiting, and we were the last reader, wake a writer up
      if c >= WAITING_WRITER && (c & MAX_READERS) == 1
        @writer_mutex.synchronize { @writer_q.signal }
      end
      break
    end
  end
end
release_write_lock() click to toggle source
# File lib/volt/utils/read_write_lock.rb, line 148
def release_write_lock
  loop do
    c = @counter.value
    if @counter.compare_and_swap(c, c - RUNNING_WRITER)
      @reader_mutex.synchronize { @reader_q.broadcast }
      @writer_mutex.synchronize { @writer_q.signal } if (c & MAX_WRITERS) > 0 # if any writers are waiting...
      break
    end
  end
end
to_s() click to toggle source
# File lib/volt/utils/read_write_lock.rb, line 159
def to_s
  c = @counter.value
  s = if c >= RUNNING_WRITER
        '1 writer running, '
      elsif (c & MAX_READERS) > 0
        "#{c & MAX_READERS} readers running, "
      else
        ''
  end

  "#<ReadWriteLock:#{object_id.to_s(16)} #{s}#{(c & MAX_WRITERS) / WAITING_WRITER} writers waiting>"
end
with_read_lock() { || ... } click to toggle source
# File lib/volt/utils/read_write_lock.rb, line 56
def with_read_lock
  acquire_read_lock
  result = yield
  release_read_lock
  result
end
with_write_lock() { || ... } click to toggle source
# File lib/volt/utils/read_write_lock.rb, line 63
def with_write_lock
  acquire_write_lock
  result = yield
  release_write_lock
  result
end