Class: ReadWriteLock

Inherits:
Object show all
Defined in:
lib/volt/utils/read_write_lock.rb

Constant Summary collapse

WAITING_WRITER =
1 << 15
RUNNING_WRITER =
1 << 30
MAX_READERS =
WAITING_WRITER - 1
MAX_WRITERS =
RUNNING_WRITER - MAX_READERS - 1

Instance Method Summary collapse

Constructor Details

#initializeReadWriteLock

Returns a new instance of ReadWriteLock.



43
44
45
46
47
48
49
# 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

Instance Method Details

#acquire_read_lockObject



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# 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_lockObject



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# 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_lockObject



101
102
103
104
105
106
107
108
109
110
111
112
# 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_lockObject



148
149
150
151
152
153
154
155
156
157
# 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_sObject



159
160
161
162
163
164
165
166
167
168
169
170
# 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_lockObject



56
57
58
59
60
61
# 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_lockObject



63
64
65
66
67
68
# File 'lib/volt/utils/read_write_lock.rb', line 63

def with_write_lock
  acquire_write_lock
  result = yield
  release_write_lock
  result
end