concurrent-ruby-edge 0.3.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +552 -0
  3. data/LICENSE.txt +18 -18
  4. data/README.md +261 -103
  5. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/abstract.rb +2 -0
  6. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/awaits.rb +2 -0
  7. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/buffer.rb +2 -0
  8. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/errors_on_unknown_message.rb +2 -0
  9. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/executes_context.rb +2 -0
  10. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/linking.rb +2 -0
  11. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/pausing.rb +2 -0
  12. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/removes_child.rb +2 -0
  13. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/sets_results.rb +2 -0
  14. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/supervising.rb +2 -0
  15. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/termination.rb +3 -1
  16. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour.rb +1 -1
  17. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/context.rb +3 -1
  18. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/core.rb +5 -4
  19. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/default_dead_letter_handler.rb +2 -0
  20. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/envelope.rb +2 -0
  21. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/errors.rb +2 -0
  22. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/internal_delegations.rb +3 -0
  23. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/reference.rb +9 -8
  24. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/root.rb +3 -0
  25. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/utils/ad_hoc.rb +2 -0
  26. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/utils/balancer.rb +2 -0
  27. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/utils/broadcast.rb +1 -0
  28. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/utils/pool.rb +1 -0
  29. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor.rb +11 -6
  30. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/base.rb +14 -14
  31. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/dropping.rb +1 -0
  32. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/sliding.rb +1 -0
  33. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/unbuffered.rb +1 -1
  34. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/tick.rb +1 -1
  35. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel.rb +3 -2
  36. data/lib/concurrent-ruby-edge/concurrent/edge/cancellation.rb +107 -0
  37. data/lib/concurrent-ruby-edge/concurrent/edge/channel.rb +453 -0
  38. data/lib/concurrent-ruby-edge/concurrent/edge/erlang_actor.rb +1549 -0
  39. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/edge/lock_free_linked_set/node.rb +2 -2
  40. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/edge/lock_free_linked_set.rb +8 -7
  41. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/edge/lock_free_queue.rb +2 -0
  42. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/edge/old_channel_integration.rb +2 -0
  43. data/lib/concurrent-ruby-edge/concurrent/edge/processing_actor.rb +184 -0
  44. data/lib/concurrent-ruby-edge/concurrent/edge/promises.rb +174 -0
  45. data/lib/concurrent-ruby-edge/concurrent/edge/throttle.rb +229 -0
  46. data/lib/concurrent-ruby-edge/concurrent/edge/version.rb +3 -0
  47. data/lib/concurrent-ruby-edge/concurrent/edge.rb +21 -0
  48. data/lib/concurrent-ruby-edge/concurrent/executor/wrapping_executor.rb +50 -0
  49. data/lib/concurrent-ruby-edge/concurrent/lazy_register.rb +83 -0
  50. data/lib/{concurrent-edge.rb → concurrent-ruby-edge/concurrent-edge.rb} +5 -4
  51. metadata +71 -67
  52. data/lib/concurrent/edge/atomic_markable_reference.rb +0 -184
  53. data/lib/concurrent/edge/cancellation.rb +0 -138
  54. data/lib/concurrent/edge/lock_free_stack.rb +0 -126
  55. data/lib/concurrent/edge/processing_actor.rb +0 -161
  56. data/lib/concurrent/edge/promises.rb +0 -2111
  57. data/lib/concurrent/edge/throttle.rb +0 -192
  58. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/public_delegations.rb +0 -0
  59. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/type_check.rb +0 -0
  60. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/utils.rb +0 -0
  61. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/buffered.rb +0 -0
  62. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/ticker.rb +0 -0
  63. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/timer.rb +0 -0
  64. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer.rb +0 -0
  65. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/selector/after_clause.rb +0 -0
  66. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/selector/default_clause.rb +0 -0
  67. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/selector/error_clause.rb +0 -0
  68. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/selector/put_clause.rb +0 -0
  69. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/selector/take_clause.rb +0 -0
  70. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/selector.rb +0 -0
  71. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/edge/lock_free_linked_set/window.rb +0 -0
@@ -0,0 +1,1549 @@
1
+ require 'set'
2
+ require 'concurrent/atomic/count_down_latch'
3
+ require 'concurrent/concern/logging'
4
+ require 'concurrent/edge/channel'
5
+ require 'concurrent/errors'
6
+ require 'concurrent/promises'
7
+ require 'concurrent/synchronization/object'
8
+
9
+ module Concurrent
10
+
11
+ # This module provides actor abstraction that has same behaviour as Erlang actor.
12
+ #
13
+ # {include:file:docs-source/erlang_actor.out.md}
14
+ # @!macro warn.edge
15
+ module ErlangActor
16
+
17
+ # TODO (pitr-ch 04-Feb-2019): mode documentation.
18
+ # TODO (pitr-ch 21-Jan-2019): actor on promises should not call blocking calls like mailbox.pop or tell
19
+ # it's fine for a actor on thread and event based though
20
+ # TODO (pitr-ch 17-Jan-2019): blocking actor should react to signals?
21
+ # e.g. override sleep to wait for signal with a given timeout?
22
+ # what about other blocking stuff
23
+ # def sleep(time)
24
+ # raise NotImplementedError
25
+ # end
26
+ #
27
+ # def sleep(time)
28
+ # raise NotImplementedError
29
+ # finish = Concurrent.monotonic_time + time
30
+ # while true
31
+ # now = Concurrent.monotonic_time
32
+ # if now < finish
33
+ # message = @Mailbox.pop_matching(AbstractSignal, finish - now)
34
+ # else
35
+ # end
36
+ # end
37
+ # end
38
+ # TODO (pitr-ch 28-Jan-2019): improve matching support, take inspiration and/or port Algebrick ideas, push ANY and similar further up the namespace
39
+
40
+ # The public reference of the actor which can be stored and passed around.
41
+ # Nothing else of the actor should be exposed.
42
+ # {Functions.spawn_actor} and {Environment#spawn} return the pid.
43
+ class Pid < Synchronization::Object
44
+ # TODO (pitr-ch 06-Feb-2019): when actor terminates, release it from memory keeping just pid
45
+
46
+ # The actor is asynchronously told a message.
47
+ # The method returns immediately unless
48
+ # the actor has bounded mailbox and there is no more space for the message.
49
+ # Then the method blocks current thread until there is space available.
50
+ # This is useful for backpressure.
51
+ #
52
+ # @param [Object] message
53
+ # @param [Numeric] timeout the maximum time in second to wait
54
+ # @return [self, true, false]
55
+ # self if timeout was nil, false on timing out and true if told in time.
56
+ def tell(message, timeout = nil)
57
+ @Actor.tell message, timeout
58
+ end
59
+
60
+ # Same as {#tell} but represented as a {Promises::Future}.
61
+ # @param [Object] message
62
+ # @return [Promises::Future(self)]
63
+ def tell_op(message)
64
+ @Actor.tell_op(message)
65
+ end
66
+
67
+ # The actor is asked the message and blocks until a reply is available,
68
+ # which is returned by the method.
69
+ # If the reply is a rejection then the methods raises it.
70
+ #
71
+ # If the actor does not call {Environment#reply} or {Environment#reply_resolution}
72
+ # the method will raise NoReply error.
73
+ # If the actor is terminated it will raise NoActor.
74
+ # Therefore the ask is never left unanswered and blocking.
75
+ #
76
+ # @param [Object] message
77
+ # @param [Numeric] timeout the maximum time in second to wait
78
+ # @param [Object] timeout_value the value returned on timeout
79
+ # @return [Object, timeout_value] reply to the message
80
+ # @raise [NoReply, NoActor]
81
+ def ask(message, timeout = nil, timeout_value = nil)
82
+ @Actor.ask message, timeout, timeout_value
83
+ end
84
+
85
+ # Same as {#tell} but represented as a {Promises::Future}.
86
+ # @param [Object] message
87
+ # @param [Promises::ResolvableFuture] probe
88
+ # a resolvable future which is resolved with the reply.
89
+ # @return [Promises::Future(Object)] reply to the message
90
+ def ask_op(message, probe = Promises.resolvable_future)
91
+ @Actor.ask_op message, probe
92
+ end
93
+
94
+ # @!macro erlang_actor.terminated
95
+ # @return [Promises::Future] a future which is resolved with
96
+ # the final result of the actor that is either the reason for
97
+ # termination or a value if terminated normally.
98
+ def terminated
99
+ @Actor.terminated
100
+ end
101
+
102
+ # @return [#to_s, nil] optional name of the actor
103
+ def name
104
+ @Name
105
+ end
106
+
107
+ # @return [String] string representation
108
+ def to_s
109
+ original = super
110
+ state = case terminated.state
111
+ when :pending
112
+ 'running'
113
+ when :fulfilled
114
+ "terminated normally with #{terminated.value}"
115
+ when :rejected
116
+ "terminated because of #{terminated.reason}"
117
+ else
118
+ raise
119
+ end
120
+ [original[0..-2], *@Name, state].join(' ') << '>'
121
+ end
122
+
123
+ alias_method :inspect, :to_s
124
+
125
+ private
126
+
127
+ safe_initialization!
128
+
129
+ def initialize(actor, name)
130
+ @Actor = actor
131
+ @Name = name
132
+ end
133
+ end
134
+
135
+ # An object representing instance of a monitor, created with {Environment#monitor}.
136
+ class Reference
137
+ end
138
+
139
+ # A class providing environment and methods for actor bodies to run in.
140
+ class Environment < Synchronization::Object
141
+ safe_initialization!
142
+
143
+ # @!macro erlang_actor.terminated
144
+ def terminated
145
+ @Actor.terminated
146
+ end
147
+
148
+ # @return [Pid] the pid of this actor
149
+ def pid
150
+ @Actor.pid
151
+ end
152
+
153
+ # @return [#to_s] the name od the actor if provided to spawn method
154
+ def name
155
+ pid.name
156
+ end
157
+
158
+ # @return [true, false] does this actor trap exit messages?
159
+ # @see http://www1.erlang.org/doc/man/erlang.html#process_flag-2
160
+ def traps?
161
+ @Actor.traps?
162
+ end
163
+
164
+ # When trap is set to true,
165
+ # exit signals arriving to a actor are converted to {Terminated} messages,
166
+ # which can be received as ordinary messages.
167
+ # If trap is set to false,
168
+ # the actor exits
169
+ # if it receives an exit signal other than normal
170
+ # and the exit signal is propagated to its linked actors.
171
+ # Application actors should normally not trap exits.
172
+ #
173
+ # @param [true, false] value
174
+ # @return [true, false] the old value of the flag
175
+ # @see http://www1.erlang.org/doc/man/erlang.html#process_flag-2
176
+ def trap(value = true)
177
+ @Actor.trap(value)
178
+ end
179
+
180
+ # Helper for constructing a {#receive} rules
181
+ # @see #receive
182
+ # @example
183
+ # receive on(Numeric) { |v| v.succ },
184
+ # on(ANY) { terminate :bad_message }
185
+ def on(matcher, value = nil, &block)
186
+ @Actor.on matcher, value, &block
187
+ end
188
+
189
+ # Receive a message.
190
+ #
191
+ # @param [::Array(), ::Array(#===), ::Array<::Array(#===, Proc)>] rules
192
+ # * No rule - `receive`, `receive {|m| m.to_s}`
193
+ # * or single rule which can be combined with the supplied block -
194
+ # `receive(Numeric)`, `receive(Numeric) {|v| v.succ}`
195
+ # * or array of matcher-proc pairs -
196
+ # `receive on(Numeric) { |v| v*2 }, on(Symbol) { |c| do_command c }`
197
+ # @param [Numeric] timeout
198
+ # how long it should wait for the message
199
+ # @param [Object] timeout_value
200
+ # if rule `on(TIMEOUT) { do_something }` is not specified
201
+ # then timeout_value is returned.
202
+ # @return [Object, nothing]
203
+ # depends on type of the actor.
204
+ # On thread it blocks until message is available
205
+ # then it returns the message (or a result of a called block).
206
+ # On pool it stops executing and continues with a given block
207
+ # when message becomes available.
208
+ # @param [Hash] options
209
+ # other options specific by type of the actor
210
+ # @option options [true, false] :keep
211
+ # Keep the rules and repeatedly call the associated blocks,
212
+ # until receive is called again.
213
+ # @yield [message] block
214
+ # to process the message
215
+ # if single matcher is supplied
216
+ # @yieldparam [Object] message the received message
217
+ # @see ErlangActor Receiving chapter in the ErlangActor examples
218
+ def receive(*rules, timeout: nil, timeout_value: nil, **options, &block)
219
+ @Actor.receive(*rules, timeout: timeout, timeout_value: timeout_value, **options, &block)
220
+ end
221
+
222
+ # Creates a link between the calling actor and another actor,
223
+ # if there is not such a link already.
224
+ # If a actor attempts to create a link to itself, nothing is done. Returns true.
225
+ #
226
+ # If pid does not exist,
227
+ # the behavior of the method depends on
228
+ # if the calling actor is trapping exits or not (see {#trap}):
229
+ # * If the calling actor is not trapping exits link raises with {NoActor}.
230
+ # * Otherwise, if the calling actor is trapping exits, link returns true,
231
+ # but an exit signal with reason noproc is sent to the calling actor.
232
+ #
233
+ # @return [true]
234
+ # @raise [NoActor]
235
+ # @see http://www1.erlang.org/doc/man/erlang.html#link-1
236
+ def link(pid)
237
+ @Actor.link(pid)
238
+ end
239
+
240
+ # Removes the link, if there is one,
241
+ # between the calling actor and the actor referred to by pid.
242
+ #
243
+ # Returns true and does not fail, even if there is no link to Id, or if Id does not exist.
244
+ #
245
+ # Once unlink(pid) has returned
246
+ # it is guaranteed
247
+ # that the link between the caller and the actor referred to by pid
248
+ # has no effect on the caller in the future (unless the link is setup again).
249
+ # If caller is trapping exits,
250
+ # an {Terminated} message due to the link might have been placed
251
+ # in the caller's message queue prior to the call, though.
252
+ #
253
+ # Note, the {Terminated} message can be the result of the link,
254
+ # but can also be the result of calling #terminate method externally.
255
+ # Therefore, it may be appropriate to cleanup the message queue
256
+ # when trapping exits after the call to unlink, as follow:
257
+ # ```ruby
258
+ # receive on(And[Terminated, -> e { e.pid == pid }], true), timeout: 0
259
+ # ```
260
+ #
261
+ # @return [true]
262
+ def unlink(pid)
263
+ @Actor.unlink(pid)
264
+ end
265
+
266
+ # @!visibility private
267
+ # @return [true, false]
268
+ def linked?(pid)
269
+ @Actor.linked? pid
270
+ end
271
+
272
+ # The calling actor starts monitoring actor with given pid.
273
+ #
274
+ # A {DownSignal} message will be sent to the monitoring actor
275
+ # if the actor with given pid dies,
276
+ # or if the actor with given pid does not exist.
277
+ #
278
+ # The monitoring is turned off either
279
+ # when the {DownSignal} message is sent, or when {#demonitor} is called.
280
+ #
281
+ # Making several calls to monitor for the same pid is not an error;
282
+ # it results in as many, completely independent, monitorings.
283
+ #
284
+ # @return [Reference]
285
+ def monitor(pid)
286
+ @Actor.monitor(pid)
287
+ end
288
+
289
+ # If MonitorRef is a reference which the calling actor obtained by calling {#monitor},
290
+ # this monitoring is turned off.
291
+ # If the monitoring is already turned off, nothing happens.
292
+ #
293
+ # Once demonitor has returned it is guaranteed that no {DownSignal} message
294
+ # due to the monitor will be placed in the caller's message queue in the future.
295
+ # A {DownSignal} message might have been placed in the caller's message queue prior to the call, though.
296
+ # Therefore, in most cases, it is advisable to remove such a 'DOWN' message from the message queue
297
+ # after monitoring has been stopped.
298
+ # `demonitor(reference, :flush)` can be used if this cleanup is wanted.
299
+ #
300
+ # The behavior of this method can be viewed as two combined operations:
301
+ # asynchronously send a "demonitor signal" to the monitored actor and
302
+ # ignore any future results of the monitor.
303
+ #
304
+ # Failure: It is an error if reference refers to a monitoring started by another actor.
305
+ # In that case it may raise an ArgumentError or go unnoticed.
306
+ #
307
+ # Options:
308
+ # * `:flush` - Remove (one) {DownSignal} message,
309
+ # if there is one, from the caller's message queue after monitoring has been stopped.
310
+ # Calling `demonitor(pid, :flush)` is equivalent to the following, but more efficient:
311
+ # ```ruby
312
+ # demonitor(pid)
313
+ # receive on(And[DownSignal, -> d { d.reference == reference}], true), timeout: 0, timeout_value: true
314
+ # ```
315
+ #
316
+ # * `info`
317
+ # The returned value is one of the following:
318
+ #
319
+ # - `true` - The monitor was found and removed.
320
+ # In this case no {DownSignal} message due to this monitor have been
321
+ # nor will be placed in the message queue of the caller.
322
+ # - `false` - The monitor was not found and could not be removed.
323
+ # This probably because someone already has placed a {DownSignal} message
324
+ # corresponding to this monitor in the caller's message queue.
325
+ #
326
+ # If the info option is combined with the flush option,
327
+ # `false` will be returned if a flush was needed; otherwise, `true`.
328
+ #
329
+ # @param [Reference] reference
330
+ # @param [:flush, :info] options
331
+ # @return [true, false]
332
+ def demonitor(reference, *options)
333
+ @Actor.demonitor(reference, *options)
334
+ end
335
+
336
+ # @!visibility private
337
+ def monitoring?(reference)
338
+ @Actor.monitoring? reference
339
+ end
340
+
341
+ # Creates an actor.
342
+ #
343
+ # @param [Object] args arguments for the actor body
344
+ # @param [:on_thread, :on_pool] type
345
+ # of the actor to be created.
346
+ # @param [Channel] channel
347
+ # The mailbox of the actor, by default it has unlimited capacity.
348
+ # Crating the actor with a bounded queue is useful to create backpressure.
349
+ # The channel can be shared with other abstractions
350
+ # but actor has to be the only consumer
351
+ # otherwise internal signals could be lost.
352
+ # @param [Environment, Module] environment
353
+ # A class which is used to run the body of the actor in.
354
+ # It can either be a child of {Environment} or a module.
355
+ # Module is extended to a new instance of environment,
356
+ # therefore if there is many actors with this module
357
+ # it is better to create a class and use it instead.
358
+ # @param [#to_s] name of the actor.
359
+ # Available by {Pid#name} or {Environment#name} and part of {Pid#to_s}.
360
+ # @param [true, false] link
361
+ # the created actor is atomically created and linked with the calling actor
362
+ # @param [true, false] monitor
363
+ # the created actor is atomically created and monitored by the calling actor
364
+ # @param [ExecutorService] executor
365
+ # The executor service to use to execute the actor on.
366
+ # Applies only to :on_pool actor type.
367
+ # @yield [*args] the body of the actor.
368
+ # When actor is spawned this block is evaluated
369
+ # until it terminates.
370
+ # The on-thread actor requires a block.
371
+ # The on-poll actor has a default `-> { start }`,
372
+ # therefore if not block is given it executes a #start method
373
+ # which needs to be provided with environment.
374
+ # @return [Pid, ::Array(Pid, Reference)] a pid or a pid-reference pair when monitor is true
375
+ # @see http://www1.erlang.org/doc/man/erlang.html#spawn-1
376
+ # @see http://www1.erlang.org/doc/man/erlang.html#spawn_link-1
377
+ # @see http://www1.erlang.org/doc/man/erlang.html#spawn_monitor-1
378
+ def spawn(*args,
379
+ type: @Actor.class,
380
+ channel: Promises::Channel.new,
381
+ environment: Environment,
382
+ name: nil,
383
+ executor: default_executor,
384
+ link: false,
385
+ monitor: false,
386
+ &body)
387
+
388
+ @Actor.spawn(*args,
389
+ type: type,
390
+ channel: channel,
391
+ environment: environment,
392
+ name: name,
393
+ executor: executor,
394
+ link: link,
395
+ monitor: monitor,
396
+ &body)
397
+ end
398
+
399
+ # Shortcut for fulfilling the reply, same as `reply_resolution true, value, nil`.
400
+ # @example
401
+ # actor = Concurrent::ErlangActor.spawn(:on_thread) { reply receive * 2 }
402
+ # actor.ask 2 #=> 4
403
+ # @param [Object] value
404
+ # @return [true, false] did the sender ask, and was it resolved
405
+ def reply(value)
406
+ # TODO (pitr-ch 08-Feb-2019): consider adding reply? which returns true,false if success, reply method will always return value
407
+ reply_resolution true, value, nil
408
+ end
409
+
410
+ # Reply to the sender of the message currently being processed
411
+ # if the actor was asked instead of told.
412
+ # The reply is stored in a {Promises::ResolvableFuture}
413
+ # so the arguments are same as for {Promises::ResolvableFuture#resolve} method.
414
+ #
415
+ # The reply may timeout, then this will fail with false.
416
+ #
417
+ # @param [true, false] fulfilled
418
+ # @param [Object] value
419
+ # @param [Object] reason
420
+ #
421
+ # @example
422
+ # actor = Concurrent::ErlangActor.spawn(:on_thread) { reply_resolution true, receive * 2, nil }
423
+ # actor.ask 2 #=> 4
424
+ #
425
+ # @return [true, false] did the sender ask, and was it resolved before it timed out?
426
+ def reply_resolution(fulfilled = true, value = nil, reason = nil)
427
+ @Actor.reply_resolution(fulfilled, value, reason)
428
+ end
429
+
430
+ # If pid **is not** provided stops the execution of the calling actor
431
+ # with the exit reason.
432
+ #
433
+ # If pid **is** provided,
434
+ # it sends an exit signal with exit reason to the actor identified by pid.
435
+ #
436
+ # The following behavior apply
437
+ # if `reason` is any object except `:normal` or `:kill`.
438
+ # If pid is not trapping exits,
439
+ # pid itself will exit with exit reason.
440
+ # If pid is trapping exits,
441
+ # the exit signal is transformed into a message {Terminated}
442
+ # and delivered to the message queue of pid.
443
+ #
444
+ # If reason is the Symbol `:normal`, pid will not exit.
445
+ # If it is trapping exits, the exit signal is transformed into a message {Terminated}
446
+ # and delivered to its message queue.
447
+ #
448
+ # If reason is the Symbol `:kill`, that is if `exit(pid, :kill)` is called,
449
+ # an untrappable exit signal is sent to pid which will unconditionally exit
450
+ # with exit reason `:killed`.
451
+ #
452
+ # Since evaluating this function causes the process to terminate, it has no return value.
453
+ #
454
+ # @param [Pid] pid
455
+ # @param [Object, :normal, :kill] reason
456
+ # @param [Object] value
457
+ # @return [nothing]
458
+ # @see http://www1.erlang.org/doc/man/erlang.html#error-1
459
+ # @see http://www1.erlang.org/doc/man/erlang.html#error-2
460
+ def terminate(pid = nil, reason, value: nil)
461
+ @Actor.terminate pid, reason, value: value
462
+ end
463
+
464
+ # @return [ExecutorService] a default executor which is picked by spawn call
465
+ def default_executor
466
+ @DefaultExecutor
467
+ end
468
+
469
+ private
470
+
471
+ def initialize(actor, executor)
472
+ super()
473
+ @Actor = actor
474
+ @DefaultExecutor = executor
475
+ end
476
+ end
477
+
478
+ # A module containing entry functions to actors like spawn_actor, terminate_actor.
479
+ # It can be included in environments working with actors.
480
+ # @example
481
+ # include Concurrent::ErlangActors::Functions
482
+ # actor = spawn_actor :on_pool do
483
+ # receive { |data| process data }
484
+ # end
485
+ # @see FunctionShortcuts
486
+ module Functions
487
+ # Creates an actor. Same as {Environment#spawn} but lacks link and monitor options.
488
+ # @param [Object] args
489
+ # @param [:on_thread, :on_pool] type
490
+ # @param [Channel] channel
491
+ # @param [Environment, Module] environment
492
+ # @param [#to_s] name of the actor
493
+ # @param [ExecutorService] executor of the actor
494
+ # @return [Pid]
495
+ # @see Environment#spawn
496
+ def spawn_actor(*args,
497
+ type:,
498
+ channel: Promises::Channel.new,
499
+ environment: Environment,
500
+ name: nil,
501
+ executor: default_actor_executor,
502
+ &body)
503
+
504
+ actor = ErlangActor.create type, channel, environment, name, executor
505
+ actor.run(*args, &body)
506
+ return actor.pid
507
+ end
508
+
509
+ # Same as {Environment#terminate}, but it requires pid.
510
+ # @param [Pid] pid
511
+ # @param [Object, :normal, :kill] reason
512
+ # @return [true]
513
+ def terminate_actor(pid, reason)
514
+ if reason == :kill
515
+ pid.tell Kill.new(nil)
516
+ else
517
+ pid.tell Terminate.new(nil, reason, false)
518
+ end
519
+ true
520
+ end
521
+
522
+ # @return [ExecutorService] the default executor service for actors
523
+ def default_actor_executor
524
+ default_executor
525
+ end
526
+
527
+ # @return [ExecutorService] the default executor service,
528
+ # may be shared by other abstractions
529
+ def default_executor
530
+ :io
531
+ end
532
+ end
533
+
534
+ # Constrains shortcuts for methods in {Functions}.
535
+ module FunctionShortcuts
536
+ # Optionally included shortcut method for {Functions#spawn_actor}
537
+ # @return [Pid]
538
+ def spawn(*args, **kwargs, &body)
539
+ spawn_actor(*args, **kwargs, &body)
540
+ end
541
+
542
+ # Optionally included shortcut method for {Functions#terminate_actor}
543
+ # @return [true]
544
+ def terminate(pid, reason)
545
+ terminate_actor(pid, reason)
546
+ end
547
+ end
548
+
549
+ extend Functions
550
+ extend FunctionShortcuts
551
+ extend Concern::Logging
552
+
553
+ class Token
554
+ def initialize(name)
555
+ @name = name
556
+ end
557
+
558
+ def to_s
559
+ @name
560
+ end
561
+
562
+ alias_method :inspect, :to_s
563
+ end
564
+
565
+ private_constant :Token
566
+
567
+ JUMP = Token.new 'JUMP'
568
+ TERMINATE = Token.new 'TERMINATE'
569
+ RECEIVE = Token.new 'RECEIVE'
570
+ NOTHING = Token.new 'NOTHING'
571
+
572
+ private_constant :JUMP
573
+ private_constant :TERMINATE
574
+ private_constant :RECEIVE
575
+ private_constant :NOTHING
576
+
577
+ # These constants are useful
578
+ # where the body of an actor is defined.
579
+ # For convenience they are provided in this module for including.
580
+ # @example
581
+ # include Concurrent::ErlangActor::EnvironmentConstants
582
+ # actor = Concurrent::ErlangActor.spawn(:on_thread) do
583
+ # receive on(Numeric) { |v| v.succ },
584
+ # on(ANY) { terminate :bad_message },
585
+ # on(TIMEOUT) { terminate :no_message },
586
+ # timeout: 1
587
+ # end
588
+ module EnvironmentConstants
589
+ # Unique identifier of a timeout, singleton.
590
+ TIMEOUT = Token.new 'TIMEOUT'
591
+ # A singleton which matches anything using #=== method
592
+ ANY = Promises::Channel::ANY
593
+
594
+ class AbstractLogicOperationMatcher
595
+ def self.[](*matchers)
596
+ new(*matchers)
597
+ end
598
+
599
+ def initialize(*matchers)
600
+ @matchers = matchers
601
+ end
602
+ end
603
+
604
+ # Combines matchers into one which matches if all match.
605
+ # @example
606
+ # And[Numeric, -> v { v >= 0 }] === 1 # => true
607
+ # And[Numeric, -> v { v >= 0 }] === -1 # => false
608
+ class And < AbstractLogicOperationMatcher
609
+ # @return [true, false]
610
+ def ===(v)
611
+ @matchers.all? { |m| m === v }
612
+ end
613
+ end
614
+
615
+ # Combines matchers into one which matches if any matches.
616
+ # @example
617
+ # Or[Symbol, String] === :v # => true
618
+ # Or[Symbol, String] === 'v' # => true
619
+ # Or[Symbol, String] === 1 # => false
620
+ class Or < AbstractLogicOperationMatcher
621
+ # @return [true, false]
622
+ def ===(v)
623
+ @matchers.any? { |m| m === v }
624
+ end
625
+ end
626
+ end
627
+
628
+ include EnvironmentConstants
629
+
630
+ class Run
631
+ attr_reader :future
632
+
633
+ def self.[](future)
634
+ new future
635
+ end
636
+
637
+ def initialize(future)
638
+ @future = future
639
+ end
640
+
641
+ TEST = -> v { v.future if v.is_a?(Run) }
642
+ end
643
+ private_constant :Run
644
+
645
+ class AbstractActor < Synchronization::Object
646
+
647
+ include EnvironmentConstants
648
+ include Concern::Logging
649
+ safe_initialization!
650
+
651
+ # @param [Promises::Channel] mailbox
652
+ def initialize(mailbox, environment, name, executor)
653
+ super()
654
+ @Mailbox = mailbox
655
+ @Pid = Pid.new self, name
656
+ @Linked = ::Set.new
657
+ @Monitors = {}
658
+ @Monitoring = {}
659
+ @MonitoringLateDelivery = {}
660
+ @Terminated = Promises.resolvable_future
661
+ @trap = false
662
+ @reply = nil
663
+
664
+ @Environment = if environment.is_a?(Class) && environment <= Environment
665
+ environment.new self, executor
666
+ elsif environment.is_a? Module
667
+ e = Environment.new self, executor
668
+ e.extend environment
669
+ e
670
+ else
671
+ raise ArgumentError,
672
+ "environment has to be a class inheriting from Environment or a module"
673
+ end
674
+ end
675
+
676
+ def tell_op(message)
677
+ log Logger::DEBUG, @Pid, told: message
678
+ if (mailbox = @Mailbox)
679
+ mailbox.push_op(message).then { @Pid }
680
+ else
681
+ Promises.fulfilled_future @Pid
682
+ end
683
+ end
684
+
685
+ def tell(message, timeout = nil)
686
+ log Logger::DEBUG, @Pid, told: message
687
+ if (mailbox = @Mailbox)
688
+ timed_out = mailbox.push message, timeout
689
+ timeout ? timed_out : @Pid
690
+ else
691
+ timeout ? false : @Pid
692
+ end
693
+ end
694
+
695
+ def ask(message, timeout, timeout_value)
696
+ log Logger::DEBUG, @Pid, asked: message
697
+ if @Terminated.resolved?
698
+ raise NoActor.new(@Pid)
699
+ else
700
+ probe = Promises.resolvable_future
701
+ question = Ask.new(message, probe)
702
+ if timeout
703
+ start = Concurrent.monotonic_time
704
+ in_time = tell question, timeout
705
+ # recheck it could have in the meantime terminated and drained mailbox
706
+ raise NoActor.new(@Pid) if @Terminated.resolved?
707
+ # has to be after resolved check, to catch case where it would return timeout_value
708
+ # when it was actually terminated
709
+ to_wait = if in_time
710
+ time = timeout - (Concurrent.monotonic_time - start)
711
+ time >= 0 ? time : 0
712
+ else
713
+ 0
714
+ end
715
+ # TODO (pitr-ch 06-Feb-2019): allow negative timeout everywhere, interpret as 0
716
+ probe.value! to_wait, timeout_value, [true, nil, nil]
717
+ else
718
+ raise NoActor.new(@Pid) if @Terminated.resolved?
719
+ tell question
720
+ probe.reject NoActor.new(@Pid), false if @Terminated.resolved?
721
+ probe.value!
722
+ end
723
+ end
724
+ end
725
+
726
+ def ask_op(message, probe)
727
+ log Logger::DEBUG, @Pid, asked: message
728
+ if @Terminated.resolved?
729
+ probe.reject NoActor.new(@Pid), false
730
+ else
731
+ tell_op(Ask.new(message, probe)).then do
732
+ probe.reject NoActor.new(@Pid), false if @Terminated.resolved?
733
+ probe
734
+ end.flat
735
+ end
736
+ end
737
+
738
+ def terminated
739
+ @Terminated.with_hidden_resolvable
740
+ end
741
+
742
+ def pid
743
+ @Pid
744
+ end
745
+
746
+ def traps?
747
+ @trap
748
+ end
749
+
750
+ def trap(value = true)
751
+ old = @trap
752
+ # noinspection RubySimplifyBooleanInspection
753
+ @trap = !!value
754
+ old
755
+ end
756
+
757
+ def on(matcher, value = nil, &block)
758
+ raise ArgumentError, 'only one of block or value can be supplied' if block && value
759
+ [matcher, value || block]
760
+ end
761
+
762
+ def receive(*rules, timeout: nil, timeout_value: nil, **options, &block)
763
+ raise NotImplementedError
764
+ end
765
+
766
+ def link(pid)
767
+ return true if pid == @Pid
768
+ if @Linked.add? pid
769
+ pid.tell Link.new(@Pid)
770
+ if pid.terminated.resolved?
771
+ # no race since it only could get NoActor
772
+ if @trap
773
+ tell Terminate.new pid, NoActor.new(pid)
774
+ else
775
+ @Linked.delete pid
776
+ raise NoActor.new(pid)
777
+ end
778
+ end
779
+ end
780
+ true
781
+ end
782
+
783
+ def unlink(pid)
784
+ pid.tell UnLink.new(@Pid) if @Linked.delete pid
785
+ true
786
+ end
787
+
788
+ def linked?(pid)
789
+ @Linked.include? pid
790
+ end
791
+
792
+ def monitor(pid)
793
+ # *monitoring* *monitored*
794
+ # send Monitor
795
+ # terminated?
796
+ # terminate before getting Monitor
797
+ # drain signals including the Monitor
798
+ reference = Reference.new
799
+ @Monitoring[reference] = pid
800
+ if pid.terminated.resolved?
801
+ # always return no-proc when terminated
802
+ tell DownSignal.new(pid, reference, NoActor.new(pid))
803
+ else
804
+ # otherwise let it race
805
+ pid.tell Monitor.new(@Pid, reference)
806
+ # no race, it cannot get anything else than NoActor
807
+ tell DownSignal.new(pid, reference, NoActor.new(pid)) if pid.terminated.resolved?
808
+ end
809
+ reference
810
+ end
811
+
812
+ def demonitor(reference, *options)
813
+ info = options.delete :info
814
+ flush = options.delete :flush
815
+ raise ArgumentError, "bad options #{options}" unless options.empty?
816
+
817
+ pid = @Monitoring.delete reference
818
+ demonitoring = !!pid
819
+ pid.tell DeMonitor.new @Pid, reference if demonitoring
820
+
821
+ if flush
822
+ # remove (one) down message having reference from mailbox
823
+ flushed = demonitoring ? !!@Mailbox.try_pop_matching(And[DownSignal, -> m { m.reference == reference }]) : false
824
+ return info ? !flushed : true
825
+ end
826
+
827
+ if info
828
+ return false unless demonitoring
829
+
830
+ if @Mailbox.peek_matching(And[DownSignal, -> m { m.reference == reference }])
831
+ @MonitoringLateDelivery[reference] = pid # allow to deliver the message once
832
+ return false
833
+ end
834
+ end
835
+
836
+ return true
837
+ end
838
+
839
+ def monitoring?(reference)
840
+ @Monitoring.include? reference
841
+ end
842
+
843
+ def spawn(*args,
844
+ type:,
845
+ channel:,
846
+ environment:,
847
+ name:,
848
+ link:,
849
+ monitor:,
850
+ executor:,
851
+ &body)
852
+ actor = ErlangActor.create type, channel, environment, name, executor
853
+ pid = actor.pid
854
+ link pid if link
855
+ ref = (monitor pid if monitor)
856
+ actor.run(*args, &body)
857
+ monitor ? [pid, ref] : pid
858
+ end
859
+
860
+ def reply_resolution(fulfilled, value, reason)
861
+ return false unless @reply
862
+ return !!@reply.resolve(fulfilled, value, reason, false)
863
+ end
864
+
865
+ def terminate(pid = nil, reason, value: nil)
866
+ if pid
867
+ # has to send it to itself even if pid equals self.pid
868
+ if reason == :kill
869
+ pid.tell Kill.new(@Pid)
870
+ else
871
+ pid.tell Terminate.new(@Pid, reason, false)
872
+ end
873
+ else
874
+ terminate_self(reason, value)
875
+ end
876
+ end
877
+
878
+ private
879
+
880
+ def canonical_rules(rules, timeout, timeout_value, given_block)
881
+ block = given_block || -> v { v }
882
+ case rules.size
883
+ when 0
884
+ rules.push(on(ANY, &block))
885
+ when 1
886
+ matcher = rules.first
887
+ if matcher.is_a?(::Array) && matcher.size == 2
888
+ return ArgumentError.new 'a block cannot be given if full rules are used' if given_block
889
+ else
890
+ rules.replace([on(matcher, &block)])
891
+ end
892
+ else
893
+ return ArgumentError.new 'a block cannot be given if full rules are used' if given_block
894
+ end
895
+
896
+ if timeout
897
+ # TIMEOUT rule has to be first, to prevent any picking it up ANY
898
+ has_timeout = nil
899
+ i = rules.size
900
+ rules.reverse_each do |r, _|
901
+ i -= 1
902
+ if r == TIMEOUT
903
+ has_timeout = i
904
+ break
905
+ end
906
+ end
907
+
908
+ rules.unshift(has_timeout ? rules[has_timeout] : on(TIMEOUT, timeout_value))
909
+ end
910
+ nil
911
+ end
912
+
913
+ def eval_task(message, job)
914
+ if job.is_a? Proc
915
+ @Environment.instance_exec message, &job
916
+ else
917
+ job
918
+ end
919
+ end
920
+
921
+ def send_exit_messages(reason)
922
+ @Linked.each do |pid|
923
+ pid.tell Terminate.new(@Pid, reason)
924
+ end.clear
925
+ @Monitors.each do |reference, pid|
926
+ pid.tell DownSignal.new(@Pid, reference, reason)
927
+ end.clear
928
+ end
929
+
930
+ def asked?
931
+ !!@reply
932
+ end
933
+
934
+ def clean_reply(reason = NoReply)
935
+ if @reply
936
+ @reply.reject(reason, false)
937
+ @reply = nil
938
+ end
939
+ end
940
+
941
+ def consume_signal(message)
942
+ if AbstractSignal === message
943
+ case message
944
+ when Ask
945
+ @reply = message.probe
946
+ message.message
947
+ when Link
948
+ @Linked.add message.from
949
+ NOTHING
950
+ when UnLink
951
+ @Linked.delete message.from
952
+ NOTHING
953
+ when Monitor
954
+ @Monitors[message.reference] = message.from
955
+ NOTHING
956
+ when DeMonitor
957
+ @Monitors.delete message.reference
958
+ NOTHING
959
+ when Kill
960
+ terminate :killed
961
+ when DownSignal
962
+ if @Monitoring.delete(message.reference) || @MonitoringLateDelivery.delete(message.reference)
963
+ # put into a queue
964
+ return Down.new(message.from, message.reference, message.info)
965
+ end
966
+
967
+ # ignore down message if no longer monitoring, and following case
968
+ #
969
+ # *monitoring* *monitored*
970
+ # send Monitor
971
+ # terminate
972
+ # terminated?
973
+ # drain signals # generates second DOWN which is dropped here
974
+ # already reported as :noproc
975
+ NOTHING
976
+ when Terminate
977
+ consume_exit message
978
+ else
979
+ raise "unknown message #{message}"
980
+ end
981
+ else
982
+ # regular message
983
+ message
984
+ end
985
+ end
986
+
987
+ def consume_exit(exit_message)
988
+ from, reason = exit_message
989
+ if !exit_message.link_terminated || @Linked.delete(from)
990
+ case reason
991
+ when :normal
992
+ if @trap
993
+ Terminated.new from, reason
994
+ else
995
+ if from == @Pid
996
+ terminate :normal
997
+ else
998
+ NOTHING # do nothing
999
+ end
1000
+ end
1001
+ else
1002
+ if @trap
1003
+ Terminated.new from, reason
1004
+ else
1005
+ terminate reason
1006
+ end
1007
+ end
1008
+ else
1009
+ # *link* *exiting*
1010
+ # send Link
1011
+ # terminate
1012
+ # terminated?
1013
+ # drain signals # generates second Terminated which is dropped here
1014
+ # already processed exit message, do nothing
1015
+ NOTHING
1016
+ end
1017
+ end
1018
+
1019
+ def initial_signal_consumption
1020
+ while true
1021
+ message = @Mailbox.try_pop
1022
+ break unless message
1023
+ consume_signal(message) == NOTHING or raise 'it was not consumable signal'
1024
+ end
1025
+ end
1026
+
1027
+ def terminate_self(reason, value)
1028
+ raise NotImplementedError
1029
+ end
1030
+
1031
+ def after_termination(final_reason)
1032
+ log Logger::DEBUG, @Pid, terminated: final_reason
1033
+ clean_reply NoActor.new(@Pid)
1034
+ while true
1035
+ message = @Mailbox.try_pop NOTHING
1036
+ break if message == NOTHING
1037
+ case message
1038
+ when Monitor
1039
+ # The actor is terminated so we must return NoActor,
1040
+ # even though we still know the reason.
1041
+ # Otherwise it would return different reasons non-deterministically.
1042
+ message.from.tell DownSignal.new(@Pid, message.reference, NoActor.new(@Pid))
1043
+ when Link
1044
+ # same as for Monitor
1045
+ message.from.tell NoActor.new(@Pid)
1046
+ when Ask
1047
+ message.probe.reject(NoActor.new(@Pid), false)
1048
+ else
1049
+ # normal messages and other signals are thrown away
1050
+ end
1051
+ end
1052
+ @Mailbox = nil
1053
+ end
1054
+ end
1055
+
1056
+ private_constant :AbstractActor
1057
+
1058
+ class OnPool < AbstractActor
1059
+
1060
+ def initialize(channel, environment, name, executor)
1061
+ super channel, environment, name, executor
1062
+ @Executor = executor
1063
+ @behaviour = []
1064
+ @keep_behaviour = false
1065
+ end
1066
+
1067
+ def run(*args, &body)
1068
+ body ||= -> { start }
1069
+
1070
+ initial_signal_consumption
1071
+ inner_run(*args, &body).
1072
+ run(Run::TEST).
1073
+ then(&method(:after_termination)).
1074
+ rescue { |e| log Logger::ERROR, e }
1075
+ end
1076
+
1077
+ def receive(*rules, timeout: nil, timeout_value: nil, keep: false, &given_block)
1078
+ clean_reply
1079
+ err = canonical_rules rules, timeout, timeout_value, given_block
1080
+ raise err if err
1081
+
1082
+ @keep_behaviour = keep
1083
+ @timeout = timeout
1084
+ @behaviour = rules
1085
+ throw JUMP, [RECEIVE]
1086
+ end
1087
+
1088
+ private
1089
+
1090
+ def terminate_self(reason, value)
1091
+ throw JUMP, [TERMINATE, reason, value]
1092
+ end
1093
+
1094
+ def inner_run(*args, &body)
1095
+ first = !!body
1096
+ future_body = -> message, _actor do
1097
+ kind, reason, value =
1098
+ if message.is_a?(::Array) && message.first == TERMINATE
1099
+ message
1100
+ else
1101
+ begin
1102
+ catch(JUMP) do
1103
+ [NOTHING,
1104
+ :normal,
1105
+ first ? @Environment.instance_exec(*args, &body) : apply_behaviour(message)]
1106
+ end
1107
+ rescue => e
1108
+ [TERMINATE, e, nil]
1109
+ end
1110
+ end
1111
+
1112
+ case kind
1113
+ when TERMINATE
1114
+ send_exit_messages reason
1115
+ @Terminated.resolve(reason == :normal, value, reason)
1116
+ reason
1117
+ when RECEIVE
1118
+ Run[inner_run]
1119
+ when NOTHING
1120
+ if @behaviour.empty?
1121
+ send_exit_messages reason
1122
+ @Terminated.resolve(reason == :normal, value, reason)
1123
+ reason
1124
+ else
1125
+ Run[inner_run]
1126
+ end
1127
+ else
1128
+ raise "bad kind: #{kind.inspect}"
1129
+ end
1130
+ end
1131
+
1132
+ if first
1133
+ Promises.future_on(@Executor, nil, self, &future_body)
1134
+ else
1135
+ internal_receive.run(Run::TEST).then(self, &future_body)
1136
+ end
1137
+ end
1138
+
1139
+ def internal_receive
1140
+ raise if @behaviour.empty?
1141
+ rules_matcher = Or[*@behaviour.map(&:first)]
1142
+ matcher = -> m { m.is_a?(Ask) ? rules_matcher === m.message : rules_matcher === m }
1143
+ start = nil
1144
+ message_future = case @timeout
1145
+ when 0
1146
+ Promises.fulfilled_future @Mailbox.try_pop_matching(matcher, TIMEOUT)
1147
+ when Numeric
1148
+ pop = @Mailbox.pop_op_matching(matcher)
1149
+ start = Concurrent.monotonic_time
1150
+ # FIXME (pitr-ch 30-Jan-2019): the scheduled future should be cancelled
1151
+ (Promises.schedule(@timeout) { TIMEOUT } | pop).then(pop) do |message, p|
1152
+ if message == TIMEOUT && !p.resolve(true, TIMEOUT, nil, false)
1153
+ # timeout raced with probe resolution, take the value instead
1154
+ p.value
1155
+ else
1156
+ message
1157
+ end
1158
+ end
1159
+ when nil
1160
+ @Mailbox.pop_op_matching(matcher)
1161
+ else
1162
+ raise
1163
+ end
1164
+
1165
+ message_future.then(start, self) do |message, s, _actor|
1166
+ log Logger::DEBUG, pid, got: message
1167
+ catch(JUMP) do
1168
+ if (message = consume_signal(message)) == NOTHING
1169
+ @timeout = [@timeout + s - Concurrent.monotonic_time, 0].max if s
1170
+ Run[internal_receive]
1171
+ else
1172
+ message
1173
+ end
1174
+ end
1175
+ end
1176
+ end
1177
+
1178
+ def apply_behaviour(message)
1179
+ @behaviour.each do |rule, job|
1180
+ if rule === message
1181
+ @behaviour = [] unless @keep_behaviour
1182
+ return eval_task(message, job)
1183
+ end
1184
+ end
1185
+ raise 'should not reach'
1186
+ end
1187
+ end
1188
+
1189
+ private_constant :OnPool
1190
+
1191
+ class OnThread < AbstractActor
1192
+ def initialize(channel, environment, name, executor)
1193
+ super channel, environment, name, executor
1194
+ @Thread = nil
1195
+ end
1196
+
1197
+ TERMINATE = Module.new
1198
+ private_constant :TERMINATE
1199
+
1200
+ def run(*args, &body)
1201
+ initial_signal_consumption
1202
+ @Thread = Thread.new(@Terminated, self) do |terminated, _actor| # sync point
1203
+ Thread.abort_on_exception = true
1204
+
1205
+ final_reason = begin
1206
+ reason, value = catch(TERMINATE) do
1207
+ [:normal, @Environment.instance_exec(*args, &body)]
1208
+ end
1209
+ send_exit_messages reason
1210
+ terminated.resolve(reason == :normal, value, reason)
1211
+ reason
1212
+ rescue => e
1213
+ send_exit_messages e
1214
+ terminated.reject e
1215
+ e
1216
+ end
1217
+
1218
+ after_termination final_reason
1219
+ @Thread = nil
1220
+ end
1221
+ end
1222
+
1223
+ def receive(*rules, timeout: nil, timeout_value: nil, &given_block)
1224
+ clean_reply
1225
+
1226
+ err = canonical_rules rules, timeout, timeout_value, given_block
1227
+ raise err if err
1228
+
1229
+ rules_matcher = Or[*rules.map(&:first)]
1230
+ matcher = -> m { m.is_a?(Ask) ? rules_matcher === m.message : rules_matcher === m }
1231
+ while true
1232
+ message = @Mailbox.pop_matching(matcher, timeout, TIMEOUT)
1233
+ log Logger::DEBUG, pid, got: message
1234
+ unless (message = consume_signal(message)) == NOTHING
1235
+ rules.each do |rule, job|
1236
+ return eval_task(message, job) if rule === message
1237
+ end
1238
+ end
1239
+ end
1240
+ end
1241
+
1242
+ private
1243
+
1244
+ def terminate_self(reason, value)
1245
+ throw TERMINATE, [reason, value]
1246
+ end
1247
+ end
1248
+
1249
+ private_constant :OnThread
1250
+
1251
+ class AbstractSignal < Synchronization::Object
1252
+ safe_initialization!
1253
+ end
1254
+
1255
+ private_constant :AbstractSignal
1256
+
1257
+ class Ask < AbstractSignal
1258
+ attr_reader :message, :probe
1259
+
1260
+ def initialize(message, probe)
1261
+ super()
1262
+ @message = message
1263
+ @probe = probe
1264
+ raise ArgumentError, 'probe is not Resolvable' unless probe.is_a? Promises::Resolvable
1265
+ end
1266
+ end
1267
+
1268
+ private_constant :Ask
1269
+
1270
+ module HasFrom
1271
+
1272
+ # @return [Pid]
1273
+ attr_reader :from
1274
+
1275
+ # @!visibility private
1276
+ def initialize(from)
1277
+ # noinspection RubySuperCallWithoutSuperclassInspection
1278
+ super()
1279
+ @from = from
1280
+ end
1281
+
1282
+ # @return [true, false]
1283
+ def ==(o)
1284
+ o.class == self.class && o.from == @from
1285
+ end
1286
+
1287
+ alias_method :eql?, :==
1288
+
1289
+ # @return [Integer]
1290
+ def hash
1291
+ @from.hash
1292
+ end
1293
+ end
1294
+
1295
+ private_constant :HasFrom
1296
+
1297
+ module HasReason
1298
+ include HasFrom
1299
+
1300
+ # @return [Object]
1301
+ attr_reader :reason
1302
+
1303
+ # @!visibility private
1304
+ def initialize(from, reason)
1305
+ # noinspection RubySuperCallWithoutSuperclassInspection
1306
+ super from
1307
+ @reason = reason
1308
+ end
1309
+
1310
+ # @return [::Array(Pid, Object)]
1311
+ def to_ary
1312
+ [@from, @reason]
1313
+ end
1314
+
1315
+ # @return [true, false]
1316
+ def ==(o)
1317
+ # noinspection RubySuperCallWithoutSuperclassInspection
1318
+ super(o) && o.reason == self.reason
1319
+ end
1320
+
1321
+ # @return [Integer]
1322
+ def hash
1323
+ [@from, @reason].hash
1324
+ end
1325
+ end
1326
+
1327
+ private_constant :HasReason
1328
+
1329
+ module HasReference
1330
+ include HasFrom
1331
+
1332
+ # @return [Reference]
1333
+ attr_reader :reference
1334
+
1335
+ # @!visibility private
1336
+ def initialize(from, reference)
1337
+ # noinspection RubySuperCallWithoutSuperclassInspection
1338
+ super from
1339
+ @reference = reference
1340
+ end
1341
+
1342
+ # @return [::Array(Pid, Reference)]
1343
+ def to_ary
1344
+ [@from, @reference]
1345
+ end
1346
+
1347
+ # @return [true, false]
1348
+ def ==(o)
1349
+ # noinspection RubySuperCallWithoutSuperclassInspection
1350
+ super(o) && o.reference == self.reference
1351
+ end
1352
+
1353
+ # @return [Integer]
1354
+ def hash
1355
+ [@from, @reference].hash
1356
+ end
1357
+ end
1358
+
1359
+ private_constant :HasReference
1360
+
1361
+ class Terminate < AbstractSignal
1362
+ include HasReason
1363
+
1364
+ attr_reader :link_terminated
1365
+
1366
+ def initialize(from, reason, link_terminated = true)
1367
+ super from, reason
1368
+ @link_terminated = link_terminated
1369
+ end
1370
+ end
1371
+
1372
+ private_constant :Terminate
1373
+
1374
+ class Kill < AbstractSignal
1375
+ include HasFrom
1376
+ end
1377
+
1378
+ private_constant :Kill
1379
+
1380
+ class Link < AbstractSignal
1381
+ include HasFrom
1382
+ end
1383
+
1384
+ private_constant :Link
1385
+
1386
+ class UnLink < AbstractSignal
1387
+ include HasFrom
1388
+ end
1389
+
1390
+ private_constant :UnLink
1391
+
1392
+ class Monitor < AbstractSignal
1393
+ include HasReference
1394
+ end
1395
+
1396
+ private_constant :Monitor
1397
+
1398
+ class DeMonitor < AbstractSignal
1399
+ include HasReference
1400
+ end
1401
+
1402
+ private_constant :DeMonitor
1403
+
1404
+ # A message send when actor terminates.
1405
+ class Terminated
1406
+ # @return [Pid]
1407
+ attr_reader :from
1408
+ # @return [Object]
1409
+ attr_reader :reason
1410
+
1411
+ # @!visibility private
1412
+ def initialize(from, reason)
1413
+ # noinspection RubySuperCallWithoutSuperclassInspection
1414
+ @from = from
1415
+ @reason = reason
1416
+ end
1417
+
1418
+ # @return [::Array(Pid, Object)]
1419
+ def to_ary
1420
+ [@from, @reason]
1421
+ end
1422
+
1423
+ # @return [true, false]
1424
+ def ==(o)
1425
+ o.class == self.class && o.from == @from && o.reason == self.reason
1426
+ end
1427
+
1428
+ alias_method :eql?, :==
1429
+
1430
+ # @return [Integer]
1431
+ def hash
1432
+ [@from, @reason].hash
1433
+ end
1434
+ end
1435
+
1436
+ class DownSignal < AbstractSignal
1437
+ include HasReference
1438
+
1439
+ # @return [Object]
1440
+ attr_reader :info
1441
+
1442
+ # @!visibility private
1443
+ def initialize(from, reference, info)
1444
+ super from, reference
1445
+ @info = info
1446
+ end
1447
+
1448
+ # @return [::Array(Pis, Reference, Object)]
1449
+ def to_ary
1450
+ [@from, @reference, @info]
1451
+ end
1452
+
1453
+ # @return [true, false]
1454
+ def ==(o)
1455
+ super(o) && o.info == self.info
1456
+ end
1457
+
1458
+ # @return [Integer]
1459
+ def hash
1460
+ to_ary.hash
1461
+ end
1462
+ end
1463
+
1464
+ private_constant :DownSignal
1465
+
1466
+ # A message send by a monitored actor when terminated.
1467
+ class Down
1468
+ # @return [Pid]
1469
+ attr_reader :from
1470
+ # @return [Reference]
1471
+ attr_reader :reference
1472
+ # @return [Object]
1473
+ attr_reader :info
1474
+
1475
+ # @!visibility private
1476
+ def initialize(from, reference, info)
1477
+ @from = from
1478
+ @reference = reference
1479
+ @info = info
1480
+ end
1481
+
1482
+ # @return [::Array(Pis, Reference, Object)]
1483
+ def to_ary
1484
+ [@from, @reference, @info]
1485
+ end
1486
+
1487
+ # @return [true, false]
1488
+ def ==(o)
1489
+ o.class == self.class && o.from == @from && o.reference == @reference && o.info == @info
1490
+ end
1491
+
1492
+ alias_method :eql?, :==
1493
+
1494
+ # @return [Integer]
1495
+ def hash
1496
+ to_ary.hash
1497
+ end
1498
+ end
1499
+
1500
+ # Abstract error class for ErlangActor errors.
1501
+ class Error < Concurrent::Error
1502
+ end
1503
+
1504
+ # An error used when actor tries to link or monitor terminated actor.
1505
+ class NoActor < Error
1506
+ # @return [Pid]
1507
+ attr_reader :pid
1508
+
1509
+ # @param [Pid] pid
1510
+ # @return [self]
1511
+ def initialize(pid = nil)
1512
+ super(pid.to_s)
1513
+ @pid = pid
1514
+ end
1515
+
1516
+ # @return [true, false]
1517
+ def ==(o)
1518
+ o.class == self.class && o.pid == self.pid
1519
+ end
1520
+
1521
+ alias_method :eql?, :==
1522
+
1523
+ # @return [Integer]
1524
+ def hash
1525
+ pid.hash
1526
+ end
1527
+ end
1528
+
1529
+ # An error used when actor is asked but no reply was given or
1530
+ # when the actor terminates before it gives a reply.
1531
+ class NoReply < Error
1532
+ end
1533
+
1534
+ # @!visibility private
1535
+ def self.create(type, channel, environment, name, executor)
1536
+ actor = KLASS_MAP.fetch(type).new(channel, environment, name, executor)
1537
+ ensure
1538
+ log Logger::DEBUG, actor.pid, created: caller[1] if actor
1539
+ end
1540
+
1541
+ KLASS_MAP = {
1542
+ on_thread: OnThread,
1543
+ on_pool: OnPool,
1544
+ OnThread => OnThread,
1545
+ OnPool => OnPool,
1546
+ }
1547
+ private_constant :KLASS_MAP
1548
+ end
1549
+ end