concurrent-ruby 0.4.1 → 0.5.0.pre.1

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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +31 -33
  3. data/lib/concurrent.rb +11 -3
  4. data/lib/concurrent/actor.rb +29 -29
  5. data/lib/concurrent/agent.rb +98 -16
  6. data/lib/concurrent/atomic.rb +125 -0
  7. data/lib/concurrent/channel.rb +36 -1
  8. data/lib/concurrent/condition.rb +67 -0
  9. data/lib/concurrent/copy_on_notify_observer_set.rb +80 -0
  10. data/lib/concurrent/copy_on_write_observer_set.rb +94 -0
  11. data/lib/concurrent/count_down_latch.rb +60 -0
  12. data/lib/concurrent/dataflow.rb +85 -0
  13. data/lib/concurrent/dereferenceable.rb +69 -31
  14. data/lib/concurrent/event.rb +27 -21
  15. data/lib/concurrent/future.rb +103 -43
  16. data/lib/concurrent/ivar.rb +78 -0
  17. data/lib/concurrent/mvar.rb +154 -0
  18. data/lib/concurrent/obligation.rb +94 -9
  19. data/lib/concurrent/postable.rb +11 -9
  20. data/lib/concurrent/promise.rb +101 -127
  21. data/lib/concurrent/safe_task_executor.rb +28 -0
  22. data/lib/concurrent/scheduled_task.rb +60 -54
  23. data/lib/concurrent/stoppable.rb +2 -2
  24. data/lib/concurrent/supervisor.rb +36 -29
  25. data/lib/concurrent/thread_local_var.rb +117 -0
  26. data/lib/concurrent/timer_task.rb +28 -30
  27. data/lib/concurrent/utilities.rb +1 -1
  28. data/lib/concurrent/version.rb +1 -1
  29. data/spec/concurrent/agent_spec.rb +121 -230
  30. data/spec/concurrent/atomic_spec.rb +201 -0
  31. data/spec/concurrent/condition_spec.rb +171 -0
  32. data/spec/concurrent/copy_on_notify_observer_set_spec.rb +10 -0
  33. data/spec/concurrent/copy_on_write_observer_set_spec.rb +10 -0
  34. data/spec/concurrent/count_down_latch_spec.rb +125 -0
  35. data/spec/concurrent/dataflow_spec.rb +160 -0
  36. data/spec/concurrent/dereferenceable_shared.rb +145 -0
  37. data/spec/concurrent/event_spec.rb +44 -9
  38. data/spec/concurrent/fixed_thread_pool_spec.rb +0 -1
  39. data/spec/concurrent/future_spec.rb +184 -69
  40. data/spec/concurrent/ivar_spec.rb +192 -0
  41. data/spec/concurrent/mvar_spec.rb +380 -0
  42. data/spec/concurrent/obligation_spec.rb +193 -0
  43. data/spec/concurrent/observer_set_shared.rb +233 -0
  44. data/spec/concurrent/postable_shared.rb +3 -7
  45. data/spec/concurrent/promise_spec.rb +270 -192
  46. data/spec/concurrent/safe_task_executor_spec.rb +58 -0
  47. data/spec/concurrent/scheduled_task_spec.rb +142 -38
  48. data/spec/concurrent/thread_local_var_spec.rb +113 -0
  49. data/spec/concurrent/thread_pool_shared.rb +2 -3
  50. data/spec/concurrent/timer_task_spec.rb +31 -1
  51. data/spec/spec_helper.rb +2 -3
  52. data/spec/support/functions.rb +4 -0
  53. data/spec/support/less_than_or_equal_to_matcher.rb +5 -0
  54. metadata +50 -30
  55. data/lib/concurrent/contract.rb +0 -21
  56. data/lib/concurrent/event_machine_defer_proxy.rb +0 -22
  57. data/md/actor.md +0 -404
  58. data/md/agent.md +0 -142
  59. data/md/channel.md +0 -40
  60. data/md/dereferenceable.md +0 -49
  61. data/md/future.md +0 -125
  62. data/md/obligation.md +0 -32
  63. data/md/promise.md +0 -217
  64. data/md/scheduled_task.md +0 -156
  65. data/md/supervisor.md +0 -246
  66. data/md/thread_pool.md +0 -225
  67. data/md/timer_task.md +0 -191
  68. data/spec/concurrent/contract_spec.rb +0 -34
  69. data/spec/concurrent/event_machine_defer_proxy_spec.rb +0 -240
@@ -62,13 +62,12 @@ share_examples_for :thread_pool do
62
62
  end
63
63
 
64
64
  it 'allows threads to exit normally' do
65
- before_thread_count = Thread.list.size
66
65
  10.times{ subject << proc{ nil } }
66
+ subject.length.should > 0
67
67
  sleep(0.1)
68
- Thread.list.size.should > before_thread_count
69
68
  subject.shutdown
70
69
  sleep(1)
71
- Thread.list.size.should == before_thread_count
70
+ subject.length.should == 0
72
71
  end
73
72
  end
74
73
 
@@ -87,7 +87,37 @@ module Concurrent
87
87
  end
88
88
 
89
89
  context '#kill' do
90
- pending
90
+
91
+ it 'kills its threads while sleeping' do
92
+ Thread.should_receive(:kill).at_least(:once).times.with(any_args)
93
+ task = TimerTask.new(run_now: false){ nil }
94
+ task.run!
95
+ sleep(0.1)
96
+ task.kill
97
+ end
98
+
99
+ it 'kills its threads once executing' do
100
+ Thread.should_receive(:kill).at_least(2).times.with(any_args)
101
+ task = TimerTask.new(run_now: true){ nil }
102
+ task.run!
103
+ sleep(0.1)
104
+ task.kill
105
+ end
106
+
107
+ it 'returns true on success' do
108
+ task = TimerTask.new(run_now: false){ nil }
109
+ task.run!
110
+ sleep(0.1)
111
+ task.kill.should be_true
112
+ end
113
+
114
+ it 'returns false on exception' do
115
+ Thread.stub(:kill).with(any_args).and_raise(StandardError)
116
+ task = TimerTask.new(run_now: false){ nil }
117
+ task.run!
118
+ sleep(0.1)
119
+ task.kill.should be_false
120
+ end
91
121
  end
92
122
  end
93
123
 
@@ -8,14 +8,13 @@ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
8
8
 
9
9
  SimpleCov.start do
10
10
  project_name 'concurrent-ruby'
11
- add_filter '/md/'
11
+ add_filter '/coverage/'
12
+ add_filter '/doc/'
12
13
  add_filter '/pkg/'
13
14
  add_filter '/spec/'
14
15
  add_filter '/tasks/'
15
16
  end
16
17
 
17
- require 'eventmachine'
18
-
19
18
  require 'concurrent'
20
19
 
21
20
  # import all the support files
@@ -15,3 +15,7 @@ end
15
15
  def jruby?
16
16
  RbConfig::CONFIG['ruby_install_name']=~ /^jruby$/i
17
17
  end
18
+
19
+ def rbx?
20
+ RbConfig::CONFIG['ruby_install_name']=~ /^rbx$/i
21
+ end
@@ -0,0 +1,5 @@
1
+ RSpec::Matchers.define :be_less_than_or_equal_to do |expected|
2
+ match do |actual|
3
+ actual <= expected
4
+ end
5
+ 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.4.1
4
+ version: 0.5.0.pre.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jerry D'Antonio
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-03 00:00:00.000000000 Z
11
+ date: 2014-03-10 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Modern concurrency tools including agents, futures, promises, thread pools, actors, supervisors, and more.
@@ -25,70 +25,79 @@ files:
25
25
  - lib/concurrent.rb
26
26
  - lib/concurrent/actor.rb
27
27
  - lib/concurrent/agent.rb
28
+ - lib/concurrent/atomic.rb
28
29
  - lib/concurrent/cached_thread_pool.rb
29
30
  - lib/concurrent/cached_thread_pool/worker.rb
30
31
  - lib/concurrent/channel.rb
31
- - lib/concurrent/contract.rb
32
+ - lib/concurrent/condition.rb
33
+ - lib/concurrent/copy_on_notify_observer_set.rb
34
+ - lib/concurrent/copy_on_write_observer_set.rb
35
+ - lib/concurrent/count_down_latch.rb
36
+ - lib/concurrent/dataflow.rb
32
37
  - lib/concurrent/dereferenceable.rb
33
38
  - lib/concurrent/event.rb
34
- - lib/concurrent/event_machine_defer_proxy.rb
35
39
  - lib/concurrent/fixed_thread_pool.rb
36
40
  - lib/concurrent/fixed_thread_pool/worker.rb
37
41
  - lib/concurrent/future.rb
38
42
  - lib/concurrent/global_thread_pool.rb
39
43
  - lib/concurrent/immediate_executor.rb
44
+ - lib/concurrent/ivar.rb
45
+ - lib/concurrent/mvar.rb
40
46
  - lib/concurrent/obligation.rb
41
47
  - lib/concurrent/postable.rb
42
48
  - lib/concurrent/promise.rb
43
49
  - lib/concurrent/runnable.rb
50
+ - lib/concurrent/safe_task_executor.rb
44
51
  - lib/concurrent/scheduled_task.rb
45
52
  - lib/concurrent/stoppable.rb
46
53
  - lib/concurrent/supervisor.rb
54
+ - lib/concurrent/thread_local_var.rb
47
55
  - lib/concurrent/timer_task.rb
48
56
  - lib/concurrent/utilities.rb
49
57
  - lib/concurrent/version.rb
50
58
  - lib/concurrent_ruby.rb
51
- - md/actor.md
52
- - md/agent.md
53
- - md/channel.md
54
- - md/dereferenceable.md
55
- - md/future.md
56
- - md/obligation.md
57
- - md/promise.md
58
- - md/scheduled_task.md
59
- - md/supervisor.md
60
- - md/thread_pool.md
61
- - md/timer_task.md
62
59
  - spec/concurrent/actor_spec.rb
63
60
  - spec/concurrent/agent_spec.rb
61
+ - spec/concurrent/atomic_spec.rb
64
62
  - spec/concurrent/cached_thread_pool_spec.rb
65
63
  - spec/concurrent/channel_spec.rb
66
- - spec/concurrent/contract_spec.rb
67
- - spec/concurrent/event_machine_defer_proxy_spec.rb
64
+ - spec/concurrent/condition_spec.rb
65
+ - spec/concurrent/copy_on_notify_observer_set_spec.rb
66
+ - spec/concurrent/copy_on_write_observer_set_spec.rb
67
+ - spec/concurrent/count_down_latch_spec.rb
68
+ - spec/concurrent/dataflow_spec.rb
69
+ - spec/concurrent/dereferenceable_shared.rb
68
70
  - spec/concurrent/event_spec.rb
69
71
  - spec/concurrent/fixed_thread_pool_spec.rb
70
72
  - spec/concurrent/future_spec.rb
71
73
  - spec/concurrent/global_thread_pool_spec.rb
72
74
  - spec/concurrent/immediate_executor_spec.rb
75
+ - spec/concurrent/ivar_spec.rb
76
+ - spec/concurrent/mvar_spec.rb
73
77
  - spec/concurrent/obligation_shared.rb
78
+ - spec/concurrent/obligation_spec.rb
79
+ - spec/concurrent/observer_set_shared.rb
74
80
  - spec/concurrent/postable_shared.rb
75
81
  - spec/concurrent/promise_spec.rb
76
82
  - spec/concurrent/runnable_shared.rb
77
83
  - spec/concurrent/runnable_spec.rb
84
+ - spec/concurrent/safe_task_executor_spec.rb
78
85
  - spec/concurrent/scheduled_task_spec.rb
79
86
  - spec/concurrent/stoppable_shared.rb
80
87
  - spec/concurrent/supervisor_spec.rb
88
+ - spec/concurrent/thread_local_var_spec.rb
81
89
  - spec/concurrent/thread_pool_shared.rb
82
90
  - spec/concurrent/timer_task_spec.rb
83
91
  - spec/concurrent/uses_global_thread_pool_shared.rb
84
92
  - spec/concurrent/utilities_spec.rb
85
93
  - spec/spec_helper.rb
86
94
  - spec/support/functions.rb
95
+ - spec/support/less_than_or_equal_to_matcher.rb
87
96
  homepage: http://www.concurrent-ruby.com
88
97
  licenses:
89
98
  - MIT
90
99
  metadata: {}
91
- post_install_message:
100
+ post_install_message:
92
101
  rdoc_options: []
93
102
  require_paths:
94
103
  - lib
@@ -99,40 +108,51 @@ required_ruby_version: !ruby/object:Gem::Requirement
99
108
  version: 1.9.2
100
109
  required_rubygems_version: !ruby/object:Gem::Requirement
101
110
  requirements:
102
- - - '>='
111
+ - - '>'
103
112
  - !ruby/object:Gem::Version
104
- version: '0'
113
+ version: 1.3.1
105
114
  requirements: []
106
- rubyforge_project:
107
- rubygems_version: 2.2.1
108
- signing_key:
115
+ rubyforge_project:
116
+ rubygems_version: 2.2.2
117
+ signing_key:
109
118
  specification_version: 4
110
- summary: Modern concurrency tools including agents, futures, promises, thread pools,
111
- actors, and more.
119
+ summary: Modern concurrency tools including agents, futures, promises, thread pools, actors, and more.
112
120
  test_files:
121
+ - spec/spec_helper.rb
113
122
  - spec/concurrent/actor_spec.rb
114
123
  - spec/concurrent/agent_spec.rb
124
+ - spec/concurrent/atomic_spec.rb
115
125
  - spec/concurrent/cached_thread_pool_spec.rb
116
126
  - spec/concurrent/channel_spec.rb
117
- - spec/concurrent/contract_spec.rb
118
- - spec/concurrent/event_machine_defer_proxy_spec.rb
127
+ - spec/concurrent/condition_spec.rb
128
+ - spec/concurrent/copy_on_notify_observer_set_spec.rb
129
+ - spec/concurrent/copy_on_write_observer_set_spec.rb
130
+ - spec/concurrent/count_down_latch_spec.rb
131
+ - spec/concurrent/dataflow_spec.rb
132
+ - spec/concurrent/dereferenceable_shared.rb
119
133
  - spec/concurrent/event_spec.rb
120
134
  - spec/concurrent/fixed_thread_pool_spec.rb
121
135
  - spec/concurrent/future_spec.rb
122
136
  - spec/concurrent/global_thread_pool_spec.rb
123
137
  - spec/concurrent/immediate_executor_spec.rb
138
+ - spec/concurrent/ivar_spec.rb
139
+ - spec/concurrent/mvar_spec.rb
124
140
  - spec/concurrent/obligation_shared.rb
141
+ - spec/concurrent/obligation_spec.rb
142
+ - spec/concurrent/observer_set_shared.rb
125
143
  - spec/concurrent/postable_shared.rb
126
144
  - spec/concurrent/promise_spec.rb
127
145
  - spec/concurrent/runnable_shared.rb
128
146
  - spec/concurrent/runnable_spec.rb
147
+ - spec/concurrent/safe_task_executor_spec.rb
129
148
  - spec/concurrent/scheduled_task_spec.rb
130
149
  - spec/concurrent/stoppable_shared.rb
131
150
  - spec/concurrent/supervisor_spec.rb
151
+ - spec/concurrent/thread_local_var_spec.rb
132
152
  - spec/concurrent/thread_pool_shared.rb
133
153
  - spec/concurrent/timer_task_spec.rb
134
154
  - spec/concurrent/uses_global_thread_pool_shared.rb
135
155
  - spec/concurrent/utilities_spec.rb
136
- - spec/spec_helper.rb
137
156
  - spec/support/functions.rb
138
- has_rdoc:
157
+ - spec/support/less_than_or_equal_to_matcher.rb
158
+ has_rdoc:
@@ -1,21 +0,0 @@
1
- require 'concurrent/obligation'
2
-
3
- module Concurrent
4
-
5
- class Contract
6
- include Obligation
7
-
8
- def initialize(opts = {})
9
- @state = :pending
10
- init_mutex
11
- set_deref_options(opts)
12
- end
13
-
14
- def complete(value, reason)
15
- @value = value
16
- @reason = reason
17
- @state = ( reason ? :rejected : :fulfilled )
18
- event.set
19
- end
20
- end
21
- end
@@ -1,22 +0,0 @@
1
- require 'concurrent/global_thread_pool'
2
-
3
- module Concurrent
4
-
5
- class EventMachineDeferProxy
6
-
7
- def post(*args, &block)
8
- if args.empty?
9
- EventMachine.defer(block)
10
- else
11
- new_block = proc{ block.call(*args) }
12
- EventMachine.defer(new_block)
13
- end
14
- return true
15
- end
16
-
17
- def <<(block)
18
- EventMachine.defer(block)
19
- return self
20
- end
21
- end
22
- end
@@ -1,404 +0,0 @@
1
- # All the world's a stage
2
-
3
- Actor-based concurrency is all the rage in some circles. Originally described in
4
- 1973, the actor model is a paradigm for creating asynchronous, concurrent objects
5
- that is becoming increasingly popular. Much has changed since actors were first
6
- written about four decades ago, which has led to a serious fragmentation within
7
- the actor community. There is *no* universally accepted, strict definition of
8
- "actor" and actor implementations differ widely between languages and libraries.
9
-
10
- A good definition of "actor" is:
11
-
12
- > An independent, concurrent, single-purpose, computational entity that
13
- > communicates exclusively via message passing.
14
-
15
- The `Concurrent::Actor` class in this library is based solely on the
16
- [Actor](http://www.scala-lang.org/api/current/index.html#scala.actors.Actor) task
17
- defined in the Scala standard library. It does not implement all the features of
18
- Scala's `Actor` but its behavior for what *has* been implemented is nearly identical.
19
- The excluded features mostly deal with Scala's message semantics, strong typing,
20
- and other characteristics of Scala that don't really apply to Ruby.
21
-
22
- Unlike most of the abstractions in this library, `Actor` takes an *object-oriented*
23
- approach to asynchronous concurrency, rather than a *functional programming*
24
- approach.
25
-
26
- ## Definition
27
-
28
- Actors are defined by subclassing the `Concurrent::Actor` class and overriding the
29
- `#act` method. The `#act` method can have any signature/arity but
30
-
31
- ```ruby
32
- def act(*args)
33
- ```
34
-
35
- is the most flexible and least error-prone signature. The `#act` method is called in
36
- response to a message being post to the `Actor` instance (see *Behavior* below).
37
-
38
- ## Behavior
39
-
40
- The `Concurrent::Actor` class includes the `Concurrent::Runnable` module. This provides
41
- an `Actor` instance with the necessary methods for running and graceful stopping.
42
- This also means that an `Actor` can be managed by a `Concurrent::Supervisor` for
43
- fault tolerance.
44
-
45
- ### Message Passing
46
-
47
- Messages from any thread can be sent (aka "post") to an `Actor` using several methods.
48
- When a message is post all arguments are gathered together and queued for processing.
49
- Messages are processed in the order they are received, one at a time, on a dedicated
50
- thread. When a message is processed the subclass `#act` method is called and the return
51
- value (or raised exception) is handled by the superclass based on the rules of the method
52
- used to post the message.
53
-
54
- All message posting methods are compatible with observation (see below).
55
-
56
- Message processing within the `#act` method is not limited in any way, but care should
57
- be taken to behave in a thread-safe, concurrency-friendly manner. A common practice is for
58
- one `Actor` to send messages to another `Actor` though this is hardly the only approach.
59
-
60
- Messages post to an `Actor` that is not running will be rejected.
61
-
62
- #### Fire and Forget
63
-
64
- The primary method of posting a message to an `Actor` is the simple `#post` method.
65
- When this method is called the message is queued for processing. The method returns
66
- false if it cannot be queued (the `Actor` is not running) otherwise it returns the
67
- size of the queue (after queueing the new message). The caller thread has no way
68
- to know the result of the message processing. When the `#post` method is used the
69
- only way to act upon the result of the message processing is via observation
70
- (see below).
71
-
72
- #### Post with an Obligation
73
-
74
- A common theme in modern asynchronous concurrency is for operations to return a
75
- "future" (or "promise"). In this context a "future" is not an instance of the
76
- `Concurrent::Future` class, but it is an object with similar behavior. Within
77
- this library "future" behavior is genericized by the `Concurrent::Obligation`
78
- mixin module (shared by `Future`, `Promise`, and others).
79
-
80
- To post a message that returns a `Obligation` use the `#post?` method. If the message
81
- cannot be queued the method will return `nil`. Otherwise an object implementing
82
- `Obligation` will returned. The `Obligation` has the exteced states (`:pending`,
83
- `:fulfilled`, and `:rejected`), the expected state-named predicate methods,
84
- plus `#value` and `#reason`. These methods all behave identically to `Concurrent::Future`.
85
-
86
- #### Post with Timeout
87
-
88
- Threads posting messages to an `Actor` should generally not block. Blocking to wait
89
- for an `Actor` to process a specific message defeats the purpose of asynchronous
90
- concurrency. The `#post!` method is provided when the caller absolutely must block.
91
- The first argument to `#post!` is a number of seconds to block while waiting for the
92
- operation to complete. All subsequent arguments constitute the message and are
93
- queued for delivery to the `#act` method. If the queued operation completes within
94
- the timeout period the `#post!` method returns the result of the operation.
95
-
96
- Unlike most methods in this library, the `#post!` method does not suppress exceptions.
97
- Because the `#post!` method return value represents the result of message processing
98
- the return value cannot effectively communicate failure. Instead, exceptions are used.
99
- Calls to the `#post!` method should generally be wrapped in `rescue` guards. The
100
- following exceptions may be raised by the `#post!` method:
101
-
102
- * `Concurrent::Runnable::LifecycleError` will be raised if the message cannot be
103
- queued, such as when the `Actor` is not running.
104
- * `Concurrent::TimeoutError` will be raised if the message is not processed within
105
- the designated timeout period
106
- * Any exception raised during message processing will be re-raised after all
107
- post-processing operations (such as observer callbacks) have completed
108
-
109
- When the `#post!` method results in a timeout the `Actor` will attempt to cancel
110
- message processing, but cancellation is not guaranteed. If message processing has
111
- not begun the cancellation will normally occur. If message processing is in-progress
112
- when `#post!` reaches timeout then processing will be allowed to complete. Code that
113
- uses the `#post!` method must therefore not assume that a timeout means that message
114
- processing did not occur.
115
-
116
- #### Implicit Forward/Reply
117
-
118
- A common idiom is for an `Actor` to send messages to another `Actor`. This creates
119
- a "data flow" style of design not dissimilar to Unix-style pipe commands. Less common,
120
- but still frequent, is for an `Actor` to send the result of message processing back
121
- to the `Actor` that sent the message. In Scala this is easy to do. The underlying
122
- message passing system implicitly communicates to the receiver the address of the
123
- sender. Therefore, Scala actors can easily reply to the sender. Ruby has no similar
124
- message passing subsystem to implicit knowledge of the sender is not possible. This
125
- `Actor` implementation provides a `#forward` method that encapsulates both
126
- aforementioned idioms. The first argument to the `#forward` method is a reference
127
- to another `Actor` to which the receiving `Actor` should forward the result of
128
- the processed messages. All subsequent arguments constitute the message and are
129
- queued for delivery to the `#act` method.
130
-
131
- Upon successful message processing the `Actor` superclass will automatically
132
- forward the result to the receiver provided when `#forward` was called. If an
133
- exception is raised no forwarding occurs.
134
-
135
- ### Error Handling
136
-
137
- Because `Actor` mixes in the `Concurrent::Runnable` module subclasses have access to
138
- the `#on_error` method and can override it to implement custom error handling. The
139
- `Actor` base class does not use `#on_error` so as to avoid conflit with subclasses
140
- which override it. Generally speaking, `#on_error` should not be used. The `Actor`
141
- base class provides concictent, reliable, and robust error handling already, and
142
- error handling specifics are tied to the message posting method. Incorrect behavior
143
- in an `#on_error` override can lead to inconsistent `Actor` behavior that may lead
144
- to confusion and difficult debugging.
145
-
146
- ### Observation
147
-
148
- The `Actor` superclass mixes in the Ruby standard library
149
- [Observable](http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html)
150
- module to provide consistent callbacks upon message processing completion. The normal
151
- `Observable` methods, including `#add_observer` behave normally. Once an observer
152
- is added to an `Actor` it will be notified of all messages processed *after*
153
- addition. Notification will *not* occur for any messages that have already been
154
- processed.
155
-
156
- Observers will be notified regardless of whether the message processing is successful
157
- or not. The `#update` method of the observer will receive four arguments. The
158
- appropriate method signature is:
159
-
160
- ```ruby
161
- def update(time, message, result, reason)
162
- ```
163
-
164
- These four arguments represent:
165
-
166
- * The time that message processing was completed
167
- * An array containing all elements of the original message, in order
168
- * The result of the call to `#act` (will be `nil` if an exception was raised)
169
- * Any exception raised by `#act` (or `nil` if message processing was successful)
170
-
171
- ### Actor Pools
172
-
173
- Every `Actor` instance operates on its own thread. When one thread isn't enough capacity
174
- to manage all the messages being sent to an `Actor` a *pool* can be used instead. A pool
175
- is a collection of `Actor` instances, all of the same type, that shate a message queue.
176
- Messages from other threads are all sent to a single queue against which all `Actor`s
177
- load balance.
178
-
179
- ## Additional Reading
180
-
181
- * [API documentation](http://www.scala-lang.org/api/current/index.html#scala.actors.Actor)
182
- for the original (now deprecated) Scala Actor
183
- * [Scala Actors: A Short Tutorial](http://www.scala-lang.org/old/node/242)
184
- * [Scala Actors 101](http://java.dzone.com/articles/scala-threadless-concurrent)
185
-
186
- ## Examples
187
-
188
- Two `Actor`s playing a back and forth game of Ping Pong, adapted from the Scala example
189
- [here](http://www.scala-lang.org/old/node/242):
190
-
191
- ```ruby
192
- class Ping < Concurrent::Actor
193
-
194
- def initialize(count, pong)
195
- super()
196
- @pong = pong
197
- @remaining = count
198
- end
199
-
200
- def act(msg)
201
-
202
- if msg == :pong
203
- print "Ping: pong\n" if @remaining % 1000 == 0
204
- @pong.post(:ping)
205
-
206
- if @remaining > 0
207
- @pong << :ping
208
- @remaining -= 1
209
- else
210
- print "Ping :stop\n"
211
- @pong << :stop
212
- self.stop
213
- end
214
- end
215
- end
216
- end
217
-
218
- class Pong < Concurrent::Actor
219
-
220
- attr_writer :ping
221
-
222
- def initialize
223
- super()
224
- @count = 0
225
- end
226
-
227
- def act(msg)
228
-
229
- if msg == :ping
230
- print "Pong: ping\n" if @count % 1000 == 0
231
- @ping << :pong
232
- @count += 1
233
-
234
- elsif msg == :stop
235
- print "Pong :stop\n"
236
- self.stop
237
- end
238
- end
239
- end
240
-
241
- pong = Pong.new
242
- ping = Ping.new(10000, pong)
243
- pong.ping = ping
244
-
245
- t1 = ping.run!
246
- t2 = pong.run!
247
- sleep(0.1)
248
-
249
- ping << :pong
250
- ```
251
-
252
- A pool of `Actor`s and a `Supervisor`
253
-
254
- ```ruby
255
- QUERIES = %w[YAHOO Microsoft google]
256
-
257
- class FinanceActor < Concurrent::Actor
258
- def act(query)
259
- finance = Finance.new(query)
260
- print "[#{Time.now}] RECEIVED '#{query}' to #{self} returned #{finance.update.suggested_symbols}\n\n"
261
- end
262
- end
263
-
264
- financial, pool = FinanceActor.pool(5)
265
-
266
- overlord = Concurrent::Supervisor.new
267
- pool.each{|actor| overlord.add_worker(actor)}
268
-
269
- overlord.run!
270
-
271
- financial.post('YAHOO')
272
-
273
- #>> [2013-10-18 09:35:28 -0400] SENT 'YAHOO' from main to worker pool
274
- #>> [2013-10-18 09:35:28 -0400] RECEIVED 'YAHOO' to #<FinanceActor:0x0000010331af70>...
275
- ```
276
-
277
- The `#post` method simply sends a message to an actor and returns. It's a
278
- fire-and-forget interaction.
279
-
280
- ```ruby
281
- class EchoActor < Concurrent::Actor
282
- def act(*message)
283
- p message
284
- end
285
- end
286
-
287
- echo = EchoActor.new
288
- echo.run!
289
-
290
- echo.post("Don't panic") #=> true
291
- #=> ["Don't panic"]
292
-
293
- echo.post(1, 2, 3, 4, 5) #=> true
294
- #=> [1, 2, 3, 4, 5]
295
-
296
- echo << "There's a frood who really knows where his towel is." #=> #<EchoActor:0x007fc8012b8448...
297
- #=> ["There's a frood who really knows where his towel is."]
298
- ```
299
-
300
- The `#post?` method returns an `Obligation` (same API as `Future`) which can be queried
301
- for value/reason on fulfillment/rejection.
302
-
303
- ```ruby
304
- class EverythingActor < Concurrent::Actor
305
- def act(message)
306
- sleep(5)
307
- return 42
308
- end
309
- end
310
-
311
- life = EverythingActor.new
312
- life.run!
313
- sleep(0.1)
314
-
315
- universe = life.post?('What do you get when you multiply six by nine?')
316
- universe.pending? #=> true
317
-
318
- # wait for it...
319
-
320
- universe.fulfilled? #=> true
321
- universe.value #=> 42
322
- ```
323
-
324
- The `#post!` method is a blocking call. It takes a number of seconds to wait as the
325
- first parameter and any number of additional parameters as the message. If the message
326
- is processed within the given number of seconds the call returns the result of the
327
- operation. If message processing raises an exception the exception is raised again
328
- by the `#post!` method. If the call to `#post!` times out a `Concurrent::Timeout`
329
- exception is raised.
330
-
331
- ```ruby
332
- life = EverythingActor.new
333
- life.run!
334
- sleep(0.1)
335
-
336
- life.post!(1, 'Mostly harmless.')
337
-
338
- # wait for it...
339
- #=> Concurrent::TimeoutError: Concurrent::TimeoutError
340
- ```
341
-
342
- And, of course, the `Actor` class mixes in Ruby's `Observable`.
343
-
344
- ```ruby
345
- class ActorObserver
346
- def update(time, message, result, ex)
347
- if result
348
- print "(#{time}) Message #{message} returned #{result}\n"
349
- elsif ex.is_a?(Concurrent::TimeoutError)
350
- print "(#{time}) Message #{message} timed out\n"
351
- else
352
- print "(#{time}) Message #{message} failed with error #{ex}\n"
353
- end
354
- end
355
- end
356
-
357
- class SimpleActor < Concurrent::Actor
358
- def act(*message)
359
- message
360
- end
361
- end
362
-
363
- actor = SimpleActor.new
364
- actor.add_observer(ActorObserver.new)
365
- actor.run!
366
-
367
- actor.post(1)
368
- #=> (2013-11-07 18:35:33 -0500) Message [1] returned [1]
369
-
370
- actor.post(1,2,3)
371
- #=> (2013-11-07 18:35:54 -0500) Message [1, 2, 3] returned [1, 2, 3]
372
-
373
- actor.post('The Nightman Cometh')
374
- #=> (2013-11-07 18:36:11 -0500) Message ["The Nightman Cometh"] returned ["The Nightman Cometh"]
375
- ```
376
-
377
- ## Copyright
378
-
379
- *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
380
- It is free software and may be redistributed under the terms specified in the LICENSE file.
381
-
382
- ## License
383
-
384
- Released under the MIT license.
385
-
386
- http://www.opensource.org/licenses/mit-license.php
387
-
388
- > Permission is hereby granted, free of charge, to any person obtaining a copy
389
- > of this software and associated documentation files (the "Software"), to deal
390
- > in the Software without restriction, including without limitation the rights
391
- > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
392
- > copies of the Software, and to permit persons to whom the Software is
393
- > furnished to do so, subject to the following conditions:
394
- >
395
- > The above copyright notice and this permission notice shall be included in
396
- > all copies or substantial portions of the Software.
397
- >
398
- > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
399
- > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
400
- > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
401
- > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
402
- > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
403
- > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
404
- > THE SOFTWARE.