concurrent-ruby 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 06e8ac2ad12cfbd2d70c6d8d687b2a9e338a9a31
4
- data.tar.gz: 00438fa26d2bd287644b0dccd7b4a31a17e350cd
3
+ metadata.gz: 1e6abc5183c2dabc1d3bda8b2cade00544f9dbf0
4
+ data.tar.gz: b119ca76d4aabf27a7361f58a6f7fc890894e846
5
5
  SHA512:
6
- metadata.gz: c6f70cef24b30511a49b2e0b348b9b291bc5c53505babfb3665b35f816aec3877fbca1c5e84c5383c78be65c26ae6b99947ea94fcc9e1c88ffebcad6f8f7ec54
7
- data.tar.gz: b23f9e760e1d7c079a1e467181d796b65366b6d068cbf797cbec1937f42e8919825f2515d25c23ea04de88750229698019b85c6dc0f204aa3419d3b720f339a8
6
+ metadata.gz: dc9406ff279405553d5050f637136f003fff9985f107686fbd861c43f7eced5a55bb8ef80e4a5710aa86af0dfbe1cd701116c75aa21a48429052080f90ba4ebd
7
+ data.tar.gz: 8e5a58bb8e30d7ae10fc13d8b41cc5e960241991ca63a36a1c6c35e9daeb85222576ce6073374b24940268d5fb3f01c51139aa20480cbe19f691517237466eaa
@@ -1,4 +1,18 @@
1
- ## Current Release v0.9.0 (10 July 2015)
1
+ ## Current Release v0.9.1 (09 August 2015)
2
+
3
+ * Fixed a Rubiniux bug in synchronization object
4
+ * Fixed all interpreter warnings (except circular references)
5
+ * Fixed require statements when requiring `Atom` alone
6
+ * Significantly improved `ThreadLocalVar` on non-JRuby platforms
7
+ * Fixed error handling in Edge `Concurrent.zip`
8
+ * `AtomicFixnum` methods `#increment` and `#decrement` now support optional delta
9
+ * New `AtomicFixnum#update` method
10
+ * Minor optimizations in `ReadWriteLock`
11
+ * New `ReentrantReadWriteLock` class
12
+ * `ThreadLocalVar#bind` method is now public
13
+ * Refactored many tests
14
+
15
+ ### Release v0.9.0 (10 July 2015)
2
16
 
3
17
  * Updated `AtomicReference`
4
18
  - `AtomicReference#try_update` now simply returns instead of raising exception
data/README.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Concurrent Ruby
2
- [![Gem Version](https://badge.fury.io/rb/concurrent-ruby.svg)](http://badge.fury.io/rb/concurrent-ruby) [![Build Status](https://travis-ci.org/ruby-concurrency/concurrent-ruby.svg?branch=master)](https://travis-ci.org/ruby-concurrency/concurrent-ruby) [![Build status](https://ci.appveyor.com/api/projects/status/iq8aboyuu3etad4w?svg=true)](https://ci.appveyor.com/project/rubyconcurrency/concurrent-ruby) [![Code Climate](https://codeclimate.com/github/ruby-concurrency/concurrent-ruby.svg)](https://codeclimate.com/github/ruby-concurrency/concurrent-ruby) [![Inline docs](http://inch-ci.org/github/ruby-concurrency/concurrent-ruby.svg)](http://inch-ci.org/github/ruby-concurrency/concurrent-ruby) [![Dependency Status](https://gemnasium.com/ruby-concurrency/concurrent-ruby.svg)](https://gemnasium.com/ruby-concurrency/concurrent-ruby) [![License](https://img.shields.io/badge/license-MIT-green.svg)](http://opensource.org/licenses/MIT) [![Gitter chat](http://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/ruby-concurrency/concurrent-ruby)
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/concurrent-ruby.svg)](http://badge.fury.io/rb/concurrent-ruby)
4
+ [![Build Status](https://travis-ci.org/ruby-concurrency/concurrent-ruby.svg?branch=master)](https://travis-ci.org/ruby-concurrency/concurrent-ruby)
5
+ [![Build status](https://ci.appveyor.com/api/projects/status/iq8aboyuu3etad4w?svg=true)](https://ci.appveyor.com/project/rubyconcurrency/concurrent-ruby)
6
+ [![Code Climate](https://codeclimate.com/github/ruby-concurrency/concurrent-ruby.svg)](https://codeclimate.com/github/ruby-concurrency/concurrent-ruby)
7
+ [![Inline docs](http://inch-ci.org/github/ruby-concurrency/concurrent-ruby.svg)](http://inch-ci.org/github/ruby-concurrency/concurrent-ruby)
8
+ [![Dependency Status](https://gemnasium.com/ruby-concurrency/concurrent-ruby.svg)](https://gemnasium.com/ruby-concurrency/concurrent-ruby)
9
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](http://opensource.org/licenses/MIT)
10
+ [![Gitter chat](https://img.shields.io/badge/IRC%20(gitter)-devs%20%26%20users-brightgreen.svg)](https://gitter.im/ruby-concurrency/concurrent-ruby)
3
11
 
4
12
  <table>
5
13
  <tr>
@@ -43,13 +51,13 @@ Java 8 is required for JRuby (Java 7 support is deprecated in version 0.9 and wi
43
51
 
44
52
  ## Features & Documentation
45
53
 
46
- We have a roadmap guiding our work toward the [v1.0.0 release](https://github.com/ruby-concurrency/concurrent-ruby/wiki/v1.0-Roadmap).
54
+ We have a roadmap guiding our work toward the [v1.0.0 release](https://github.com/ruby-concurrency/concurrent-ruby/issues/257).
47
55
 
48
56
  The primary site for documentation is the automatically generated [API documentation](http://ruby-concurrency.github.io/concurrent-ruby/frames.html)
49
57
 
50
- We also have a [mailing list](http://groups.google.com/group/concurrent-ruby).
58
+ We also have a [mailing list](http://groups.google.com/group/concurrent-ruby) and [IRC (gitter)](https://gitter.im/ruby-concurrency/concurrent-ruby).
51
59
 
52
- This library contains a variety of concurrency abstractions at high and low levels. One of the high-level abstractions is likely to meet most common needs.
60
+ This library contains a variety of concurrency abstractions at high and low levels. One of the high-level abstractions is likely to meet most common needs.
53
61
 
54
62
  #### General-purpose Concurrency Abstractions
55
63
 
@@ -59,7 +67,7 @@ This library contains a variety of concurrency abstractions at high and low leve
59
67
  * [Dataflow](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent.html#dataflow-class_method): Built on Futures, Dataflow allows you to create a task that will be scheduled when all of its data dependencies are available.
60
68
  * [Promise](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Promise.html): Similar to Futures, with more features.
61
69
  * [ScheduledTask](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ScheduledTask.html): Like a Future scheduled for a specific future time.
62
- * [TimerTask](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/TimerTask.html): A Thread that periodically wakes up to perform work at regular intervals.
70
+ * [TimerTask](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/TimerTask.html): A Thread that periodically wakes up to perform work at regular intervals.
63
71
 
64
72
  #### Thread-safe Value Objects
65
73
 
@@ -97,6 +105,7 @@ Derived from Ruby's [Struct](http://ruby-doc.org/core-2.2.0/Struct.html):
97
105
  * [Thread-local variables](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadLocalVar.html)
98
106
  * [Software transactional memory](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/TVar.html) (TVar)
99
107
  * [ReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ReadWriteLock.html)
108
+ * [ReentrantReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ReentrantReadWriteLock.html)
100
109
 
101
110
  ### Edge Features
102
111
 
@@ -108,34 +117,31 @@ be obeyed though. Features developed in `concurrent-ruby-edge` are expected to m
108
117
 
109
118
  * [Actor](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Actor.html):
110
119
  Implements the Actor Model, where concurrent actors exchange messages.
111
- * [new Future Framework](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge.html) - new
120
+ * [new Future Framework](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/FutureShortcuts.html) - new
112
121
  unified implementation of Futures and Promises which combines Features of previous `Future`,
113
122
  `Promise`, `IVar`, `Event`, `Probe`, `dataflow`, `Delay`, `TimerTask` into single framework. It uses extensively
114
- new synchronization layer to make all the paths **lock-free** with exception of blocking threads on `#wait`.
115
- It offers better performance and does not block threads when not required.
123
+ new synchronization layer to make all the features **non-blocking** and **lock-free** with exception of obviously blocking
124
+ operations like `#wait`, `#value`. It also offers better performance.
116
125
  * [Agent](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Agent.html): A single atomic value that represents an identity.
117
126
  * [Channel](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Channel.html):
118
127
  Communicating Sequential Processes (CSP).
119
128
  * [Exchanger](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Exchanger.html)
120
129
  * [LazyRegister](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/LazyRegister.html)
121
- * [New Future Promise Framework](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge.html) - new
122
- unified implementation of Futures and Promises which combines Features of previous `Future`,
123
- `Promise`, `IVar`, `Probe`, `dataflow`, `Delay`, `TimerTask` into single framework. It uses extensively
124
- new synchronization layer to make all the paths lock-free with exception of blocking threads on `#wait`.
125
- It offers better performance and does not block threads (exception being `#wait` and similar methods where it's
126
- intended).
127
-
130
+ * [Atomic Markable Reference](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/AtomicMarkableReference.html)
131
+ * [LockFreeLinked Set](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/LockFreeLinkedSet.html)
132
+ * [LockFreeStack](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/LockFreeStack.html)
128
133
 
129
134
  #### Statuses:
130
135
 
131
136
  *Why are these not in core?*
132
137
 
133
- - **Actor** - Partial documentation and tests; stability is good.
138
+ - **Actor** - Partial documentation and tests; stability is good.
134
139
  - **Future/Promise Framework** - API changes; partial documentation and tests; stability good.
135
140
  - **Agent** - Incomplete behaviour compared to Clojure's models; stability good.
136
141
  - **Channel** - Missing documentation; limted features; stability good.
137
142
  - **Exchanger** - Known race condition requiring a new implementation.
138
- - **LazyRegister** - Missing documentation and tests.
143
+ - **LazyRegister** - Missing documentation and tests.
144
+ - **AtomicMarkableReference, LockFreeLinkedSet, LockFreeStack** - Needs real world battle testing
139
145
 
140
146
  ## Usage
141
147
 
@@ -179,8 +185,6 @@ require 'concurrent/actor' # Concurrent::Actor and supporting code
179
185
  require 'concurrent/edge/future' # new Future Framework
180
186
  require 'concurrent/agent' # Concurrent::Agent
181
187
  require 'concurrent/channel ' # Concurrent::Channel and supporting code
182
- require 'concurrent/exchanger' # Concurrent::Exchanger
183
- require 'concurrent/lazy_register' # Concurrent::LazyRegister
184
188
  ```
185
189
 
186
190
  If the library does not behave as expected, `Concurrent.use_stdlib_logger(Logger::DEBUG)` could help to reveal the problem.
@@ -66,22 +66,26 @@ module Concurrent
66
66
 
67
67
  # @!macro [attach] atomic_fixnum_method_increment
68
68
  #
69
- # Increases the current value by 1.
69
+ # Increases the current value by the given amount (defaults to 1).
70
+ #
71
+ # @param [Fixnum] delta the amount by which to increase the current value
70
72
  #
71
73
  # @return [Fixnum] the current value after incrementation
72
- def increment
73
- synchronize { ns_set(@value + 1) }
74
+ def increment(delta = 1)
75
+ synchronize { ns_set(@value + delta.to_i) }
74
76
  end
75
77
 
76
78
  alias_method :up, :increment
77
79
 
78
80
  # @!macro [attach] atomic_fixnum_method_decrement
79
81
  #
80
- # Decreases the current value by 1.
82
+ # Decreases the current value by the given amount (defaults to 1).
83
+ #
84
+ # @param [Fixnum] delta the amount by which to decrease the current value
81
85
  #
82
86
  # @return [Fixnum] the current value after decrementation
83
- def decrement
84
- synchronize { ns_set(@value -1) }
87
+ def decrement(delta = 1)
88
+ synchronize { ns_set(@value - delta.to_i) }
85
89
  end
86
90
 
87
91
  alias_method :down, :decrement
@@ -97,8 +101,8 @@ module Concurrent
97
101
  # @return [Fixnum] true if the value was updated else false
98
102
  def compare_and_set(expect, update)
99
103
  synchronize do
100
- if @value == expect
101
- @value = update
104
+ if @value == expect.to_i
105
+ @value = update.to_i
102
106
  true
103
107
  else
104
108
  false
@@ -106,6 +110,23 @@ module Concurrent
106
110
  end
107
111
  end
108
112
 
113
+ # @!macro [attach] atomic_fixnum_method_update
114
+ #
115
+ # Pass the current value to the given block, replacing it
116
+ # with the block's result. May retry if the value changes
117
+ # during the block's execution.
118
+ #
119
+ # @yield [Object] Calculate a new value for the atomic reference using
120
+ # given (old) value
121
+ # @yieldparam [Object] old_value the starting value of the atomic reference
122
+ #
123
+ # @return [Object] the new value
124
+ def update
125
+ synchronize do
126
+ @value = yield @value
127
+ end
128
+ end
129
+
109
130
  protected
110
131
 
111
132
  # @!visibility private
@@ -1,5 +1,5 @@
1
1
  require 'thread'
2
- require 'concurrent/atomic/atomic_reference'
2
+ require 'concurrent/atomic/atomic_fixnum'
3
3
  require 'concurrent/errors'
4
4
  require 'concurrent/synchronization'
5
5
 
@@ -33,7 +33,7 @@ module Concurrent
33
33
  WAITING_WRITER = 1 << 15
34
34
 
35
35
  # @!visibility private
36
- RUNNING_WRITER = 1 << 30
36
+ RUNNING_WRITER = 1 << 29
37
37
 
38
38
  # @!visibility private
39
39
  MAX_READERS = WAITING_WRITER - 1
@@ -50,11 +50,11 @@ module Concurrent
50
50
  # Each reader increments the counter by 1 when acquiring a read lock
51
51
  # (and decrements by 1 when releasing the read lock)
52
52
  # The counter is increased by (1 << 15) for each writer waiting to acquire the
53
- # write lock, and by (1 << 30) if the write lock is taken
53
+ # write lock, and by (1 << 29) if the write lock is taken
54
54
 
55
55
  # Create a new `ReadWriteLock` in the unlocked state.
56
56
  def initialize
57
- @Counter = AtomicReference.new(0) # single integer which represents lock state
57
+ @Counter = AtomicFixnum.new(0) # single integer which represents lock state
58
58
  @ReadLock = Synchronization::Lock.new
59
59
  @WriteLock = Synchronization::Lock.new
60
60
  ensure_ivar_visibility!
@@ -122,11 +122,11 @@ module Concurrent
122
122
  if running_writer?(c)
123
123
  @ReadLock.wait_until { !running_writer? }
124
124
  else
125
- return if @Counter.compare_and_swap(c, c+1)
125
+ return if @Counter.compare_and_set(c, c+1)
126
126
  end
127
127
  end
128
128
  else
129
- break if @Counter.compare_and_swap(c, c+1)
129
+ break if @Counter.compare_and_set(c, c+1)
130
130
  end
131
131
  end
132
132
  true
@@ -138,7 +138,7 @@ module Concurrent
138
138
  def release_read_lock
139
139
  while true
140
140
  c = @Counter.value
141
- if @Counter.compare_and_swap(c, c-1)
141
+ if @Counter.compare_and_set(c, c-1)
142
142
  # If one or more writers were waiting, and we were the last reader, wake a writer up
143
143
  if waiting_writer?(c) && running_readers(c) == 1
144
144
  @WriteLock.signal
@@ -162,8 +162,8 @@ module Concurrent
162
162
 
163
163
  if c == 0 # no readers OR writers running
164
164
  # if we successfully swap the RUNNING_WRITER bit on, then we can go ahead
165
- break if @Counter.compare_and_swap(0, RUNNING_WRITER)
166
- elsif @Counter.compare_and_swap(c, c+WAITING_WRITER)
165
+ break if @Counter.compare_and_set(0, RUNNING_WRITER)
166
+ elsif @Counter.compare_and_set(c, c+WAITING_WRITER)
167
167
  while true
168
168
  # Now we have successfully incremented, so no more readers will be able to increment
169
169
  # (they will wait instead)
@@ -180,7 +180,7 @@ module Concurrent
180
180
  # Then we are OK to stop waiting and go ahead
181
181
  # Otherwise go back and wait again
182
182
  c = @Counter.value
183
- break if !running_writer?(c) && !running_readers?(c) && @Counter.compare_and_swap(c, c+RUNNING_WRITER-WAITING_WRITER)
183
+ break if !running_writer?(c) && !running_readers?(c) && @Counter.compare_and_set(c, c+RUNNING_WRITER-WAITING_WRITER)
184
184
  end
185
185
  break
186
186
  end
@@ -192,7 +192,7 @@ module Concurrent
192
192
  #
193
193
  # @return [Boolean] true if the lock is successfully released
194
194
  def release_write_lock
195
- c = @Counter.update { |c| c-RUNNING_WRITER }
195
+ c = @Counter.update { |counter| counter-RUNNING_WRITER }
196
196
  @ReadLock.broadcast
197
197
  @WriteLock.signal if waiting_writers(c) > 0
198
198
  true
@@ -0,0 +1,375 @@
1
+ require 'thread'
2
+ require 'concurrent/atomic/atomic_reference'
3
+ require 'concurrent/errors'
4
+ require 'concurrent/synchronization'
5
+ require 'concurrent/atomic/thread_local_var'
6
+
7
+ module Concurrent
8
+
9
+ # Re-entrant read-write lock implementation
10
+ #
11
+ # Allows any number of concurrent readers, but only one concurrent writer
12
+ # (And while the "write" lock is taken, no read locks can be obtained either.
13
+ # Hence, the write lock can also be called an "exclusive" lock.)
14
+ #
15
+ # If another thread has taken a read lock, any thread which wants a write lock
16
+ # will block until all the readers release their locks. However, once a thread
17
+ # starts waiting to obtain a write lock, any additional readers that come along
18
+ # will also wait (so writers are not starved).
19
+ #
20
+ # A thread can acquire both a read and write lock at the same time. A thread can
21
+ # also acquire a read lock OR a write lock more than once. Only when the read (or
22
+ # write) lock is released as many times as it was acquired, will the thread
23
+ # actually let it go, allowing other threads which might have been waiting
24
+ # to proceed.
25
+ #
26
+ # If both read and write locks are acquired by the same thread, it is not strictly
27
+ # necessary to release them in the same order they were acquired. In other words,
28
+ # the following code is legal:
29
+ #
30
+ # @example
31
+ # lock = Concurrent::ReentrantReadWriteLock.new
32
+ # lock.acquire_write_lock
33
+ # lock.acquire_read_lock
34
+ # lock.release_write_lock
35
+ # # At this point, the current thread is holding only a read lock, not a write
36
+ # # lock. So other threads can take read locks, but not a write lock.
37
+ # lock.release_read_lock
38
+ # # Now the current thread is not holding either a read or write lock, so
39
+ # # another thread could potentially acquire a write lock.
40
+ #
41
+ # This implementation was inspired by `java.util.concurrent.ReentrantReadWriteLock`.
42
+ #
43
+ # @example
44
+ # lock = Concurrent::ReentrantReadWriteLock.new
45
+ # lock.with_read_lock { data.retrieve }
46
+ # lock.with_write_lock { data.modify! }
47
+ #
48
+ # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html java.util.concurrent.ReentrantReadWriteLock
49
+ class ReentrantReadWriteLock < Synchronization::Object
50
+
51
+ # Implementation notes:
52
+ #
53
+ # A goal is to make the uncontended path for both readers/writers mutex-free
54
+ # Only if there is reader-writer or writer-writer contention, should mutexes be used
55
+ # Otherwise, a single CAS operation is all we need to acquire/release a lock
56
+ #
57
+ # Internal state is represented by a single integer ("counter"), and updated
58
+ # using atomic compare-and-swap operations
59
+ # When the counter is 0, the lock is free
60
+ # Each thread which has one OR MORE read locks increments the counter by 1
61
+ # (and decrements by 1 when releasing the read lock)
62
+ # The counter is increased by (1 << 15) for each writer waiting to acquire the
63
+ # write lock, and by (1 << 29) if the write lock is taken
64
+ #
65
+ # Additionally, each thread uses a thread-local variable to count how many times
66
+ # it has acquired a read lock, AND how many times it has acquired a write lock.
67
+ # It uses a similar trick; an increment of 1 means a read lock was taken, and
68
+ # an increment of (1 << 15) means a write lock was taken
69
+ # This is what makes re-entrancy possible
70
+ #
71
+ # 2 rules are followed to ensure good liveness properties:
72
+ # 1) Once a writer has queued up and is waiting for a write lock, no other thread
73
+ # can take a lock without waiting
74
+ # 2) When a write lock is released, readers are given the "first chance" to wake
75
+ # up and acquire a read lock
76
+ # Following these rules means readers and writers tend to "take turns", so neither
77
+ # can starve the other, even under heavy contention
78
+
79
+ # @!visibility private
80
+ READER_BITS = 15
81
+ # @!visibility private
82
+ WRITER_BITS = 14
83
+
84
+ # Used with @Counter:
85
+ # @!visibility private
86
+ WAITING_WRITER = 1 << READER_BITS
87
+ # @!visibility private
88
+ RUNNING_WRITER = 1 << (READER_BITS + WRITER_BITS)
89
+ # @!visibility private
90
+ MAX_READERS = WAITING_WRITER - 1
91
+ # @!visibility private
92
+ MAX_WRITERS = RUNNING_WRITER - MAX_READERS - 1
93
+
94
+ # Used with @HeldCount:
95
+ # @!visibility private
96
+ WRITE_LOCK_HELD = 1 << READER_BITS
97
+ # @!visibility private
98
+ READ_LOCK_MASK = WRITE_LOCK_HELD - 1
99
+ # @!visibility private
100
+ WRITE_LOCK_MASK = MAX_WRITERS
101
+
102
+ # Create a new `ReentrantReadWriteLock` in the unlocked state.
103
+ def initialize
104
+ @Counter = AtomicFixnum.new(0) # single integer which represents lock state
105
+ @ReadQueue = Synchronization::Lock.new # used to queue waiting readers
106
+ @WriteQueue = Synchronization::Lock.new # used to queue waiting writers
107
+ @HeldCount = ThreadLocalVar.new(0) # indicates # of R & W locks held by this thread
108
+ ensure_ivar_visibility!
109
+ end
110
+
111
+ # Execute a block operation within a read lock.
112
+ #
113
+ # @yield the task to be performed within the lock.
114
+ #
115
+ # @return [Object] the result of the block operation.
116
+ #
117
+ # @raise [ArgumentError] when no block is given.
118
+ # @raise [Concurrent::ResourceLimitError] if the maximum number of readers
119
+ # is exceeded.
120
+ def with_read_lock
121
+ raise ArgumentError.new('no block given') unless block_given?
122
+ acquire_read_lock
123
+ begin
124
+ yield
125
+ ensure
126
+ release_read_lock
127
+ end
128
+ end
129
+
130
+ # Execute a block operation within a write lock.
131
+ #
132
+ # @yield the task to be performed within the lock.
133
+ #
134
+ # @return [Object] the result of the block operation.
135
+ #
136
+ # @raise [ArgumentError] when no block is given.
137
+ # @raise [Concurrent::ResourceLimitError] if the maximum number of readers
138
+ # is exceeded.
139
+ def with_write_lock
140
+ raise ArgumentError.new('no block given') unless block_given?
141
+ acquire_write_lock
142
+ begin
143
+ yield
144
+ ensure
145
+ release_write_lock
146
+ end
147
+ end
148
+
149
+ # Acquire a read lock. If a write lock is held by another thread, will block
150
+ # until it is released.
151
+ #
152
+ # @return [Boolean] true if the lock is successfully acquired
153
+ #
154
+ # @raise [Concurrent::ResourceLimitError] if the maximum number of readers
155
+ # is exceeded.
156
+ def acquire_read_lock
157
+ if (held = @HeldCount.value) > 0
158
+ # If we already have a lock, there's no need to wait
159
+ if held & READ_LOCK_MASK == 0
160
+ # But we do need to update the counter, if we were holding a write
161
+ # lock but not a read lock
162
+ @Counter.update { |c| c + 1 }
163
+ end
164
+ @HeldCount.value = held + 1
165
+ return true
166
+ end
167
+
168
+ while true
169
+ c = @Counter.value
170
+ raise ResourceLimitError.new('Too many reader threads') if max_readers?(c)
171
+
172
+ # If a writer is waiting OR running when we first queue up, we need to wait
173
+ if waiting_or_running_writer?(c)
174
+ # Before going to sleep, check again with the ReadQueue mutex held
175
+ @ReadQueue.synchronize do
176
+ @ReadQueue.ns_wait if waiting_or_running_writer?
177
+ end
178
+ # Note: the above 'synchronize' block could have used #wait_until,
179
+ # but that waits repeatedly in a loop, checking the wait condition
180
+ # each time it wakes up (to protect against spurious wakeups)
181
+ # But we are already in a loop, which is only broken when we successfully
182
+ # acquire the lock! So we don't care about spurious wakeups, and would
183
+ # rather not pay the extra overhead of using #wait_until
184
+
185
+ # After a reader has waited once, they are allowed to "barge" ahead of waiting writers
186
+ # But if a writer is *running*, the reader still needs to wait (naturally)
187
+ while true
188
+ c = @Counter.value
189
+ if running_writer?(c)
190
+ @ReadQueue.synchronize do
191
+ @ReadQueue.ns_wait if running_writer?
192
+ end
193
+ elsif @Counter.compare_and_set(c, c+1)
194
+ @HeldCount.value = held + 1
195
+ return true
196
+ end
197
+ end
198
+ elsif @Counter.compare_and_set(c, c+1)
199
+ @HeldCount.value = held + 1
200
+ return true
201
+ end
202
+ end
203
+ end
204
+
205
+ # Try to acquire a read lock and return true if we succeed. If it cannot be
206
+ # acquired immediately, return false.
207
+ #
208
+ # @return [Boolean] true if the lock is successfully acquired
209
+ def try_read_lock
210
+ if (held = @HeldCount.value) > 0
211
+ if held & READ_LOCK_MASK == 0
212
+ # If we hold a write lock, but not a read lock...
213
+ @Counter.update { |c| c + 1 }
214
+ end
215
+ @HeldCount.value = held + 1
216
+ return true
217
+ else
218
+ c = @Counter.value
219
+ if !waiting_or_running_writer?(c) && @Counter.compare_and_set(c, c+1)
220
+ @HeldCount.value = held + 1
221
+ return true
222
+ end
223
+ end
224
+ false
225
+ end
226
+
227
+ # Release a previously acquired read lock.
228
+ #
229
+ # @return [Boolean] true if the lock is successfully released
230
+ def release_read_lock
231
+ held = @HeldCount.value = @HeldCount.value - 1
232
+ rlocks_held = held & READ_LOCK_MASK
233
+ if rlocks_held == 0
234
+ c = @Counter.update { |counter| counter - 1 }
235
+ # If one or more writers were waiting, and we were the last reader, wake a writer up
236
+ if waiting_or_running_writer?(c) && running_readers(c) == 0
237
+ @WriteQueue.signal
238
+ end
239
+ elsif rlocks_held == READ_LOCK_MASK
240
+ raise IllegalOperationError, "Cannot release a read lock which is not held"
241
+ end
242
+ true
243
+ end
244
+
245
+ # Acquire a write lock. Will block and wait for all active readers and writers.
246
+ #
247
+ # @return [Boolean] true if the lock is successfully acquired
248
+ #
249
+ # @raise [Concurrent::ResourceLimitError] if the maximum number of writers
250
+ # is exceeded.
251
+ def acquire_write_lock
252
+ if (held = @HeldCount.value) >= WRITE_LOCK_HELD
253
+ # if we already have a write (exclusive) lock, there's no need to wait
254
+ @HeldCount.value = held + WRITE_LOCK_HELD
255
+ return true
256
+ end
257
+
258
+ while true
259
+ c = @Counter.value
260
+ raise ResourceLimitError.new('Too many writer threads') if max_writers?(c)
261
+
262
+ # To go ahead and take the lock without waiting, there must be no writer
263
+ # running right now, AND no writers who came before us still waiting to
264
+ # acquire the lock
265
+ # Additionally, if any read locks have been taken, we must hold all of them
266
+ if c == held
267
+ # If we successfully swap the RUNNING_WRITER bit on, then we can go ahead
268
+ if @Counter.compare_and_set(c, c+RUNNING_WRITER)
269
+ @HeldCount.value = held + WRITE_LOCK_HELD
270
+ return true
271
+ end
272
+ elsif @Counter.compare_and_set(c, c+WAITING_WRITER)
273
+ while true
274
+ # Now we have successfully incremented, so no more readers will be able to increment
275
+ # (they will wait instead)
276
+ # However, readers OR writers could decrement right here
277
+ @WriteQueue.synchronize do
278
+ # So we have to do another check inside the synchronized section
279
+ # If a writer OR another reader is running, then go to sleep
280
+ c = @Counter.value
281
+ @WriteQueue.ns_wait if running_writer?(c) || running_readers(c) != held
282
+ end
283
+ # Note: if you are thinking of replacing the above 'synchronize' block
284
+ # with #wait_until, read the comment in #acquire_read_lock first!
285
+
286
+ # We just came out of a wait
287
+ # If we successfully turn the RUNNING_WRITER bit on with an atomic swap,
288
+ # then we are OK to stop waiting and go ahead
289
+ # Otherwise go back and wait again
290
+ c = @Counter.value
291
+ if !running_writer?(c) &&
292
+ running_readers(c) == held &&
293
+ @Counter.compare_and_set(c, c+RUNNING_WRITER-WAITING_WRITER)
294
+ @HeldCount.value = held + WRITE_LOCK_HELD
295
+ return true
296
+ end
297
+ end
298
+ end
299
+ end
300
+ end
301
+
302
+ # Try to acquire a write lock and return true if we succeed. If it cannot be
303
+ # acquired immediately, return false.
304
+ #
305
+ # @return [Boolean] true if the lock is successfully acquired
306
+ def try_write_lock
307
+ if (held = @HeldCount.value) >= WRITE_LOCK_HELD
308
+ @HeldCount.value = held + WRITE_LOCK_HELD
309
+ return true
310
+ else
311
+ c = @Counter.value
312
+ if !waiting_or_running_writer?(c) &&
313
+ running_readers(c) == held &&
314
+ @Counter.compare_and_set(c, c+RUNNING_WRITER)
315
+ @HeldCount.value = held + WRITE_LOCK_HELD
316
+ return true
317
+ end
318
+ end
319
+ false
320
+ end
321
+
322
+ # Release a previously acquired write lock.
323
+ #
324
+ # @return [Boolean] true if the lock is successfully released
325
+ def release_write_lock
326
+ held = @HeldCount.value = @HeldCount.value - WRITE_LOCK_HELD
327
+ wlocks_held = held & WRITE_LOCK_MASK
328
+ if wlocks_held == 0
329
+ c = @Counter.update { |counter| counter - RUNNING_WRITER }
330
+ @ReadQueue.broadcast
331
+ @WriteQueue.signal if waiting_writers(c) > 0
332
+ elsif wlocks_held == WRITE_LOCK_MASK
333
+ raise IllegalOperationError, "Cannot release a write lock which is not held"
334
+ end
335
+ true
336
+ end
337
+
338
+ private
339
+
340
+ # @!visibility private
341
+ def running_readers(c = @Counter.value)
342
+ c & MAX_READERS
343
+ end
344
+
345
+ # @!visibility private
346
+ def running_readers?(c = @Counter.value)
347
+ (c & MAX_READERS) > 0
348
+ end
349
+
350
+ # @!visibility private
351
+ def running_writer?(c = @Counter.value)
352
+ c >= RUNNING_WRITER
353
+ end
354
+
355
+ # @!visibility private
356
+ def waiting_writers(c = @Counter.value)
357
+ (c & MAX_WRITERS) >> READER_BITS
358
+ end
359
+
360
+ # @!visibility private
361
+ def waiting_or_running_writer?(c = @Counter.value)
362
+ c >= WAITING_WRITER
363
+ end
364
+
365
+ # @!visibility private
366
+ def max_readers?(c = @Counter.value)
367
+ (c & MAX_READERS) == MAX_READERS
368
+ end
369
+
370
+ # @!visibility private
371
+ def max_writers?(c = @Counter.value)
372
+ (c & MAX_WRITERS) == MAX_WRITERS
373
+ end
374
+ end
375
+ end