concurrent-ruby-edge 0.3.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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