Class: Garcon::ReadWriteLock

Inherits:
Object
  • Object
show all
Defined in:
lib/garcon/task/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

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

Examples:

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

Instance Method Summary collapse

Constructor Details

#initializeReadWriteLock

Create a new ‘ReadWriteLock` in the unlocked state.



72
73
74
75
76
77
78
# 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

Instance Method Details

#acquire_read_lockBoolean

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:

  • (Garcon::ResourceLimitError)

    If the maximum number of readers is exceeded.



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# 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_lockBoolean

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

Returns:

  • (Boolean)

    True if the lock is successfully acquired.

Raises:

  • (Garcon::ResourceLimitError)

    If the maximum number of writers is exceeded.



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# 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?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.



263
264
265
# File 'lib/garcon/task/read_write_lock.rb', line 263

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

#release_read_lockBoolean

Release a previously acquired read lock.

Returns:

  • (Boolean)

    True if the lock is successfully released.



166
167
168
169
170
171
172
173
174
175
176
177
# 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_lockBoolean

Release a previously acquired write lock.

Returns:

  • (Boolean)

    True if the lock is successfully released.



219
220
221
222
223
224
225
226
227
228
229
230
231
# 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_sObject

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



236
237
238
239
240
241
242
243
244
245
246
247
# 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 { ... } ⇒ 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.

  • (Garcon::ResourceLimitError)

    If the maximum number of readers is exceeded.



93
94
95
96
97
98
99
100
101
# 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 { ... } ⇒ 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.

  • (Garcon::ResourceLimitError)

    If the maximum number of readers is exceeded.



115
116
117
118
119
120
121
122
123
# 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?Boolean

Queries if the write lock is held by any thread.

Returns:

  • (Boolean)

    True if the write lock is held else false.



254
255
256
# File 'lib/garcon/task/read_write_lock.rb', line 254

def write_locked?
  @counter.value >= RUNNING_WRITER
end