concurrent-ruby 0.6.0.pre.1 → 0.6.0.pre.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +16 -0
- data/lib/concurrent.rb +9 -29
- data/lib/concurrent/{actor.rb → actor/actor.rb} +3 -3
- data/lib/concurrent/actor/actor_context.rb +77 -0
- data/lib/concurrent/actor/actor_ref.rb +67 -0
- data/lib/concurrent/{postable.rb → actor/postable.rb} +1 -1
- data/lib/concurrent/actor/simple_actor_ref.rb +94 -0
- data/lib/concurrent/actors.rb +5 -0
- data/lib/concurrent/agent.rb +81 -47
- data/lib/concurrent/async.rb +35 -35
- data/lib/concurrent/atomic/atomic_boolean.rb +157 -0
- data/lib/concurrent/atomic/atomic_fixnum.rb +170 -0
- data/lib/concurrent/{condition.rb → atomic/condition.rb} +0 -0
- data/lib/concurrent/{copy_on_notify_observer_set.rb → atomic/copy_on_notify_observer_set.rb} +48 -13
- data/lib/concurrent/{copy_on_write_observer_set.rb → atomic/copy_on_write_observer_set.rb} +41 -20
- data/lib/concurrent/atomic/count_down_latch.rb +116 -0
- data/lib/concurrent/atomic/cyclic_barrier.rb +106 -0
- data/lib/concurrent/atomic/event.rb +103 -0
- data/lib/concurrent/{thread_local_var.rb → atomic/thread_local_var.rb} +0 -0
- data/lib/concurrent/atomics.rb +9 -0
- data/lib/concurrent/channel/buffered_channel.rb +6 -4
- data/lib/concurrent/channel/channel.rb +30 -2
- data/lib/concurrent/channel/unbuffered_channel.rb +2 -2
- data/lib/concurrent/channel/waitable_list.rb +3 -1
- data/lib/concurrent/channels.rb +5 -0
- data/lib/concurrent/{channel → collection}/blocking_ring_buffer.rb +16 -5
- data/lib/concurrent/collection/priority_queue.rb +305 -0
- data/lib/concurrent/{channel → collection}/ring_buffer.rb +6 -1
- data/lib/concurrent/collections.rb +3 -0
- data/lib/concurrent/configuration.rb +68 -19
- data/lib/concurrent/dataflow.rb +9 -9
- data/lib/concurrent/delay.rb +21 -13
- data/lib/concurrent/dereferenceable.rb +40 -33
- data/lib/concurrent/exchanger.rb +3 -0
- data/lib/concurrent/{cached_thread_pool.rb → executor/cached_thread_pool.rb} +8 -9
- data/lib/concurrent/executor/executor.rb +222 -0
- data/lib/concurrent/{fixed_thread_pool.rb → executor/fixed_thread_pool.rb} +6 -7
- data/lib/concurrent/{immediate_executor.rb → executor/immediate_executor.rb} +5 -5
- data/lib/concurrent/executor/java_cached_thread_pool.rb +31 -0
- data/lib/concurrent/{java_fixed_thread_pool.rb → executor/java_fixed_thread_pool.rb} +7 -11
- data/lib/concurrent/executor/java_single_thread_executor.rb +21 -0
- data/lib/concurrent/{java_thread_pool_executor.rb → executor/java_thread_pool_executor.rb} +66 -77
- data/lib/concurrent/executor/one_by_one.rb +65 -0
- data/lib/concurrent/{per_thread_executor.rb → executor/per_thread_executor.rb} +4 -4
- data/lib/concurrent/executor/ruby_cached_thread_pool.rb +29 -0
- data/lib/concurrent/{ruby_fixed_thread_pool.rb → executor/ruby_fixed_thread_pool.rb} +5 -4
- data/lib/concurrent/executor/ruby_single_thread_executor.rb +72 -0
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +282 -0
- data/lib/concurrent/{ruby_thread_pool_worker.rb → executor/ruby_thread_pool_worker.rb} +6 -6
- data/lib/concurrent/{safe_task_executor.rb → executor/safe_task_executor.rb} +20 -13
- data/lib/concurrent/executor/single_thread_executor.rb +35 -0
- data/lib/concurrent/executor/thread_pool_executor.rb +68 -0
- data/lib/concurrent/executor/timer_set.rb +138 -0
- data/lib/concurrent/executors.rb +9 -0
- data/lib/concurrent/future.rb +39 -40
- data/lib/concurrent/ivar.rb +22 -15
- data/lib/concurrent/mvar.rb +2 -1
- data/lib/concurrent/obligation.rb +9 -3
- data/lib/concurrent/observable.rb +33 -0
- data/lib/concurrent/options_parser.rb +46 -0
- data/lib/concurrent/promise.rb +23 -24
- data/lib/concurrent/scheduled_task.rb +21 -45
- data/lib/concurrent/timer_task.rb +204 -126
- data/lib/concurrent/tvar.rb +1 -1
- data/lib/concurrent/utilities.rb +3 -36
- data/lib/concurrent/{processor_count.rb → utility/processor_count.rb} +1 -1
- data/lib/concurrent/utility/timeout.rb +36 -0
- data/lib/concurrent/utility/timer.rb +21 -0
- data/lib/concurrent/version.rb +1 -1
- data/lib/concurrent_ruby_ext.bundle +0 -0
- data/spec/concurrent/{actor_context_spec.rb → actor/actor_context_spec.rb} +0 -8
- data/spec/concurrent/{actor_ref_shared.rb → actor/actor_ref_shared.rb} +9 -59
- data/spec/concurrent/{actor_spec.rb → actor/actor_spec.rb} +43 -41
- data/spec/concurrent/{postable_shared.rb → actor/postable_shared.rb} +0 -0
- data/spec/concurrent/actor/simple_actor_ref_spec.rb +135 -0
- data/spec/concurrent/agent_spec.rb +160 -71
- data/spec/concurrent/atomic/atomic_boolean_spec.rb +172 -0
- data/spec/concurrent/atomic/atomic_fixnum_spec.rb +186 -0
- data/spec/concurrent/{condition_spec.rb → atomic/condition_spec.rb} +2 -2
- data/spec/concurrent/{copy_on_notify_observer_set_spec.rb → atomic/copy_on_notify_observer_set_spec.rb} +0 -0
- data/spec/concurrent/{copy_on_write_observer_set_spec.rb → atomic/copy_on_write_observer_set_spec.rb} +0 -0
- data/spec/concurrent/atomic/count_down_latch_spec.rb +151 -0
- data/spec/concurrent/atomic/cyclic_barrier_spec.rb +248 -0
- data/spec/concurrent/{event_spec.rb → atomic/event_spec.rb} +18 -3
- data/spec/concurrent/{observer_set_shared.rb → atomic/observer_set_shared.rb} +15 -6
- data/spec/concurrent/{thread_local_var_spec.rb → atomic/thread_local_var_spec.rb} +0 -0
- data/spec/concurrent/channel/buffered_channel_spec.rb +1 -1
- data/spec/concurrent/channel/channel_spec.rb +6 -4
- data/spec/concurrent/channel/probe_spec.rb +37 -9
- data/spec/concurrent/channel/unbuffered_channel_spec.rb +2 -2
- data/spec/concurrent/{channel → collection}/blocking_ring_buffer_spec.rb +0 -0
- data/spec/concurrent/collection/priority_queue_spec.rb +317 -0
- data/spec/concurrent/{channel → collection}/ring_buffer_spec.rb +0 -0
- data/spec/concurrent/configuration_spec.rb +4 -70
- data/spec/concurrent/dereferenceable_shared.rb +5 -4
- data/spec/concurrent/exchanger_spec.rb +10 -5
- data/spec/concurrent/{cached_thread_pool_shared.rb → executor/cached_thread_pool_shared.rb} +15 -37
- data/spec/concurrent/{fixed_thread_pool_shared.rb → executor/fixed_thread_pool_shared.rb} +0 -0
- data/spec/concurrent/{global_thread_pool_shared.rb → executor/global_thread_pool_shared.rb} +10 -8
- data/spec/concurrent/{immediate_executor_spec.rb → executor/immediate_executor_spec.rb} +0 -0
- data/spec/concurrent/{java_cached_thread_pool_spec.rb → executor/java_cached_thread_pool_spec.rb} +1 -21
- data/spec/concurrent/{java_fixed_thread_pool_spec.rb → executor/java_fixed_thread_pool_spec.rb} +0 -0
- data/spec/concurrent/executor/java_single_thread_executor_spec.rb +21 -0
- data/spec/concurrent/{java_thread_pool_executor_spec.rb → executor/java_thread_pool_executor_spec.rb} +0 -0
- data/spec/concurrent/{per_thread_executor_spec.rb → executor/per_thread_executor_spec.rb} +0 -4
- data/spec/concurrent/{ruby_cached_thread_pool_spec.rb → executor/ruby_cached_thread_pool_spec.rb} +1 -1
- data/spec/concurrent/{ruby_fixed_thread_pool_spec.rb → executor/ruby_fixed_thread_pool_spec.rb} +0 -0
- data/spec/concurrent/executor/ruby_single_thread_executor_spec.rb +18 -0
- data/spec/concurrent/{ruby_thread_pool_executor_spec.rb → executor/ruby_thread_pool_executor_spec.rb} +12 -24
- data/spec/concurrent/executor/safe_task_executor_spec.rb +103 -0
- data/spec/concurrent/{thread_pool_class_cast_spec.rb → executor/thread_pool_class_cast_spec.rb} +12 -0
- data/spec/concurrent/{thread_pool_executor_shared.rb → executor/thread_pool_executor_shared.rb} +0 -0
- data/spec/concurrent/{thread_pool_shared.rb → executor/thread_pool_shared.rb} +84 -119
- data/spec/concurrent/executor/timer_set_spec.rb +183 -0
- data/spec/concurrent/future_spec.rb +12 -0
- data/spec/concurrent/ivar_spec.rb +11 -1
- data/spec/concurrent/observable_shared.rb +173 -0
- data/spec/concurrent/observable_spec.rb +51 -0
- data/spec/concurrent/options_parser_spec.rb +71 -0
- data/spec/concurrent/runnable_shared.rb +6 -0
- data/spec/concurrent/scheduled_task_spec.rb +60 -40
- data/spec/concurrent/timer_task_spec.rb +130 -144
- data/spec/concurrent/{processor_count_spec.rb → utility/processor_count_spec.rb} +0 -0
- data/spec/concurrent/{utilities_spec.rb → utility/timeout_spec.rb} +0 -0
- data/spec/concurrent/utility/timer_spec.rb +52 -0
- metadata +147 -108
- data/lib/concurrent/actor_context.rb +0 -31
- data/lib/concurrent/actor_ref.rb +0 -39
- data/lib/concurrent/atomic.rb +0 -121
- data/lib/concurrent/channel/probe.rb +0 -19
- data/lib/concurrent/count_down_latch.rb +0 -60
- data/lib/concurrent/event.rb +0 -80
- data/lib/concurrent/java_cached_thread_pool.rb +0 -45
- data/lib/concurrent/ruby_cached_thread_pool.rb +0 -37
- data/lib/concurrent/ruby_thread_pool_executor.rb +0 -268
- data/lib/concurrent/simple_actor_ref.rb +0 -124
- data/lib/concurrent/thread_pool_executor.rb +0 -30
- data/spec/concurrent/atomic_spec.rb +0 -201
- data/spec/concurrent/count_down_latch_spec.rb +0 -125
- data/spec/concurrent/safe_task_executor_spec.rb +0 -58
- data/spec/concurrent/simple_actor_ref_spec.rb +0 -219
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 873e9e671b06398c80e8c918e5e2ba7c0d75564e
|
4
|
+
data.tar.gz: 69963957bf5e06a7a5b7ca21a678039496874c35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 92bb87ece26412064c4d9697b1d92ce1cc4963d74b6f9c32b32db091d216ff8655bbd1961322db8618666a571f4f9559cf7823aa9db1fa33687b2c45db1bb57a
|
7
|
+
data.tar.gz: 0d2645e119b50b88748b6131bcc984e127399bb9f5988fdfaa095fe915149ad03e60ab530523736006d3eb71a1b86821b6a889c032e9bd3c5d6a04a821d8ca2d
|
data/README.md
CHANGED
@@ -33,6 +33,20 @@ The design goals of this gem are:
|
|
33
33
|
</tr>
|
34
34
|
</table>
|
35
35
|
|
36
|
+
### Install
|
37
|
+
|
38
|
+
```shell
|
39
|
+
gem install concurrent-ruby
|
40
|
+
```
|
41
|
+
or add the following line to Gemfile:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
gem 'concurrent-ruby'
|
45
|
+
```
|
46
|
+
and run `bundle install` from your shell.
|
47
|
+
|
48
|
+
*NOTE: There is an old gem from 2007 called "concurrent" that does not appear to be under active development. That isn't us. Please do not run* `gem install concurrent`*. It is not the droid you are looking for.*
|
49
|
+
|
36
50
|
## Features & Documentation
|
37
51
|
|
38
52
|
Please see the [Concurrent Ruby Wiki](https://github.com/jdantonio/concurrent-ruby/wiki)
|
@@ -128,6 +142,8 @@ ibm.value #=> 187.57
|
|
128
142
|
* [Michele Della Torre](https://github.com/mighe)
|
129
143
|
* [Chris Seaton](https://github.com/chrisseaton)
|
130
144
|
* [Lucas Allan](https://github.com/lucasallan)
|
145
|
+
* [Petr Chalupa](https://github.com/pitr-ch)
|
146
|
+
* [Ravil Bayramgalin](https://github.com/brainopia)
|
131
147
|
* [Giuseppe Capizzi](https://github.com/gcapizzi)
|
132
148
|
* [Brian Shirai](https://github.com/brixen)
|
133
149
|
* [Chip Miller](https://github.com/chip-miller)
|
data/lib/concurrent.rb
CHANGED
@@ -1,52 +1,32 @@
|
|
1
1
|
require 'concurrent/version'
|
2
2
|
require 'concurrent/configuration'
|
3
3
|
|
4
|
-
require 'concurrent/
|
5
|
-
require 'concurrent/
|
6
|
-
require 'concurrent/
|
7
|
-
require 'concurrent/
|
8
|
-
require 'concurrent/
|
9
|
-
require 'concurrent/
|
10
|
-
require 'concurrent/ivar'
|
4
|
+
require 'concurrent/atomics'
|
5
|
+
require 'concurrent/actors'
|
6
|
+
require 'concurrent/channels'
|
7
|
+
require 'concurrent/collections'
|
8
|
+
require 'concurrent/executors'
|
9
|
+
require 'concurrent/utilities'
|
11
10
|
|
12
|
-
require 'concurrent/actor'
|
13
11
|
require 'concurrent/agent'
|
14
12
|
require 'concurrent/async'
|
15
13
|
require 'concurrent/dataflow'
|
16
14
|
require 'concurrent/delay'
|
17
15
|
require 'concurrent/dereferenceable'
|
18
|
-
require 'concurrent/event'
|
19
16
|
require 'concurrent/exchanger'
|
20
17
|
require 'concurrent/future'
|
18
|
+
require 'concurrent/ivar'
|
21
19
|
require 'concurrent/mvar'
|
22
20
|
require 'concurrent/obligation'
|
23
|
-
require 'concurrent/
|
24
|
-
require 'concurrent/
|
21
|
+
require 'concurrent/observable'
|
22
|
+
require 'concurrent/options_parser'
|
25
23
|
require 'concurrent/promise'
|
26
24
|
require 'concurrent/runnable'
|
27
25
|
require 'concurrent/scheduled_task'
|
28
26
|
require 'concurrent/stoppable'
|
29
27
|
require 'concurrent/supervisor'
|
30
|
-
require 'concurrent/thread_local_var'
|
31
28
|
require 'concurrent/timer_task'
|
32
29
|
require 'concurrent/tvar'
|
33
|
-
require 'concurrent/utilities'
|
34
|
-
|
35
|
-
require 'concurrent/channel/probe'
|
36
|
-
require 'concurrent/channel/channel'
|
37
|
-
require 'concurrent/channel/unbuffered_channel'
|
38
|
-
require 'concurrent/channel/buffered_channel'
|
39
|
-
require 'concurrent/channel/ring_buffer'
|
40
|
-
require 'concurrent/channel/blocking_ring_buffer'
|
41
|
-
|
42
|
-
require 'concurrent/actor_context'
|
43
|
-
require 'concurrent/simple_actor_ref'
|
44
|
-
|
45
|
-
require 'concurrent/cached_thread_pool'
|
46
|
-
require 'concurrent/fixed_thread_pool'
|
47
|
-
require 'concurrent/immediate_executor'
|
48
|
-
require 'concurrent/per_thread_executor'
|
49
|
-
require 'concurrent/thread_pool_executor'
|
50
30
|
|
51
31
|
# Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell,
|
52
32
|
# F#, C#, Java, and classic concurrency patterns.
|
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'thread'
|
2
2
|
require 'observer'
|
3
3
|
|
4
|
-
require 'concurrent/
|
4
|
+
require 'concurrent/actor/postable'
|
5
|
+
require 'concurrent/atomic/event'
|
5
6
|
require 'concurrent/obligation'
|
6
|
-
require 'concurrent/postable'
|
7
7
|
require 'concurrent/runnable'
|
8
8
|
|
9
9
|
module Concurrent
|
@@ -123,7 +123,7 @@ module Concurrent
|
|
123
123
|
#
|
124
124
|
# @see http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html
|
125
125
|
class Actor
|
126
|
-
include Observable
|
126
|
+
include ::Observable
|
127
127
|
include Postable
|
128
128
|
include Runnable
|
129
129
|
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'concurrent/actor/simple_actor_ref'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
|
5
|
+
# Actor-based concurrency is all the rage in some circles. Originally described in
|
6
|
+
# 1973, the actor model is a paradigm for creating asynchronous, concurrent objects
|
7
|
+
# that is becoming increasingly popular. Much has changed since actors were first
|
8
|
+
# written about four decades ago, which has led to a serious fragmentation within
|
9
|
+
# the actor community. There is *no* universally accepted, strict definition of
|
10
|
+
# "actor" and actor implementations differ widely between languages and libraries.
|
11
|
+
#
|
12
|
+
# A good definition of "actor" is:
|
13
|
+
#
|
14
|
+
# An independent, concurrent, single-purpose, computational entity that communicates exclusively via message passing.
|
15
|
+
#
|
16
|
+
# The actor framework in this library is heavily influenced by the Akka toolkit,
|
17
|
+
# with additional inspiration from Erlang and Scala. Unlike many of the abstractions
|
18
|
+
# in this library, `ActorContext` takes an *object-oriented* approach to asynchronous
|
19
|
+
# concurrency, rather than a *functional programming* approach.
|
20
|
+
#
|
21
|
+
# Creating an actor class is achieved by including the `ActorContext` module
|
22
|
+
# within a standard Ruby class. One `ActorContext` is mixed in, however, everything
|
23
|
+
# changes. Objects of the class can no longer be instanced with the `#new` method.
|
24
|
+
# Instead, the various factor methods, such as `#spawn`, must be used. These factory
|
25
|
+
# methods do not return direct references to the actor object. Instead they return
|
26
|
+
# objects of one of the `ActorRef` subclasses. The `ActorRef` objects encapsulate
|
27
|
+
# actor objects. This encapsulation is necessary to prevent synchronous method calls
|
28
|
+
# froom being made against the actors. It also allows the messaging and lifecycle
|
29
|
+
# behavior to be implemented within the `ActorRef` subclasses, allowing for better
|
30
|
+
# separation of responsibility.
|
31
|
+
#
|
32
|
+
# @see Concurrent::ActorRef
|
33
|
+
#
|
34
|
+
# @see http://akka.io/
|
35
|
+
# @see http://www.erlang.org/doc/getting_started/conc_prog.html
|
36
|
+
# @see http://www.scala-lang.org/api/current/index.html#scala.actors.Actor
|
37
|
+
#
|
38
|
+
# @see http://doc.akka.io/docs/akka/snapshot/general/supervision.html#What_Restarting_Means What Restarting Means
|
39
|
+
# @see http://doc.akka.io/docs/akka/snapshot/general/supervision.html#What_Lifecycle_Monitoring_Means What Lifecycle Monitoring Means
|
40
|
+
module ActorContext
|
41
|
+
|
42
|
+
# Callback method called by the `ActorRef` which encapsulates the actor instance.
|
43
|
+
def on_start
|
44
|
+
end
|
45
|
+
|
46
|
+
# Callback method called by the `ActorRef` which encapsulates the actor instance.
|
47
|
+
def on_shutdown
|
48
|
+
end
|
49
|
+
|
50
|
+
# Callback method called by the `ActorRef` which encapsulates the actor instance.
|
51
|
+
#
|
52
|
+
# @param [Time] time the date/time at which the error occurred
|
53
|
+
# @param [Array] message the message that caused the error
|
54
|
+
# @param [Exception] exception the exception object that was raised
|
55
|
+
def on_error(time, message, exception)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.included(base)
|
59
|
+
|
60
|
+
class << base
|
61
|
+
|
62
|
+
# Create a single, unregistered actor. The actor will run on its own, dedicated
|
63
|
+
# thread. The thread will be started the first time a message is post to the actor.
|
64
|
+
# Should the thread ever die it will be restarted the next time a message is post.
|
65
|
+
#
|
66
|
+
# @param [Hash] opts the options defining actor behavior
|
67
|
+
# @option opts [Array] :args (`nil`) arguments to be passed to the actor constructor
|
68
|
+
#
|
69
|
+
# @return [SimpleActorRef] the `ActorRef` encapsulating the actor
|
70
|
+
def spawn(opts = {})
|
71
|
+
args = opts.fetch(:args, [])
|
72
|
+
Concurrent::SimpleActorRef.new(self.new(*args), opts)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Concurrent
|
2
|
+
|
3
|
+
# Base class for classes that encapsulate `ActorContext` objects.
|
4
|
+
#
|
5
|
+
# @see Concurrent::ActorContext
|
6
|
+
module ActorRef
|
7
|
+
|
8
|
+
# @!method post(*msg, &block)
|
9
|
+
#
|
10
|
+
# Send a message and return a future which will eventually be updated
|
11
|
+
# with the result of the operation. An optional callback block can be
|
12
|
+
# given which will be called once the operation is complete. Although
|
13
|
+
# it is possible to use a callback block and also interrogate the
|
14
|
+
# returned future it is a good practice to do one or the other.
|
15
|
+
#
|
16
|
+
# @param [Array] msg One or more elements of the message
|
17
|
+
#
|
18
|
+
# @yield a callback operation to be performed when the operation is complete.
|
19
|
+
# @yieldparam [Time] time the date/time at which the error occurred
|
20
|
+
# @yieldparam [Object] result the result of message processing or `nil` on error
|
21
|
+
# @yieldparam [Exception] exception the exception object that was raised or `nil` on success
|
22
|
+
#
|
23
|
+
# @return [IVar] a future that will eventually contain the result of message processing
|
24
|
+
|
25
|
+
# @!method post!(timeout, *msg)
|
26
|
+
# Send a message synchronously and block awaiting the response.
|
27
|
+
#
|
28
|
+
# @param [Integer] timeout the maximum number of seconds to block waiting
|
29
|
+
# for a response
|
30
|
+
# @param [Array] msg One or more elements of the message
|
31
|
+
#
|
32
|
+
# @return [Object] the result of successful message processing
|
33
|
+
#
|
34
|
+
# @raise [Concurrent::TimeoutError] if a timeout occurs
|
35
|
+
# @raise [Exception] an exception which occurred during message processing
|
36
|
+
|
37
|
+
# @!method running?()
|
38
|
+
# Is the actor running and processing messages?
|
39
|
+
# @return [Boolean] `true` if running else `false`
|
40
|
+
|
41
|
+
# @!method shutdown?()
|
42
|
+
# Is the actor shutdown and no longer processing messages?
|
43
|
+
# @return [Boolean] `true` if shutdown else `false`
|
44
|
+
|
45
|
+
# @!method shutdown()
|
46
|
+
# Shutdown the actor, gracefully exit all threads, and stop processing messages.
|
47
|
+
# @return [Boolean] `true` if shutdown is successful else `false`
|
48
|
+
|
49
|
+
# @!method join(limit = nil)
|
50
|
+
# Suspend the current thread until the actor has been shutdown
|
51
|
+
# @param [Integer] limit the maximum number of seconds to block waiting for the
|
52
|
+
# actor to shutdown. Block indefinitely when `nil` or not given
|
53
|
+
# @return [Boolean] `true` if the actor shutdown before the limit expired else `false`
|
54
|
+
# @see http://www.ruby-doc.org/core-2.1.1/Thread.html#method-i-join
|
55
|
+
|
56
|
+
# Send a message and return. It's a fire-and-forget interaction.
|
57
|
+
#
|
58
|
+
# @param [Object] message a single value representing the message to be sent
|
59
|
+
# to the actor or an array of multiple message components
|
60
|
+
#
|
61
|
+
# @return [ActorRef] self
|
62
|
+
def <<(message)
|
63
|
+
post(*message)
|
64
|
+
self
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'concurrent/actor/actor_ref'
|
2
|
+
require 'concurrent/atomic/event'
|
3
|
+
require 'concurrent/executor/single_thread_executor'
|
4
|
+
require 'concurrent/ivar'
|
5
|
+
|
6
|
+
module Concurrent
|
7
|
+
|
8
|
+
class SimpleActorRef
|
9
|
+
include ActorRef
|
10
|
+
|
11
|
+
def initialize(actor, opts = {})
|
12
|
+
@actor = actor
|
13
|
+
@mutex = Mutex.new
|
14
|
+
@one_by_one = OneByOne.new
|
15
|
+
@executor = OptionsParser::get_executor_from(opts)
|
16
|
+
@stop_event = Event.new
|
17
|
+
@reset_on_error = opts.fetch(:reset_on_error, true)
|
18
|
+
@exception_class = opts.fetch(:rescue_exception, false) ? Exception : StandardError
|
19
|
+
@args = opts.fetch(:args, []) if @reset_on_error
|
20
|
+
|
21
|
+
@actor.define_singleton_method(:shutdown, &method(:set_stop_event))
|
22
|
+
@actor.on_start
|
23
|
+
end
|
24
|
+
|
25
|
+
def running?
|
26
|
+
not @stop_event.set?
|
27
|
+
end
|
28
|
+
|
29
|
+
def shutdown?
|
30
|
+
@stop_event.set?
|
31
|
+
end
|
32
|
+
|
33
|
+
def post(*msg, &block)
|
34
|
+
raise ArgumentError.new('message cannot be empty') if msg.empty?
|
35
|
+
ivar = IVar.new
|
36
|
+
@one_by_one.post(@executor, Message.new(msg, ivar, block), &method(:process_message))
|
37
|
+
ivar
|
38
|
+
end
|
39
|
+
|
40
|
+
def post!(timeout, *msg)
|
41
|
+
raise Concurrent::TimeoutError unless timeout.nil? || timeout >= 0
|
42
|
+
ivar = self.post(*msg)
|
43
|
+
ivar.value(timeout)
|
44
|
+
if ivar.incomplete?
|
45
|
+
raise Concurrent::TimeoutError
|
46
|
+
elsif ivar.reason
|
47
|
+
raise ivar.reason
|
48
|
+
end
|
49
|
+
ivar.value
|
50
|
+
end
|
51
|
+
|
52
|
+
def shutdown
|
53
|
+
@mutex.synchronize do
|
54
|
+
return if shutdown?
|
55
|
+
@actor.on_shutdown
|
56
|
+
@stop_event.set
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def join(limit = nil)
|
61
|
+
@stop_event.wait(limit)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
Message = Struct.new(:payload, :ivar, :callback)
|
67
|
+
|
68
|
+
def set_stop_event
|
69
|
+
@stop_event.set
|
70
|
+
end
|
71
|
+
|
72
|
+
def process_message(message)
|
73
|
+
result = ex = nil
|
74
|
+
|
75
|
+
begin
|
76
|
+
result = @actor.receive(*message.payload)
|
77
|
+
rescue @exception_class => ex
|
78
|
+
@actor.on_error(Time.now, message.payload, ex)
|
79
|
+
if @reset_on_error
|
80
|
+
@mutex.synchronize{ @actor = @actor.class.new(*@args) }
|
81
|
+
end
|
82
|
+
ensure
|
83
|
+
now = Time.now
|
84
|
+
message.ivar.complete(ex.nil?, result, ex)
|
85
|
+
|
86
|
+
begin
|
87
|
+
message.callback.call(now, result, ex) if message.callback
|
88
|
+
rescue @exception_class => ex
|
89
|
+
# suppress
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/concurrent/agent.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
require 'thread'
|
2
|
-
require 'observer'
|
3
2
|
|
4
|
-
require 'concurrent/configuration'
|
5
3
|
require 'concurrent/dereferenceable'
|
6
|
-
require 'concurrent/
|
4
|
+
require 'concurrent/observable'
|
5
|
+
require 'concurrent/options_parser'
|
6
|
+
require 'concurrent/utility/timeout'
|
7
7
|
|
8
8
|
module Concurrent
|
9
9
|
|
10
10
|
# An agent is a single atomic value that represents an identity. The current value
|
11
|
-
# of the agent can be requested at any time (
|
12
|
-
# the global thread pool. Consumers can
|
11
|
+
# of the agent can be requested at any time (`#deref`). Each agent has a work queue and operates on
|
12
|
+
# the global thread pool. Consumers can `#post` code blocks to the agent. The code block (function)
|
13
13
|
# will receive the current value of the agent as its sole parameter. The return value of the block
|
14
14
|
# will become the new value of the agent. Agents support two error handling modes: fail and continue.
|
15
15
|
# A good example of an agent is a shared incrementing counter, such as the score in a video game.
|
@@ -34,13 +34,13 @@ module Concurrent
|
|
34
34
|
# @return [Fixnum] the maximum number of seconds before an update is cancelled
|
35
35
|
class Agent
|
36
36
|
include Dereferenceable
|
37
|
-
include
|
37
|
+
include Concurrent::Observable
|
38
38
|
|
39
39
|
# The default timeout value (in seconds); used when no timeout option
|
40
40
|
# is given at initialization
|
41
41
|
TIMEOUT = 5
|
42
42
|
|
43
|
-
attr_reader :timeout
|
43
|
+
attr_reader :timeout, :task_executor, :operation_executor
|
44
44
|
|
45
45
|
# Initialize a new Agent with the given initial value and provided options.
|
46
46
|
#
|
@@ -49,32 +49,34 @@ module Concurrent
|
|
49
49
|
#
|
50
50
|
# @option opts [Fixnum] :timeout (TIMEOUT) maximum number of seconds before an update is cancelled
|
51
51
|
#
|
52
|
-
# @option opts [Boolean] :operation (false) when
|
53
|
-
# operation pool (for long-running operations), when
|
52
|
+
# @option opts [Boolean] :operation (false) when `true` will execute the future on the global
|
53
|
+
# operation pool (for long-running operations), when `false` will execute the future on the
|
54
54
|
# global task pool (for short-running tasks)
|
55
55
|
# @option opts [object] :executor when provided will run all operations on
|
56
56
|
# this executor rather than the global thread pool (overrides :operation)
|
57
57
|
#
|
58
|
-
# @option opts [String] :dup_on_deref (false) call
|
59
|
-
# @option opts [String] :freeze_on_deref (false) call
|
60
|
-
# @option opts [String] :copy_on_deref (nil) call the given
|
58
|
+
# @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
|
59
|
+
# @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
|
60
|
+
# @option opts [String] :copy_on_deref (nil) call the given `Proc` passing the internal value and
|
61
61
|
# returning the value returned from the proc
|
62
62
|
def initialize(initial, opts = {})
|
63
|
-
@value
|
64
|
-
@rescuers
|
65
|
-
@validator
|
66
|
-
@timeout
|
67
|
-
|
68
|
-
@
|
63
|
+
@value = initial
|
64
|
+
@rescuers = []
|
65
|
+
@validator = Proc.new { |result| true }
|
66
|
+
@timeout = opts.fetch(:timeout, TIMEOUT).freeze
|
67
|
+
self.observers = CopyOnWriteObserverSet.new
|
68
|
+
@one_by_one = OneByOne.new
|
69
|
+
@task_executor = OptionsParser.get_task_executor_from(opts)
|
70
|
+
@operation_executor = OptionsParser.get_operation_executor_from(opts)
|
69
71
|
init_mutex
|
70
72
|
set_deref_options(opts)
|
71
73
|
end
|
72
74
|
|
73
75
|
# Specifies a block operation to be performed when an update operation raises
|
74
76
|
# an exception. Rescue blocks will be checked in order they were added. The first
|
75
|
-
# block for which the raised exception "is-a" subclass of the given
|
76
|
-
# be called. If no
|
77
|
-
# This behavior is intended to be identical to Ruby's
|
77
|
+
# block for which the raised exception "is-a" subclass of the given `clazz` will
|
78
|
+
# be called. If no `clazz` is given the block will match any caught exception.
|
79
|
+
# This behavior is intended to be identical to Ruby's `begin/rescue/end` behavior.
|
78
80
|
# Any number of rescue handlers can be added. If no rescue handlers are added then
|
79
81
|
# caught exceptions will be suppressed.
|
80
82
|
#
|
@@ -111,53 +113,78 @@ module Concurrent
|
|
111
113
|
# @yieldparam [Object] value the result of the last update operation
|
112
114
|
# @yieldreturn [Boolean] true if the value is valid else false
|
113
115
|
def validate(&block)
|
114
|
-
|
116
|
+
unless block.nil?
|
117
|
+
mutex.lock
|
118
|
+
@validator = block
|
119
|
+
mutex.unlock
|
120
|
+
end
|
115
121
|
self
|
116
122
|
end
|
117
123
|
alias_method :validates, :validate
|
118
124
|
alias_method :validate_with, :validate
|
119
125
|
alias_method :validates_with, :validate
|
120
126
|
|
121
|
-
# Update the current value with the result of the given block operation
|
127
|
+
# Update the current value with the result of the given block operation,
|
128
|
+
# block should not do blocking calls, use #post_off for blocking calls
|
122
129
|
#
|
123
130
|
# @yield the operation to be performed with the current value in order to calculate
|
124
131
|
# the new value
|
125
132
|
# @yieldparam [Object] value the current value
|
126
133
|
# @yieldreturn [Object] the new value
|
134
|
+
# @return [true, nil] nil when no block is given
|
127
135
|
def post(&block)
|
128
|
-
@
|
136
|
+
post_on(@task_executor, &block)
|
129
137
|
end
|
130
138
|
|
131
|
-
# Update the current value with the result of the given block operation
|
139
|
+
# Update the current value with the result of the given block operation,
|
140
|
+
# block can do blocking calls
|
141
|
+
#
|
142
|
+
# @yield the operation to be performed with the current value in order to calculate
|
143
|
+
# the new value
|
144
|
+
# @yieldparam [Object] value the current value
|
145
|
+
# @yieldreturn [Object] the new value
|
146
|
+
# @return [true, nil] nil when no block is given
|
147
|
+
def post_off(&block)
|
148
|
+
post_on(@operation_executor, &block)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Update the current value with the result of the given block operation,
|
152
|
+
# block should not do blocking calls, use #post_off for blocking calls
|
132
153
|
#
|
133
154
|
# @yield the operation to be performed with the current value in order to calculate
|
134
155
|
# the new value
|
135
156
|
# @yieldparam [Object] value the current value
|
136
157
|
# @yieldreturn [Object] the new value
|
137
158
|
def <<(block)
|
138
|
-
|
159
|
+
post(&block)
|
139
160
|
self
|
140
161
|
end
|
141
162
|
|
142
|
-
|
143
|
-
|
163
|
+
# Waits/blocks until all the updates sent before this call are done.
|
164
|
+
#
|
165
|
+
# @param [Numeric] timeout the maximum time in second to wait.
|
166
|
+
# @return [Boolean] false on timeout, true otherwise
|
167
|
+
def await(timeout = nil)
|
168
|
+
done = Event.new
|
169
|
+
post { |val| done.set; val }
|
170
|
+
done.wait timeout
|
144
171
|
end
|
145
172
|
|
146
|
-
|
173
|
+
private
|
147
174
|
|
148
|
-
def
|
149
|
-
|
175
|
+
def post_on(executor, &block)
|
176
|
+
return nil if block.nil?
|
177
|
+
@one_by_one.post(executor) { work(&block) }
|
178
|
+
true
|
150
179
|
end
|
151
180
|
|
152
|
-
private
|
153
|
-
|
154
181
|
# @!visibility private
|
155
182
|
Rescuer = Struct.new(:clazz, :block) # :nodoc:
|
156
183
|
|
157
184
|
# @!visibility private
|
158
185
|
def try_rescue(ex) # :nodoc:
|
159
186
|
rescuer = mutex.synchronize do
|
160
|
-
@rescuers.find{|r| ex.is_a?(r.clazz) }
|
187
|
+
@rescuers.find { |r| ex.is_a?(r.clazz) }
|
161
188
|
end
|
162
189
|
rescuer.block.call(ex) if rescuer
|
163
190
|
rescue Exception => ex
|
@@ -166,24 +193,31 @@ module Concurrent
|
|
166
193
|
|
167
194
|
# @!visibility private
|
168
195
|
def work(&handler) # :nodoc:
|
196
|
+
validator, value = mutex.synchronize { [@validator, @value] }
|
197
|
+
|
169
198
|
begin
|
199
|
+
# FIXME creates second thread
|
200
|
+
result, valid = Concurrent::timeout(@timeout) do
|
201
|
+
result = handler.call(value)
|
202
|
+
[result, validator.call(result)]
|
203
|
+
end
|
204
|
+
rescue Exception => ex
|
205
|
+
exception = ex
|
206
|
+
end
|
170
207
|
|
171
|
-
|
208
|
+
mutex.lock
|
209
|
+
should_notify = if !exception && valid
|
210
|
+
@value = result
|
211
|
+
true
|
212
|
+
end
|
213
|
+
mutex.unlock
|
172
214
|
|
173
|
-
|
174
|
-
result = Concurrent::timeout(@timeout) do
|
175
|
-
handler.call(@value)
|
176
|
-
end
|
177
|
-
if @validator.call(result)
|
178
|
-
@value = result
|
179
|
-
should_notify = true
|
180
|
-
end
|
181
|
-
end
|
215
|
+
if should_notify
|
182
216
|
time = Time.now
|
183
|
-
|
184
|
-
rescue Exception => ex
|
185
|
-
try_rescue(ex)
|
217
|
+
observers.notify_observers { [time, self.value] }
|
186
218
|
end
|
219
|
+
|
220
|
+
try_rescue(exception)
|
187
221
|
end
|
188
222
|
end
|
189
223
|
end
|