# Class: Concurrent::ReadWriteLock

Inherits:
  Synchronization::Object
  - Object
  - Synchronization::Object
  - Concurrent::ReadWriteLock
  show all

Defined in:
  lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb

## Overview

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

Ruby read-write lock implementation

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).

This implementation is based on `java.util.concurrent.ReentrantReadWriteLock`.

Examples:

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

See Also:

- java.util.concurrent.ReentrantReadWriteLock

## Instance Method Summary collapse

- #acquire_read_lock ⇒ Boolean

  Acquire a read lock.

- #acquire_write_lock ⇒ Boolean

  Acquire a write lock.

- #has_waiters? ⇒ Boolean

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

- #initialize ⇒ ReadWriteLock constructor

  Create a new `ReadWriteLock` in the unlocked state.

- #release_read_lock ⇒ Boolean

  Release a previously acquired read lock.

- #release_write_lock ⇒ Boolean

  Release a previously acquired write lock.

- #with_read_lock { ... } ⇒ Object

  Execute a block operation within a read lock.

- #with_write_lock { ... } ⇒ Object

  Execute a block operation within a write lock.

- #write_locked? ⇒ Boolean

  Queries if the write lock is held by any thread.

## Constructor Details

### #initialize ⇒ `ReadWriteLock`

Create a new `ReadWriteLock` in the unlocked state.

    58
    59
    60
    61
    62
    63

    # File 'lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb', line 58

    def initialize
      super()
      @Counter   = AtomicFixnum.new(0) # single integer which represents lock state
      @ReadLock  = Synchronization::Lock.new
      @WriteLock = Synchronization::Lock.new
    end

## Instance Method Details

### #acquire_read_lock ⇒ `Boolean`

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.

Returns:

- (`Boolean`) —

  true if the lock is successfully acquired

Raises:

- (`Concurrent::ResourceLimitError`) —

  if the maximum number of readers is exceeded.

    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134

    # File 'lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb', line 110

    def acquire_read_lock
      while true
        c = @Counter.value
        raise ResourceLimitError.new('Too many reader threads') if max_readers?(c)

        # If a writer is waiting when we first queue up, we need to wait
        if waiting_writer?(c)
          @ReadLock.wait_until { !waiting_writer? }

          # 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)
          while true
            c = @Counter.value
            if running_writer?(c)
              @ReadLock.wait_until { !running_writer? }
            else
              return if @Counter.compare_and_set(c, c+1)
            end
          end
        else
          break if @Counter.compare_and_set(c, c+1)
        end
      end
      true
    end

### #acquire_write_lock ⇒ `Boolean`

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

Returns:

- (`Boolean`) —

  true if the lock is successfully acquired

Raises:

- (`Concurrent::ResourceLimitError`) —

  if the maximum number of writers is exceeded.

    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190

    # File 'lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb', line 159

    def acquire_write_lock
      while true
        c = @Counter.value
        raise ResourceLimitError.new('Too many writer threads') if max_writers?(c)

        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_set(0, RUNNING_WRITER)
        elsif @Counter.compare_and_set(c, c+WAITING_WRITER)
          while true
            # 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
            @WriteLock.wait_until 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
              !running_writer?(c) && !running_readers?(c)
            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 !running_writer?(c) && !running_readers?(c) && @Counter.compare_and_set(c, c+RUNNING_WRITER-WAITING_WRITER)
          end
          break
        end
      end
      true
    end

### #has_waiters? ⇒ `Boolean`

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

Returns:

- (`Boolean`) —

  true if any threads are waiting for a lock else false

    213
    214
    215

    # File 'lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb', line 213

    def has_waiters?
      waiting_writer?(@Counter.value)
    end

### #release_read_lock ⇒ `Boolean`

Release a previously acquired read lock.

Returns:

- (`Boolean`) —

  true if the lock is successfully released

    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151

    # File 'lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb', line 139

    def release_read_lock
      while true
        c = @Counter.value
        if @Counter.compare_and_set(c, c-1)
          # If one or more writers were waiting, and we were the last reader, wake a writer up
          if waiting_writer?(c) && running_readers(c) == 1
            @WriteLock.signal
          end
          break
        end
      end
      true
    end

### #release_write_lock ⇒ `Boolean`

Release a previously acquired write lock.

Returns:

- (`Boolean`) —

  true if the lock is successfully released

    195
    196
    197
    198
    199
    200
    201

    # File 'lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb', line 195

    def release_write_lock
      return true unless running_writer?
      c = @Counter.update { |counter| counter - RUNNING_WRITER }
      @ReadLock.broadcast
      @WriteLock.signal if waiting_writers(c) > 0
      true
    end

### #with_read_lock { ... } ⇒ `Object`

Execute a block operation within a read lock.

Yields:

- the task to be performed within the lock.

Returns:

- (`Object`) —

  the result of the block operation.

Raises:

- (`ArgumentError`) —

  when no block is given.

- (`Concurrent::ResourceLimitError`) —

  if the maximum number of readers is exceeded.

    74
    75
    76
    77
    78
    79
    80
    81
    82

    # File 'lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb', line 74

    def with_read_lock
      raise ArgumentError.new('no block given') unless block_given?
      acquire_read_lock
      begin
        yield
      ensure
        release_read_lock
      end
    end

### #with_write_lock { ... } ⇒ `Object`

Execute a block operation within a write lock.

Yields:

- the task to be performed within the lock.

Returns:

- (`Object`) —

  the result of the block operation.

Raises:

- (`ArgumentError`) —

  when no block is given.

- (`Concurrent::ResourceLimitError`) —

  if the maximum number of readers is exceeded.

    93
    94
    95
    96
    97
    98
    99
    100
    101

    # File 'lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb', line 93

    def with_write_lock
      raise ArgumentError.new('no block given') unless block_given?
      acquire_write_lock
      begin
        yield
      ensure
        release_write_lock
      end
    end

### #write_locked? ⇒ `Boolean`

Queries if the write lock is held by any thread.

Returns:

- (`Boolean`) —

  true if the write lock is held else false`

    206
    207
    208

    # File 'lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb', line 206

    def write_locked?
      @Counter.value >= RUNNING_WRITER
    end