concurrent-ruby 0.9.0.pre3-java → 0.9.1-java

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: 10a5012e42fee5380405d4af4afcbc1057bdda26
4
- data.tar.gz: d3a355604220a071410631424fc05288df4a67e6
3
+ metadata.gz: f83e4336126502153551f929eee369cd8216b18d
4
+ data.tar.gz: 2c5ff95b7bc1850a2a81e3ed90c0cbe2120e5064
5
5
  SHA512:
6
- metadata.gz: 3e3bfd78da01a87137ad705c28a0503f057689e64614d4e22fcea1a79d7e29f041a90d36d0dceb16937fa17e8dc66542a4a528ebaee11d0a05ab0c7a901049f8
7
- data.tar.gz: 41308b6e724dfa7dbc4a74f979b748b62aa475c8396e7e46234e2c0214910aafb253112f9c413934f97e2123c8831ad394d34cf8c6be251262420ee24b387bca
6
+ metadata.gz: 204ffe7abafca91f53b32075ea39a4c9cd260c753c7d1dc454b26b6e9e1238c150e3a78e9fce5a498a5d935efcc0e9b217ee20cef3477bc979a5078cb29d7077
7
+ data.tar.gz: 9d854dad02dbdb4a510d1df469467047b5ab5499341848ffacbe04bd914ba4c437189125a2d690bac474fa178a377df41d79643fa2d001abca6657b0b1ab4cc7
@@ -1,5 +1,18 @@
1
- ### Next Release v0.9.0 (Target Date: 7 June 2015)
1
+ ## Current Release v0.9.1 (09 August 2015)
2
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)
3
16
 
4
17
  * Updated `AtomicReference`
5
18
  - `AtomicReference#try_update` now simply returns instead of raising exception
@@ -98,7 +111,7 @@
98
111
  * Removed brute-force killing of threads in tests
99
112
  * Fixed a thread pool bug when the operating system cannot allocate more threads
100
113
 
101
- ## Current Release v0.8.0 (25 January 2015)
114
+ ### Release v0.8.0 (25 January 2015)
102
115
 
103
116
  * C extension for MRI have been extracted into the `concurrent-ruby-ext` companion gem.
104
117
  Please see the README for more detail.
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) [![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>
@@ -39,16 +47,17 @@
39
47
 
40
48
  MRI 1.9.3, 2.0, 2.1, 2.2, JRuby (1.9 mode), and Rubinius 2.x are supported.
41
49
  This gem should be fully compatible with any interpreter that is compliant with Ruby 1.9.3 or newer.
50
+ Java 8 is required for JRuby (Java 7 support is deprecated in version 0.9 and will be removed in 1.0).
42
51
 
43
52
  ## Features & Documentation
44
53
 
45
- 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).
46
55
 
47
56
  The primary site for documentation is the automatically generated [API documentation](http://ruby-concurrency.github.io/concurrent-ruby/frames.html)
48
57
 
49
- 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).
50
59
 
51
- 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.
52
61
 
53
62
  #### General-purpose Concurrency Abstractions
54
63
 
@@ -58,7 +67,7 @@ This library contains a variety of concurrency abstractions at high and low leve
58
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.
59
68
  * [Promise](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Promise.html): Similar to Futures, with more features.
60
69
  * [ScheduledTask](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ScheduledTask.html): Like a Future scheduled for a specific future time.
61
- * [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.
62
71
 
63
72
  #### Thread-safe Value Objects
64
73
 
@@ -96,6 +105,7 @@ Derived from Ruby's [Struct](http://ruby-doc.org/core-2.2.0/Struct.html):
96
105
  * [Thread-local variables](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadLocalVar.html)
97
106
  * [Software transactional memory](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/TVar.html) (TVar)
98
107
  * [ReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ReadWriteLock.html)
108
+ * [ReentrantReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ReentrantReadWriteLock.html)
99
109
 
100
110
  ### Edge Features
101
111
 
@@ -107,34 +117,31 @@ be obeyed though. Features developed in `concurrent-ruby-edge` are expected to m
107
117
 
108
118
  * [Actor](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Actor.html):
109
119
  Implements the Actor Model, where concurrent actors exchange messages.
110
- * [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
111
121
  unified implementation of Futures and Promises which combines Features of previous `Future`,
112
122
  `Promise`, `IVar`, `Event`, `Probe`, `dataflow`, `Delay`, `TimerTask` into single framework. It uses extensively
113
- new synchronization layer to make all the paths **lock-free** with exception of blocking threads on `#wait`.
114
- 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.
115
125
  * [Agent](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Agent.html): A single atomic value that represents an identity.
116
126
  * [Channel](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Channel.html):
117
127
  Communicating Sequential Processes (CSP).
118
128
  * [Exchanger](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Exchanger.html)
119
129
  * [LazyRegister](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/LazyRegister.html)
120
- * [New Future Promise Framework](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge.html) - new
121
- unified implementation of Futures and Promises which combines Features of previous `Future`,
122
- `Promise`, `IVar`, `Probe`, `dataflow`, `Delay`, `TimerTask` into single framework. It uses extensively
123
- new synchronization layer to make all the paths lock-free with exception of blocking threads on `#wait`.
124
- It offers better performance and does not block threads (exception being `#wait` and similar methods where it's
125
- intended).
126
-
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)
127
133
 
128
134
  #### Statuses:
129
135
 
130
- *Why is not in core?*
136
+ *Why are these not in core?*
131
137
 
132
- - **Actor** - partial documentation and tests, stability good.
133
- - **Future/Promise Framework** - partial documentation and tests, stability good.
134
- - **Agent** - incomplete behaviour compared to Clojure's model, stability good.
135
- - **Channel** - missing documentation, stability good.
136
- - **Exchanger** - known race issue.
137
- - **LazyRegister** - missing documentation and tests.
138
+ - **Actor** - Partial documentation and tests; stability is good.
139
+ - **Future/Promise Framework** - API changes; partial documentation and tests; stability good.
140
+ - **Agent** - Incomplete behaviour compared to Clojure's models; stability good.
141
+ - **Channel** - Missing documentation; limted features; stability good.
142
+ - **Exchanger** - Known race condition requiring a new implementation.
143
+ - **LazyRegister** - Missing documentation and tests.
144
+ - **AtomicMarkableReference, LockFreeLinkedSet, LockFreeStack** - Needs real world battle testing
138
145
 
139
146
  ## Usage
140
147
 
@@ -178,8 +185,6 @@ require 'concurrent/actor' # Concurrent::Actor and supporting code
178
185
  require 'concurrent/edge/future' # new Future Framework
179
186
  require 'concurrent/agent' # Concurrent::Agent
180
187
  require 'concurrent/channel ' # Concurrent::Channel and supporting code
181
- require 'concurrent/exchanger' # Concurrent::Exchanger
182
- require 'concurrent/lazy_register' # Concurrent::LazyRegister
183
188
  ```
184
189
 
185
190
  If the library does not behave as expected, `Concurrent.use_stdlib_logger(Logger::DEBUG)` could help to reveal the problem.
@@ -248,10 +253,16 @@ bundle exec rake build # Build JRuby-specific core gem (alias for `
248
253
  bundle exec rake build:core # Build concurrent-ruby-<version>-java.gem into the pkg directory
249
254
 
250
255
  *All except JRuby*
251
- bundle exec rake build # Build core and extension gems
252
256
  bundle exec rake build:core # Build concurrent-ruby-<version>.gem into the pkg directory
253
257
  bundle exec rake build:ext # Build concurrent-ruby-ext-<version>.gem into the pkg directory
254
258
 
259
+ *When Docker IS installed*
260
+ bundle exec rake build:windows # Build the windows binary <version> gems per rake-compiler-dock
261
+ bundle exec rake build # Build core, extension, and edge gems, including Windows binaries
262
+
263
+ *When Docker is NOT installed*
264
+ bundle exec rake build # Build core, extension, and edge gems (excluding Windows binaries)
265
+
255
266
  *All*
256
267
  bundle exec rake clean # Remove any temporary products
257
268
  bundle exec rake clobber # Remove any generated file
@@ -260,7 +271,7 @@ bundle exec rake compile # Compile all the extensions
260
271
 
261
272
  ## Maintainers
262
273
 
263
- * [Jerry D'Antonio](https://github.com/jdantonio)
274
+ * [Jerry D'Antonio](https://github.com/jdantonio) (creator)
264
275
  * [Michele Della Torre](https://github.com/mighe)
265
276
  * [Chris Seaton](https://github.com/chrisseaton)
266
277
  * [Lucas Allan](https://github.com/lucasallan)
@@ -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