|
1 |
| -require 'thread' |
2 |
| -require 'observer' |
3 |
| - |
4 |
| -require 'concurrent/event' |
5 |
| -require 'concurrent/obligation' |
6 |
| -require 'concurrent/postable' |
7 |
| -require 'concurrent/runnable' |
8 |
| - |
9 |
| -module Concurrent |
10 |
| - |
11 |
| - # Actor-based concurrency is all the rage in some circles. Originally described in |
12 |
| - # 1973, the actor model is a paradigm for creating asynchronous, concurrent objects |
13 |
| - # that is becoming increasingly popular. Much has changed since actors were first |
14 |
| - # written about four decades ago, which has led to a serious fragmentation within |
15 |
| - # the actor community. There is *no* universally accepted, strict definition of |
16 |
| - # "actor" and actor implementations differ widely between languages and libraries. |
17 |
| - # |
18 |
| - # A good definition of "actor" is: |
19 |
| - # |
20 |
| - # An independent, concurrent, single-purpose, computational entity that communicates exclusively via message passing. |
21 |
| - # |
22 |
| - # The +Concurrent::Actor+ class in this library is based solely on the |
23 |
| - # {http://www.scala-lang.org/api/current/index.html#scala.actors.Actor Actor} trait |
24 |
| - # defined in the Scala standard library. It does not implement all the features of |
25 |
| - # Scala's +Actor+ but its behavior for what *has* been implemented is nearly identical. |
26 |
| - # The excluded features mostly deal with Scala's message semantics, strong typing, |
27 |
| - # and other characteristics of Scala that don't really apply to Ruby. |
28 |
| - # |
29 |
| - # Unlike many of the abstractions in this library, +Actor+ takes an *object-oriented* |
30 |
| - # approach to asynchronous concurrency, rather than a *functional programming* |
31 |
| - # approach. |
32 |
| - # |
33 |
| - # Because +Actor+ mixes in the +Concurrent::Runnable+ module subclasses have access to |
34 |
| - # the +#on_error+ method and can override it to implement custom error handling. The |
35 |
| - # +Actor+ base class does not use +#on_error+ so as to avoid conflit with subclasses |
36 |
| - # which override it. Generally speaking, +#on_error+ should not be used. The +Actor+ |
37 |
| - # base class provides concictent, reliable, and robust error handling already, and |
38 |
| - # error handling specifics are tied to the message posting method. Incorrect behavior |
39 |
| - # in an +#on_error+ override can lead to inconsistent +Actor+ behavior that may lead |
40 |
| - # to confusion and difficult debugging. |
41 |
| - # |
42 |
| - # The +Actor+ superclass mixes in the Ruby standard library |
43 |
| - # {http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html Observable} |
44 |
| - # module to provide consistent callbacks upon message processing completion. The normal |
45 |
| - # +Observable+ methods, including +#add_observer+ behave normally. Once an observer |
46 |
| - # is added to an +Actor+ it will be notified of all messages processed *after* |
47 |
| - # addition. Notification will *not* occur for any messages that have already been |
48 |
| - # processed. |
49 |
| - # |
50 |
| - # Observers will be notified regardless of whether the message processing is successful |
51 |
| - # or not. The +#update+ method of the observer will receive four arguments. The |
52 |
| - # appropriate method signature is: |
53 |
| - # |
54 |
| - # def update(time, message, result, reason) |
55 |
| - # |
56 |
| - # These four arguments represent: |
57 |
| - # |
58 |
| - # * The time that message processing was completed |
59 |
| - # * An array containing all elements of the original message, in order |
60 |
| - # * The result of the call to +#act+ (will be +nil+ if an exception was raised) |
61 |
| - # * Any exception raised by +#act+ (or +nil+ if message processing was successful) |
62 |
| - # |
63 |
| - # @example Actor Ping Pong |
64 |
| - # class Ping < Concurrent::Actor |
65 |
| - # |
66 |
| - # def initialize(count, pong) |
67 |
| - # super() |
68 |
| - # @pong = pong |
69 |
| - # @remaining = count |
70 |
| - # end |
71 |
| - # |
72 |
| - # def act(msg) |
73 |
| - # |
74 |
| - # if msg == :pong |
75 |
| - # print "Ping: pong\n" if @remaining % 1000 == 0 |
76 |
| - # @pong.post(:ping) |
77 |
| - # |
78 |
| - # if @remaining > 0 |
79 |
| - # @pong << :ping |
80 |
| - # @remaining -= 1 |
81 |
| - # else |
82 |
| - # print "Ping :stop\n" |
83 |
| - # @pong << :stop |
84 |
| - # self.stop |
85 |
| - # end |
86 |
| - # end |
87 |
| - # end |
88 |
| - # end |
89 |
| - # |
90 |
| - # class Pong < Concurrent::Actor |
91 |
| - # |
92 |
| - # attr_writer :ping |
93 |
| - # |
94 |
| - # def initialize |
95 |
| - # super() |
96 |
| - # @count = 0 |
97 |
| - # end |
98 |
| - # |
99 |
| - # def act(msg) |
100 |
| - # |
101 |
| - # if msg == :ping |
102 |
| - # print "Pong: ping\n" if @count % 1000 == 0 |
103 |
| - # @ping << :pong |
104 |
| - # @count += 1 |
105 |
| - # |
106 |
| - # elsif msg == :stop |
107 |
| - # print "Pong :stop\n" |
108 |
| - # self.stop |
109 |
| - # end |
110 |
| - # end |
111 |
| - # end |
112 |
| - # |
113 |
| - # pong = Pong.new |
114 |
| - # ping = Ping.new(10000, pong) |
115 |
| - # pong.ping = ping |
116 |
| - # |
117 |
| - # t1 = ping.run! |
118 |
| - # t2 = pong.run! |
119 |
| - # |
120 |
| - # ping << :pong |
121 |
| - # |
122 |
| - # @deprecated +Actor+ is being replaced with a completely new framework prior to v1.0.0 |
123 |
| - # |
124 |
| - # @see http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html |
125 |
| - class Actor |
126 |
| - include ::Observable |
127 |
| - include Postable |
128 |
| - include Runnable |
129 |
| - |
130 |
| - private |
131 |
| - |
132 |
| - # @!visibility private |
133 |
| - class Poolbox # :nodoc: |
134 |
| - include Postable |
135 |
| - |
136 |
| - def initialize(queue) |
137 |
| - @queue = queue |
138 |
| - end |
139 |
| - end |
140 |
| - |
141 |
| - public |
142 |
| - |
143 |
| - # Create a pool of actors that share a common mailbox. |
144 |
| - # |
145 |
| - # Every +Actor+ instance operates on its own thread. When one thread isn't enough capacity |
146 |
| - # to manage all the messages being sent to an +Actor+ a *pool* can be used instead. A pool |
147 |
| - # is a collection of +Actor+ instances, all of the same type, that shate a message queue. |
148 |
| - # Messages from other threads are all sent to a single queue against which all +Actor+s |
149 |
| - # load balance. |
150 |
| - # |
151 |
| - # @param [Integer] count the number of actors in the pool |
152 |
| - # @param [Array] args zero or more arguments to pass to each actor in the pool |
153 |
| - # |
154 |
| - # @return [Array] two-element array with the shared mailbox as the first element |
155 |
| - # and an array of actors as the second element |
156 |
| - # |
157 |
| - # @raise ArgumentError if +count+ is zero or less |
158 |
| - # |
159 |
| - # @example |
160 |
| - # class EchoActor < Concurrent::Actor |
161 |
| - # def act(*message) |
162 |
| - # puts "#{message} handled by #{self}" |
163 |
| - # end |
164 |
| - # end |
165 |
| - # |
166 |
| - # mailbox, pool = EchoActor.pool(5) |
167 |
| - # pool.each{|echo| echo.run! } |
168 |
| - # |
169 |
| - # 10.times{|i| mailbox.post(i) } |
170 |
| - # #=> [0] handled by #<EchoActor:0x007fc8014fb8b8> |
171 |
| - # #=> [1] handled by #<EchoActor:0x007fc8014fb890> |
172 |
| - # #=> [2] handled by #<EchoActor:0x007fc8014fb868> |
173 |
| - # #=> [3] handled by #<EchoActor:0x007fc8014fb890> |
174 |
| - # #=> [4] handled by #<EchoActor:0x007fc8014fb840> |
175 |
| - # #=> [5] handled by #<EchoActor:0x007fc8014fb8b8> |
176 |
| - # #=> [6] handled by #<EchoActor:0x007fc8014fb8b8> |
177 |
| - # #=> [7] handled by #<EchoActor:0x007fc8014fb818> |
178 |
| - # #=> [8] handled by #<EchoActor:0x007fc8014fb890> |
179 |
| - # |
180 |
| - # @deprecated +Actor+ is being replaced with a completely new framework prior to v1.0.0 |
181 |
| - def self.pool(count, *args, &block) |
182 |
| - warn '[DEPRECATED] `Actor` is deprecated and will be replaced with `ActorContext`.' |
183 |
| - raise ArgumentError.new('count must be greater than zero') unless count > 0 |
184 |
| - mailbox = Queue.new |
185 |
| - actors = count.times.collect do |
186 |
| - if block_given? |
187 |
| - actor = self.new(*args, &block.dup) |
188 |
| - else |
189 |
| - actor = self.new(*args) |
190 |
| - end |
191 |
| - actor.instance_variable_set(:@queue, mailbox) |
192 |
| - actor |
193 |
| - end |
194 |
| - return Poolbox.new(mailbox), actors |
195 |
| - end |
196 |
| - |
197 |
| - protected |
198 |
| - |
199 |
| - # Actors are defined by subclassing the +Concurrent::Actor+ class and overriding the |
200 |
| - # #act method. The #act method can have any signature/arity but +def act(*args)+ |
201 |
| - # is the most flexible and least error-prone signature. The #act method is called in |
202 |
| - # response to a message being post to the +Actor+ instance (see *Behavior* below). |
203 |
| - # |
204 |
| - # @param [Array] message one or more arguments representing the message sent to the |
205 |
| - # actor via one of the Concurrent::Postable methods |
206 |
| - # |
207 |
| - # @return [Object] the result obtained when the message is successfully processed |
208 |
| - # |
209 |
| - # @raise NotImplementedError unless overridden in the +Actor+ subclass |
210 |
| - # |
211 |
| - # @deprecated +Actor+ is being replaced with a completely new framework prior to v1.0.0 |
212 |
| - # |
213 |
| - # @!visibility public |
214 |
| - def act(*message) |
215 |
| - warn '[DEPRECATED] `Actor` is deprecated and will be replaced with `ActorContext`.' |
216 |
| - raise NotImplementedError.new("#{self.class} does not implement #act") |
217 |
| - end |
218 |
| - |
219 |
| - # @!visibility private |
220 |
| - # |
221 |
| - # @deprecated +Actor+ is being replaced with a completely new framework prior to v1.0.0 |
222 |
| - def on_run # :nodoc: |
223 |
| - warn '[DEPRECATED] `Actor` is deprecated and will be replaced with `ActorContext`.' |
224 |
| - queue.clear |
225 |
| - end |
226 |
| - |
227 |
| - # @!visibility private |
228 |
| - # |
229 |
| - # @deprecated +Actor+ is being replaced with a completely new framework prior to v1.0.0 |
230 |
| - def on_stop # :nodoc: |
231 |
| - queue.clear |
232 |
| - queue.push(:stop) |
233 |
| - end |
234 |
| - |
235 |
| - # @!visibility private |
236 |
| - # |
237 |
| - # @deprecated +Actor+ is being replaced with a completely new framework prior to v1.0.0 |
238 |
| - def on_task # :nodoc: |
239 |
| - package = queue.pop |
240 |
| - return if package == :stop |
241 |
| - result = ex = nil |
242 |
| - notifier = package.notifier |
243 |
| - begin |
244 |
| - if notifier.nil? || (notifier.is_a?(Event) && ! notifier.set?) |
245 |
| - result = act(*package.message) |
246 |
| - end |
247 |
| - rescue => ex |
248 |
| - on_error(Time.now, package.message, ex) |
249 |
| - ensure |
250 |
| - if notifier.is_a?(Event) && ! notifier.set? |
251 |
| - package.handler.push(result || ex) |
252 |
| - package.notifier.set |
253 |
| - elsif package.handler.is_a?(IVar) |
254 |
| - package.handler.complete(! result.nil?, result, ex) |
255 |
| - elsif package.handler.respond_to?(:post) && ex.nil? |
256 |
| - package.handler.post(result) |
257 |
| - end |
258 |
| - |
259 |
| - changed |
260 |
| - notify_observers(Time.now, package.message, result, ex) |
261 |
| - end |
262 |
| - end |
263 |
| - |
264 |
| - # @!visibility private |
265 |
| - # |
266 |
| - # @deprecated +Actor+ is being replaced with a completely new framework prior to v1.0.0 |
267 |
| - def on_error(time, msg, ex) # :nodoc: |
268 |
| - end |
269 |
| - end |
270 |
| -end |
| 1 | +require 'concurrent/actor/actor' |
| 2 | +require 'concurrent/actor/actor_context' |
| 3 | +require 'concurrent/actor/actor_ref' |
| 4 | +require 'concurrent/actor/postable' |
| 5 | +require 'concurrent/actor/simple_actor_ref' |
0 commit comments