concurrent-ruby 0.4.1 → 0.5.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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.