concurrent-ruby 0.8.0.pre1 → 0.8.0.pre2

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: 92bfafa2b9e5e7a3b60cbf6dd6f785a8f515ae64
4
- data.tar.gz: 14ca027a5eadd0daa3d10fac49860418bb9cb7bc
3
+ metadata.gz: f7918a0f32c1369463e3045101a1a3b1629d501a
4
+ data.tar.gz: c97009756916b18836a277c4d62df2f57c02b797
5
5
  SHA512:
6
- metadata.gz: 7fae6fa7ae3849e3d68578eb782e13f1dd2e149260c8ef9a5dc8bef72891af0cce9e82abb697cfe72819a1e1c1385936138b63b73f59dd71fba2c9b1bc47bcb8
7
- data.tar.gz: 66264191f55ab2064cb527a32c84da91550e47681f0e67856535e72b3cb23875d308b8770ec74adfa921800f9c0ae79d14b2f7f7481e545e648dd98eda9ea36a
6
+ metadata.gz: 48a95b84f0d4f4545e0e962031d74e608ac922d7f99177fb98186ba3d6a4bc86e7f49033dae64afccdbb3c7a4730544826f91a362300548eb0519d145150fbbd
7
+ data.tar.gz: 4624b6a91696efe3d365091171f6e03c9de2a764d8d1791892d0ae43293a05e08d740536914f9ef57b3e10f7f8ba1ee766f1e0bda322f24e53a2565038d47af0
@@ -1,5 +1,6 @@
1
1
  ### Next Release v0.7.2 (TBD)
2
2
 
3
+ * New `Semaphore` class based on [java.util.concurrent.Semaphore](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Semaphore.html)
3
4
  * New `Promise.all?` and `Promise.any?` class methods
4
5
  * Renamed `:overflow_policy` on thread pools to `:fallback_policy`
5
6
  * Thread pools still accept the `:overflow_policy` option but display a warning
data/README.md CHANGED
@@ -37,7 +37,7 @@
37
37
 
38
38
  ### Supported Ruby versions
39
39
 
40
- MRI 1.9.3, 2.0, 2.1, JRuby (1.9 mode), and Rubinius 2.x are supported.
40
+ MRI 1.9.3, 2.0, 2.1, 2.2, JRuby (1.9 mode), and Rubinius 2.x are supported.
41
41
  This gem should be fully compatible with any interpreter that is compliant with Ruby 1.9.3 or newer.
42
42
 
43
43
  ## Features & Documentation
@@ -52,19 +52,19 @@ This library contains a variety of concurrency abstractions at high and low leve
52
52
 
53
53
  ### High-level, general-purpose asynchronous concurrency abstractions
54
54
 
55
- * [Actor](./doc/actor/main.md): Implements the Actor Model, where concurrent actors exchange messages.
56
- * [Agent](./doc/agent.md): A single atomic value that represents an identity.
57
- * [Async](./doc/async.md): A mixin module that provides simple asynchronous behavior to any standard class/object or object.
58
- * [Future](./doc/future.md): An asynchronous operation that produces a value.
59
- * [Dataflow](./doc/dataflow.md): Built on Futures, Dataflow allows you to create a task that will be scheduled when all of its data dependencies are available.
60
- * [Promise](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Promise.html): Similar to Futures, with more features.
61
- * [ScheduledTask](./doc/scheduled_task.md): Like a Future scheduled for a specific future time.
55
+ * [Actor](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Actor.html): Implements the Actor Model, where concurrent actors exchange messages.
56
+ * [Agent](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Agent.html): A single atomic value that represents an identity.
57
+ * [Async](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Async.html): A mixin module that provides simple asynchronous behavior to any standard class/object or object.
58
+ * [Future](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Future.html): An asynchronous operation that produces a value.
59
+ * [Dataflow](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Dataflow.html): Built on Futures, Dataflow allows you to create a task that will be scheduled when all of its data dependencies are available.
60
+ * [Promise](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Promise.html): Similar to Futures, with more features.
61
+ * [ScheduledTask](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ScheduledTask.html): Like a Future scheduled for a specific future time.
62
62
  * [TimerTask](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/TimerTask.html): A Thread that periodically wakes up to perform work at regular intervals.
63
63
  * [Channel](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Channel.html): Communicating Sequential Processes (CSP).
64
64
 
65
65
  ### Java-inspired ThreadPools and other executors
66
66
 
67
- * See [ThreadPool](./doc/thread_pools.md) overview, which also contains a list of other Executors available.
67
+ * See [ThreadPool](http://ruby-concurrency.github.io/concurrent-ruby/file.thread_pools.html) overview, which also contains a list of other Executors available.
68
68
 
69
69
  ### Thread-safe Observers
70
70
 
@@ -73,6 +73,7 @@ This library contains a variety of concurrency abstractions at high and low leve
73
73
  * [CopyOnWriteObserverSet](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/CopyOnWriteObserverSet.html)
74
74
 
75
75
  ### Thread synchronization classes and algorithms
76
+
76
77
  Lower-level abstractions mainly used as building blocks.
77
78
 
78
79
  * [condition](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Condition.html)
@@ -80,10 +81,12 @@ Lower-level abstractions mainly used as building blocks.
80
81
  * [cyclic barrier](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/CyclicBarrier.html)
81
82
  * [event](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Event.html)
82
83
  * [exchanger](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Exchanger.html)
84
+ * [semaphore](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Semaphore.html)
83
85
  * [timeout](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent.html#timeout-class_method)
84
86
  * [timer](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent.html#timer-class_method)
85
87
 
86
88
  ### Thread-safe variables
89
+
87
90
  Lower-level abstractions mainly used as building blocks.
88
91
 
89
92
  * [AtomicBoolean](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/AtomicBoolean.html)
@@ -92,7 +95,7 @@ Lower-level abstractions mainly used as building blocks.
92
95
  * [I-Structures](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/IVar.html) (IVar)
93
96
  * [M-Structures](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/MVar.html) (MVar)
94
97
  * [thread-local variables](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadLocalVar.html)
95
- * [software transactional memory](./doc/tvar.md) (TVar)
98
+ * [software transactional memory](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/TVar.html) (TVar)
96
99
 
97
100
  ## Usage
98
101
 
@@ -153,7 +156,7 @@ To minimize installation errors the C extensions are available in the `concurren
153
156
  gem. The extension gem lists `concurrent-ruby` as a dependency so it is not necessary to install both.
154
157
  Simply install the extension gen:
155
158
 
156
- ```shell
159
+ ```ruby
157
160
  gem install concurrent-ruby-ext
158
161
  ```
159
162
 
@@ -167,13 +170,45 @@ and run `bundle install` from your shell.
167
170
 
168
171
  In code it is only necessary to
169
172
 
170
- ```shell
173
+ ```ruby
171
174
  require 'concurrent'
172
175
  ```
173
176
 
174
177
  The `concurrent-ruby` gem will automatically detect the presence of the `concurrent-ruby-ext` gem
175
178
  and load the appropriate C extensions.
176
179
 
180
+ #### Note For gem developers
181
+
182
+ No gems should depend on `concurrent-ruby-ext`. Doing so will force C extensions on your users.
183
+ The best practice is to depend on `concurrent-ruby` and let users to decide if they want C extensions.
184
+
185
+ ### Building
186
+
187
+ All published versions of this gem (core, extension, and several platform-specific packages) are compiled,
188
+ packaged, tested, and published using an open, [automated process](https://github.com/ruby-concurrency/rake-compiler-dev-box).
189
+ This process can also be used to create pre-compiled binaries of the extension gem for virtally
190
+ any platform. *Documentation is forthcoming...*
191
+
192
+ ```
193
+ *MRI only*
194
+ rake build:native # Build concurrent-ruby-ext-<version>-<platform>.gem into the pkg directory
195
+ rake compile:extension # Compile extension
196
+
197
+ *JRuby only*
198
+ rake build # Build JRuby-specific core gem (alias for `build:core`)
199
+ rake build:core # Build concurrent-ruby-<version>-java.gem into the pkg directory
200
+
201
+ *All except JRuby*
202
+ rake build # Build core and extension gems
203
+ rake build:core # Build concurrent-ruby-<version>.gem into the pkg directory
204
+ rake build:ext # Build concurrent-ruby-ext-<version>.gem into the pkg directory
205
+
206
+ *All*
207
+ rake clean # Remove any temporary products
208
+ rake clobber # Remove any generated file
209
+ rake compile # Compile all the extensions
210
+ ```
211
+
177
212
  ## Maintainers
178
213
 
179
214
  * [Jerry D'Antonio](https://github.com/jdantonio)
@@ -0,0 +1,232 @@
1
+ require 'concurrent/atomic/condition'
2
+
3
+ module Concurrent
4
+ class MutexSemaphore
5
+ # @!macro [attach] semaphore_method_initialize
6
+ #
7
+ # Create a new `Semaphore` with the initial `count`.
8
+ #
9
+ # @param [Fixnum] count the initial count
10
+ #
11
+ # @raise [ArgumentError] if `count` is not an integer or is less than zero
12
+ def initialize(count)
13
+ unless count.is_a?(Fixnum) && count >= 0
14
+ fail ArgumentError, 'count must be an non-negative integer'
15
+ end
16
+ @mutex = Mutex.new
17
+ @condition = Condition.new
18
+ @free = count
19
+ end
20
+
21
+ # @!macro [attach] semaphore_method_acquire
22
+ #
23
+ # Acquires the given number of permits from this semaphore,
24
+ # blocking until all are available.
25
+ #
26
+ # @param [Fixnum] permits Number of permits to acquire
27
+ #
28
+ # @raise [ArgumentError] if `permits` is not an integer or is less than
29
+ # one
30
+ #
31
+ # @return [Nil]
32
+ def acquire(permits = 1)
33
+ unless permits.is_a?(Fixnum) && permits > 0
34
+ fail ArgumentError, 'permits must be an integer greater than zero'
35
+ end
36
+ @mutex.synchronize do
37
+ try_acquire_timed(permits, nil)
38
+ nil
39
+ end
40
+ end
41
+
42
+ # @!macro [attach] semaphore_method_available_permits
43
+ #
44
+ # Returns the current number of permits available in this semaphore.
45
+ #
46
+ # @return [Integer]
47
+ def available_permits
48
+ @mutex.synchronize { @free }
49
+ end
50
+
51
+ # @!macro [attach] semaphore_method_drain_permits
52
+ #
53
+ # Acquires and returns all permits that are immediately available.
54
+ #
55
+ # @return [Integer]
56
+ def drain_permits
57
+ @mutex.synchronize do
58
+ @free.tap { |_| @free = 0 }
59
+ end
60
+ end
61
+
62
+ # @!macro [attach] semaphore_method_try_acquire
63
+ #
64
+ # Acquires the given number of permits from this semaphore,
65
+ # only if all are available at the time of invocation or within
66
+ # `timeout` interval
67
+ #
68
+ # @param [Fixnum] permits the number of permits to acquire
69
+ #
70
+ # @param [Fixnum] timeout the number of seconds to wait for the counter
71
+ # or `nil` to return immediately
72
+ #
73
+ # @raise [ArgumentError] if `permits` is not an integer or is less than
74
+ # one
75
+ #
76
+ # @return [Boolean] `false` if no permits are available, `true` when
77
+ # acquired a permit
78
+ def try_acquire(permits = 1, timeout = nil)
79
+ unless permits.is_a?(Fixnum) && permits > 0
80
+ fail ArgumentError, 'permits must be an integer greater than zero'
81
+ end
82
+ @mutex.synchronize do
83
+ if timeout.nil?
84
+ try_acquire_now(permits)
85
+ else
86
+ try_acquire_timed(permits, timeout)
87
+ end
88
+ end
89
+ end
90
+
91
+ # @!macro [attach] semaphore_method_release
92
+ #
93
+ # Releases the given number of permits, returning them to the semaphore.
94
+ #
95
+ # @param [Fixnum] permits Number of permits to return to the semaphore.
96
+ #
97
+ # @raise [ArgumentError] if `permits` is not a number or is less than one
98
+ #
99
+ # @return [Nil]
100
+ def release(permits = 1)
101
+ unless permits.is_a?(Fixnum) && permits > 0
102
+ fail ArgumentError, 'permits must be an integer greater than zero'
103
+ end
104
+ @mutex.synchronize do
105
+ @free += permits
106
+ permits.times { @condition.signal }
107
+ end
108
+ nil
109
+ end
110
+
111
+ # @!macro [attach] semaphore_method_reduce_permits
112
+ #
113
+ # @api private
114
+ #
115
+ # Shrinks the number of available permits by the indicated reduction.
116
+ #
117
+ # @param [Fixnum] reduction Number of permits to remove.
118
+ #
119
+ # @raise [ArgumentError] if `reduction` is not an integer or is negative
120
+ #
121
+ # @raise [ArgumentError] if `@free` - `@reduction` is less than zero
122
+ #
123
+ # @return [Nil]
124
+ def reduce_permits(reduction)
125
+ unless reduction.is_a?(Fixnum) && reduction >= 0
126
+ fail ArgumentError, 'reduction must be an non-negative integer'
127
+ end
128
+ @mutex.synchronize { @free -= reduction }
129
+ nil
130
+ end
131
+
132
+ private
133
+
134
+ def try_acquire_now(permits)
135
+ if @free >= permits
136
+ @free -= permits
137
+ true
138
+ else
139
+ false
140
+ end
141
+ end
142
+
143
+ def try_acquire_timed(permits, timeout)
144
+ remaining = Condition::Result.new(timeout)
145
+ while !try_acquire_now(permits) && remaining.can_wait?
146
+ @condition.signal
147
+ remaining = @condition.wait(@mutex, remaining.remaining_time)
148
+ end
149
+ remaining.can_wait? ? true : false
150
+ end
151
+ end
152
+
153
+ if RUBY_PLATFORM == 'java'
154
+
155
+ # @!macro semaphore
156
+ #
157
+ # A counting semaphore. Conceptually, a semaphore maintains a set of permits. Each {#acquire} blocks if necessary
158
+ # until a permit is available, and then takes it. Each {#release} adds a permit,
159
+ # potentially releasing a blocking acquirer.
160
+ # However, no actual permit objects are used; the Semaphore just keeps a count of the number available and
161
+ # acts accordingly.
162
+ class JavaSemaphore
163
+ # @!macro semaphore_method_initialize
164
+ def initialize(count)
165
+ unless count.is_a?(Fixnum) && count >= 0
166
+ fail(ArgumentError,
167
+ 'count must be in integer greater than or equal zero')
168
+ end
169
+ @semaphore = java.util.concurrent.Semaphore.new(count)
170
+ end
171
+
172
+ # @!macro semaphore_method_acquire
173
+ def acquire(permits = 1)
174
+ unless permits.is_a?(Fixnum) && permits > 0
175
+ fail ArgumentError, 'permits must be an integer greater than zero'
176
+ end
177
+ @semaphore.acquire(permits)
178
+ end
179
+
180
+ # @!macro semaphore_method_available_permits
181
+ def available_permits
182
+ @semaphore.availablePermits
183
+ end
184
+
185
+ # @!macro semaphore_method_drain_permits
186
+ def drain_permits
187
+ @semaphore.drainPermits
188
+ end
189
+
190
+ # @!macro semaphore_method_try_acquire
191
+ def try_acquire(permits = 1, timeout = nil)
192
+ unless permits.is_a?(Fixnum) && permits > 0
193
+ fail ArgumentError, 'permits must be an integer greater than zero'
194
+ end
195
+ if timeout.nil?
196
+ @semaphore.tryAcquire(permits)
197
+ else
198
+ @semaphore.tryAcquire(permits,
199
+ timeout,
200
+ java.util.concurrent.TimeUnit::SECONDS)
201
+ end
202
+ end
203
+
204
+ # @!macro semaphore_method_release
205
+ def release(permits = 1)
206
+ unless permits.is_a?(Fixnum) && permits > 0
207
+ fail ArgumentError, 'permits must be an integer greater than zero'
208
+ end
209
+ @semaphore.release(permits)
210
+ true
211
+ end
212
+
213
+ # @!macro semaphore_method_reduce_permits
214
+ def reduce_permits(reduction)
215
+ unless reduction.is_a?(Fixnum) && reduction >= 0
216
+ fail ArgumentError, 'reduction must be an non-negative integer'
217
+ end
218
+ @semaphore.reducePermits(reduction)
219
+ end
220
+ end
221
+
222
+ # @!macro semaphore
223
+ class Semaphore < JavaSemaphore
224
+ end
225
+
226
+ else
227
+
228
+ # @!macro semaphore
229
+ class Semaphore < MutexSemaphore
230
+ end
231
+ end
232
+ end
@@ -8,3 +8,5 @@ require 'concurrent/atomic/cyclic_barrier'
8
8
  require 'concurrent/atomic/count_down_latch'
9
9
  require 'concurrent/atomic/event'
10
10
  require 'concurrent/atomic/synchronization'
11
+ require 'concurrent/atomic/semaphore'
12
+ require 'concurrent/atomic/thread_local_var'
@@ -49,8 +49,6 @@ module Concurrent
49
49
  # * `:discard`: Discard the task and return false.
50
50
  # * `:caller_runs`: Execute the task on the calling thread.
51
51
  #
52
- # {include:file:doc/thread_pools.md}
53
- #
54
52
  # @note When running on the JVM (JRuby) this class will inherit from `JavaThreadPoolExecutor`.
55
53
  # On all other platforms it will inherit from `RubyThreadPoolExecutor`.
56
54
  #
@@ -55,10 +55,10 @@ module Concurrent
55
55
  @queue.push(Task.new(time, args, task))
56
56
  @timer_executor.post(&method(:process_tasks))
57
57
  end
58
-
59
- true
60
58
  end
61
59
 
60
+ @condition.signal
61
+ true
62
62
  end
63
63
 
64
64
  # For a timer, #kill is like an orderly shutdown, except we need to manually
@@ -129,8 +129,20 @@ module Concurrent
129
129
  interval = task.time - Time.now.to_f
130
130
 
131
131
  if interval <= 0
132
+ # We need to remove the task from the queue before passing
133
+ # it to the executor, to avoid race conditions where we pass
134
+ # the peek'ed task to the executor and then pop a different
135
+ # one that's been added in the meantime.
136
+ #
137
+ # Note that there's no race condition between the peek and
138
+ # this pop - this pop could retrieve a different task from
139
+ # the peek, but that task would be due to fire now anyway
140
+ # (because @queue is a priority queue, and this thread is
141
+ # the only reader, so whatever timer is at the head of the
142
+ # queue now must have the same pop time, or a closer one, as
143
+ # when we peeked).
144
+ task = mutex.synchronize { @queue.pop }
132
145
  @task_executor.post(*task.args, &task.op)
133
- mutex.synchronize { @queue.pop }
134
146
  else
135
147
  mutex.synchronize do
136
148
  @condition.wait(mutex, [interval, 60].min)
@@ -1,4 +1,3 @@
1
1
  module Concurrent
2
- VERSION = '0.8.0.pre1'
3
- EXT_VERSION = '0.1.0.pre1'
2
+ VERSION = '0.8.0.pre2'
4
3
  end
@@ -1,5 +1,5 @@
1
1
  module Concurrent
2
-
2
+
3
3
  @@c_ext_loaded ||= false
4
4
  @@java_ext_loaded ||= false
5
5
 
@@ -8,25 +8,30 @@ module Concurrent
8
8
  defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
9
9
  end
10
10
 
11
+ # @!visibility private
12
+ def self.jruby?
13
+ RUBY_PLATFORM == 'java'
14
+ end
15
+
11
16
  if allow_c_extensions? && !@@c_ext_loaded
12
17
  begin
13
- require 'concurrent_ruby_ext'
18
+ require 'concurrent/extension'
14
19
  @@c_ext_loaded = true
15
20
  rescue LoadError
16
21
  # may be a Windows cross-compiled native gem
17
22
  begin
18
- require "#{RUBY_VERSION[0..2]}/concurrent_ruby_ext"
23
+ require "#{RUBY_VERSION[0..2]}/concurrent/extension"
19
24
  @@c_ext_loaded = true
20
25
  rescue LoadError
21
26
  warn 'Performance on MRI may be improved with the concurrent-ruby-ext gem. Please see http://concurrent-ruby.com'
22
27
  end
23
28
  end
24
- elsif RUBY_PLATFORM == 'java' && !@@java_ext_loaded
29
+ elsif jruby? && !@@java_ext_loaded
25
30
  begin
26
31
  require 'concurrent_ruby_ext'
27
32
  @@java_ext_loaded = true
28
33
  rescue LoadError
29
- #warn 'Attempted to load Java extensions on unsupported platform. Continuing with pure-Ruby.'
34
+ warn 'Attempted to load Java extensions on unsupported platform. Continuing with pure-Ruby.'
30
35
  end
31
36
  end
32
37
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: concurrent-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0.pre1
4
+ version: 0.8.0.pre2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jerry D'Antonio
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-15 00:00:00.000000000 Z
11
+ date: 2015-01-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ref
@@ -87,6 +87,7 @@ files:
87
87
  - lib/concurrent/atomic/count_down_latch.rb
88
88
  - lib/concurrent/atomic/cyclic_barrier.rb
89
89
  - lib/concurrent/atomic/event.rb
90
+ - lib/concurrent/atomic/semaphore.rb
90
91
  - lib/concurrent/atomic/synchronization.rb
91
92
  - lib/concurrent/atomic/thread_local_var.rb
92
93
  - lib/concurrent/atomic_reference/concurrent_update_error.rb