Add ErlangActor implementation
This commit is contained in:
parent
85763d2c4c
commit
a5a3024405
|
@ -0,0 +1,145 @@
|
|||
## Examples
|
||||
|
||||
The simplest example is to use the actor as an asynchronous execution.
|
||||
Although, `Promises.future { 1 + 1 }` is better suited for that purpose.
|
||||
|
||||
```ruby
|
||||
actor = Concurrent::ErlangActor.spawn(:on_thread, name: 'addition') { 1 + 1 }
|
||||
actor.terminated.value!
|
||||
```
|
||||
|
||||
Let's send some messages and maintain some internal state
|
||||
which is what actors are good for.
|
||||
|
||||
```ruby
|
||||
actor = Concurrent::ErlangActor.spawn(:on_thread, name: 'sum') do
|
||||
sum = 0 # internal state
|
||||
# receive and sum the messages until the actor gets :done
|
||||
while true
|
||||
message = receive
|
||||
break if message == :done
|
||||
# if the message is asked and not only told,
|
||||
# reply with a current sum
|
||||
reply sum += message
|
||||
end
|
||||
sum
|
||||
end
|
||||
```
|
||||
|
||||
The actor can be either told a message asynchronously,
|
||||
or asked. The ask method will block until actor replies.
|
||||
|
||||
```ruby
|
||||
# tell returns immediately returning the actor
|
||||
actor.tell(1).tell(1)
|
||||
# blocks, waiting for the answer
|
||||
actor.ask 10
|
||||
# stop the actor
|
||||
actor.tell :done
|
||||
actor.terminated.value!
|
||||
```
|
||||
|
||||
### Receiving
|
||||
|
||||
Simplest message receive.
|
||||
|
||||
```ruby
|
||||
actor = Concurrent::ErlangActor.spawn(:on_thread) { receive }
|
||||
actor.tell :m
|
||||
actor.terminated.value!
|
||||
```
|
||||
|
||||
which also works for actor on pool,
|
||||
because if no block is given it will use a default block `{ |v| v }`
|
||||
|
||||
```ruby
|
||||
actor = Concurrent::ErlangActor.spawn(:on_pool) { receive { |v| v } }
|
||||
# can simply be following
|
||||
actor = Concurrent::ErlangActor.spawn(:on_pool) { receive }
|
||||
actor.tell :m
|
||||
actor.terminated.value!
|
||||
```
|
||||
|
||||
TBA
|
||||
|
||||
### Actor types
|
||||
|
||||
There are two types of actors.
|
||||
The type is specified when calling spawn as a first argument,
|
||||
`Concurrent::ErlangActor.spawn(:on_thread, ...` or
|
||||
`Concurrent::ErlangActor.spawn(:on_pool, ...`.
|
||||
|
||||
The main difference is in how receive method returns.
|
||||
|
||||
- `:on_thread` it blocks the thread until message is available,
|
||||
then it returns or calls the provided block first.
|
||||
|
||||
- However, `:on_pool` it has to free up the thread on the receive
|
||||
call back to the pool. Therefore the call to receive ends the
|
||||
execution of current scope. The receive has to be given block
|
||||
or blocks that act as a continuations and are called
|
||||
when there is message available.
|
||||
|
||||
Let's have a look at how the bodies of actors differ between the types:
|
||||
|
||||
```ruby
|
||||
ping = Concurrent::ErlangActor.spawn(:on_thread) { reply receive }
|
||||
ping.ask 42
|
||||
```
|
||||
|
||||
It first calls receive, which blocks the thread of the actor.
|
||||
When it returns the received message is passed an an argument to reply,
|
||||
which replies the same value back to the ask method.
|
||||
Then the actor terminates normally, because there is nothing else to do.
|
||||
|
||||
However when running on pool a block with code which should be evaluated
|
||||
after the message is received has to be provided.
|
||||
|
||||
```ruby
|
||||
ping = Concurrent::ErlangActor.spawn(:on_pool) { receive { |m| reply m } }
|
||||
ping.ask 42
|
||||
```
|
||||
|
||||
It starts by calling receive which will remember the given block for later
|
||||
execution when a message is available and stops executing the current scope.
|
||||
Later when a message becomes available the previously provided block is given
|
||||
the message and called. The result of the block is the final value of the
|
||||
normally terminated actor.
|
||||
|
||||
The direct blocking style of `:on_thread` is simpler to write and more straight
|
||||
forward however it has limitations. Each `:on_thread` actor creates a Thread
|
||||
taking time and resources.
|
||||
There is also a limited number of threads the Ruby process can create
|
||||
so you may hit the limit and fail to create more threads and therefore actors.
|
||||
|
||||
Since the `:on_pool` actor runs on a poll of threads, its creations
|
||||
is faster and cheaper and it does not create new threads.
|
||||
Therefore there is no limit (only RAM) on how many actors can be created.
|
||||
|
||||
To simplify, if you need only few actors `:on_thread` is fine.
|
||||
However if you will be creating hundreds of actors or
|
||||
they will be short-lived `:on_pool` should be used.
|
||||
|
||||
### Erlang behaviour
|
||||
|
||||
The actor matches Erlang processes in behaviour.
|
||||
Therefore it supports the usual Erlang actor linking, monitoring, exit behaviour, etc.
|
||||
|
||||
```ruby
|
||||
actor = Concurrent::ErlangActor.spawn(:on_thread) do
|
||||
spawn(link: true) do # equivalent of spawn_link in Erlang
|
||||
terminate :err # equivalent of exit in Erlang
|
||||
end
|
||||
trap # equivalent of process_flag(trap_exit, true)
|
||||
receive
|
||||
end
|
||||
actor.terminated.value!
|
||||
```
|
||||
|
||||
### TODO
|
||||
|
||||
* receives
|
||||
* More erlang behaviour examples
|
||||
* Back pressure with bounded mailbox
|
||||
* _op methods
|
||||
* types of actors
|
|
@ -0,0 +1,7 @@
|
|||
require 'concurrent-edge'
|
||||
|
||||
def do_stuff(*args)
|
||||
sleep 0.01
|
||||
:stuff
|
||||
end
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
## Examples
|
||||
|
||||
The simplest example is to use the actor as an asynchronous execution.
|
||||
Although, `Promises.future { 1 + 1 }` is better suited for that purpose.
|
||||
|
||||
```ruby
|
||||
actor = Concurrent::ErlangActor.spawn(:on_thread, name: 'addition') { 1 + 1 }
|
||||
# => #<Concurrent::ErlangActor::Pid:0x000002 addition>
|
||||
actor.terminated.value! # => 2
|
||||
```
|
||||
|
||||
Let's send some messages and maintain some internal state
|
||||
which is what actors are good for.
|
||||
|
||||
```ruby
|
||||
actor = Concurrent::ErlangActor.spawn(:on_thread, name: 'sum') do
|
||||
sum = 0 # internal state
|
||||
# receive and sum the messages until the actor gets :done
|
||||
while true
|
||||
message = receive
|
||||
break if message == :done
|
||||
# if the message is asked and not only told,
|
||||
# reply with a current sum
|
||||
reply sum += message
|
||||
end
|
||||
sum
|
||||
end
|
||||
# => #<Concurrent::ErlangActor::Pid:0x000003 sum>
|
||||
```
|
||||
|
||||
The actor can be either told a message asynchronously,
|
||||
or asked. The ask method will block until actor replies.
|
||||
|
||||
```ruby
|
||||
# tell returns immediately returning the actor
|
||||
actor.tell(1).tell(1)
|
||||
# => #<Concurrent::ErlangActor::Pid:0x000003 sum>
|
||||
# blocks, waiting for the answer
|
||||
actor.ask 10 # => 12
|
||||
# stop the actor
|
||||
actor.tell :done
|
||||
# => #<Concurrent::ErlangActor::Pid:0x000003 sum>
|
||||
actor.terminated.value! # => 12
|
||||
```
|
||||
|
||||
### Actor types
|
||||
|
||||
There are two types of actors.
|
||||
The type is specified when calling spawn as a first argument,
|
||||
`Concurrent::ErlangActor.spawn(:on_thread, ...` or
|
||||
`Concurrent::ErlangActor.spawn(:on_pool, ...`.
|
||||
|
||||
The main difference is in how receive method returns.
|
||||
|
||||
- `:on_thread` it blocks the thread until message is available,
|
||||
then it returns or calls the provided block first.
|
||||
|
||||
- However, `:on_pool` it has to free up the thread on the receive
|
||||
call back to the pool. Therefore the call to receive ends the
|
||||
execution of current scope. The receive has to be given block
|
||||
or blocks that act as a continuations and are called
|
||||
when there is message available.
|
||||
|
||||
Let's have a look at how the bodies of actors differ between the types:
|
||||
|
||||
```ruby
|
||||
ping = Concurrent::ErlangActor.spawn(:on_thread) { reply receive }
|
||||
# => #<Concurrent::ErlangActor::Pid:0x000004>
|
||||
ping.ask 42 # => 42
|
||||
```
|
||||
|
||||
It first calls receive, which blocks the thread of the actor.
|
||||
When it returns the received message is passed an an argument to reply,
|
||||
which replies the same value back to the ask method.
|
||||
Then the actor terminates normally, because there is nothing else to do.
|
||||
|
||||
However when running on pool a block with code which should be evaluated
|
||||
after the message is received has to be provided.
|
||||
|
||||
```ruby
|
||||
ping = Concurrent::ErlangActor.spawn(:on_pool) { receive { |m| reply m } }
|
||||
# => #<Concurrent::ErlangActor::Pid:0x000005>
|
||||
ping.ask 42 # => 42
|
||||
```
|
||||
|
||||
It starts by calling receive which will remember the given block for later
|
||||
execution when a message is available and stops executing the current scope.
|
||||
Later when a message becomes available the previously provided block is given
|
||||
the message and called. The result of the block is the final value of the
|
||||
normally terminated actor.
|
||||
|
||||
The direct blocking style of `:on_thread` is simpler to write and more straight
|
||||
forward however it has limitations. Each `:on_thread` actor creates a Thread
|
||||
taking time and resources.
|
||||
There is also a limited number of threads the Ruby process can create
|
||||
so you may hit the limit and fail to create more threads and therefore actors.
|
||||
|
||||
Since the `:on_pool` actor runs on a poll of threads, its creations
|
||||
is faster and cheaper and it does not create new threads.
|
||||
Therefore there is no limit (only RAM) on how many actors can be created.
|
||||
|
||||
To simplify, if you need only few actors `:on_thread` is fine.
|
||||
However if you will be creating hundreds of actors or
|
||||
they will be short-lived `:on_pool` should be used.
|
||||
|
||||
### Erlang behaviour
|
||||
|
||||
The actor matches Erlang processes in behaviour.
|
||||
Therefore it supports the usual Erlang actor linking, monitoring, exit behaviour, etc.
|
||||
|
||||
```ruby
|
||||
actor = Concurrent::ErlangActor.spawn(:on_thread) do
|
||||
spawn(link: true) do # equivalent of spawn_link in Erlang
|
||||
terminate :err # equivalent of exit in Erlang
|
||||
end
|
||||
trap # equivalent of process_flag(trap_exit, true)
|
||||
receive
|
||||
end
|
||||
# => #<Concurrent::ErlangActor::Pid:0x000006>
|
||||
actor.terminated.value!
|
||||
# => #<Concurrent::ErlangActor::Exit:0x000007
|
||||
# @from=#<Concurrent::ErlangActor::Pid:0x000008>,
|
||||
# @link_terminated=true,
|
||||
# @reason=:err>
|
||||
```
|
||||
|
||||
### TODO
|
||||
|
||||
* More erlang behaviour examples
|
||||
* Back pressure with bounded mailbox
|
||||
* _op methods
|
||||
* types of actors
|
|
@ -13,3 +13,4 @@ require 'concurrent/edge/throttle'
|
|||
require 'concurrent/edge/channel'
|
||||
|
||||
require 'concurrent/edge/processing_actor'
|
||||
require 'concurrent/edge/erlang_actor'
|
||||
|
|
|
@ -55,6 +55,9 @@ module Concurrent
|
|||
message message, future
|
||||
end
|
||||
|
||||
# @!visibility privated
|
||||
alias_method :ask_op, :ask
|
||||
|
||||
# Sends the message synchronously and blocks until the message
|
||||
# is processed. Raises on error.
|
||||
#
|
||||
|
|
|
@ -49,6 +49,10 @@ module Concurrent
|
|||
def any.===(other)
|
||||
true
|
||||
end
|
||||
|
||||
def any.to_s
|
||||
'ANY'
|
||||
end
|
||||
end
|
||||
|
||||
# Create channel.
|
||||
|
@ -164,7 +168,7 @@ module Concurrent
|
|||
#
|
||||
# @!macro channel.warn.blocks
|
||||
# @!macro channel.param.timeout
|
||||
# @!macro promises.param.timeout_value
|
||||
# @param [Object] timeout_value a value returned by the method when it times out
|
||||
# @return [Object, nil] message or nil when timed out
|
||||
def pop(timeout = nil, timeout_value = nil)
|
||||
pop_matching ANY, timeout, timeout_value
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -15,9 +15,9 @@ module Concurrent
|
|||
# values[-5, 5] # => [49996, 49997, 49998, 49999, 50000]
|
||||
# @!macro warn.edge
|
||||
class ProcessingActor < Synchronization::Object
|
||||
# TODO (pitr-ch 18-Dec-2016): (un)linking, bidirectional, sends special message, multiple link calls has no effect,
|
||||
# TODO (pitr-ch 21-Dec-2016): Make terminated a cancellation token?
|
||||
# link_spawn atomic, Can it be fixed by sending exit when linked dead actor?
|
||||
|
||||
# TODO (pitr-ch 29-Jan-2019): simplify as much as possible, maybe even do not delegate to mailbox, no ask linking etc
|
||||
# TODO (pitr-ch 03-Feb-2019): remove completely
|
||||
|
||||
safe_initialization!
|
||||
|
||||
|
@ -60,12 +60,8 @@ module Concurrent
|
|||
# @yieldparam [Object] *args
|
||||
# @yieldreturn [Promises::Future(Object)] a future representing next step of execution
|
||||
# @return [ProcessingActor]
|
||||
# @example
|
||||
# # TODO (pitr-ch 19-Jan-2017): actor with limited mailbox
|
||||
def self.act_listening(channel, *args, &process)
|
||||
actor, _, terminated = ProcessingActor.new channel
|
||||
Promises.future(actor, *args, &process).run.tangle(terminated)
|
||||
actor
|
||||
ProcessingActor.new channel, *args, &process
|
||||
end
|
||||
|
||||
# # Receives a message when available, used in the actor's process.
|
||||
|
@ -162,19 +158,21 @@ module Concurrent
|
|||
end
|
||||
|
||||
# @return [String] string representation.
|
||||
def inspect
|
||||
format '%s termination:%s>', super[0..-2], termination.state
|
||||
def to_s
|
||||
format '%s termination: %s>', super[0..-2], termination.state
|
||||
end
|
||||
|
||||
alias_method :inspect, :to_s
|
||||
|
||||
def to_ary
|
||||
[self, @Mailbox, @Terminated]
|
||||
[@Mailbox, @Terminated]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def initialize(channel = Promises::Channel.new)
|
||||
def initialize(channel, *args, &process)
|
||||
@Mailbox = channel
|
||||
@Terminated = Promises.resolvable_future
|
||||
@Terminated = Promises.future(self, *args, &process).run
|
||||
super()
|
||||
end
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ module Concurrent
|
|||
# Asks the actor with its value.
|
||||
# @return [Future] new future with the response form the actor
|
||||
def then_ask(actor)
|
||||
self.then(actor) { |v, a| a.ask(v) }.flat
|
||||
self.then(actor) { |v, a| a.ask_op(v) }.flat
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,976 @@
|
|||
RSpec.describe 'Concurrent' do
|
||||
describe 'ErlangActor', edge: true do
|
||||
|
||||
shared_examples 'erlang actor' do
|
||||
# TODO (pitr-ch 06-Feb-2019): include constants instead
|
||||
ANY ||= Concurrent::ErlangActor::ANY
|
||||
TIMEOUT ||= Concurrent::ErlangActor::TIMEOUT
|
||||
identity = -> v { v }
|
||||
|
||||
specify "run to termination" do
|
||||
expect(Concurrent::ErlangActor.spawn(type) do
|
||||
:v
|
||||
end.terminated.value!).to eq :v
|
||||
end
|
||||
|
||||
specify '#receive' do
|
||||
id = -> v { v }
|
||||
succ = -> v { v.succ }
|
||||
|
||||
[[[:v], -> { receive }, :v],
|
||||
[[:v], -> { receive on(ANY, &id) }, :v],
|
||||
[[:v, 1], -> { receive Numeric }, 1],
|
||||
[[:v, 1], -> { receive(Numeric, &succ) }, 2],
|
||||
|
||||
[[:v], -> { receive Numeric, timeout: 0 }, nil],
|
||||
[[:v], -> { receive(Numeric, timeout: 0, &succ) }, nil],
|
||||
[[:v], -> { receive Numeric, timeout: 0, timeout_value: :timeout }, :timeout],
|
||||
[[:v], -> { receive(Numeric, timeout: 0, timeout_value: :timeout, &succ) }, :timeout],
|
||||
|
||||
[[:v, 1], -> { receive Numeric, timeout: 1 }, 1],
|
||||
[[:v, 1], -> { receive(Numeric, timeout: 1, &succ) }, 2],
|
||||
[[:v, 1], -> { receive Numeric, timeout: 1, timeout_value: :timeout }, 1],
|
||||
[[:v, 1], -> { receive(Numeric, timeout: 1, timeout_value: :timeout, &succ) }, 2],
|
||||
|
||||
[[:v], -> { receive on(Numeric, &id), on(TIMEOUT, nil), timeout: 0 }, nil],
|
||||
[[:v], -> { receive on(Numeric, &succ), on(TIMEOUT, nil), timeout: 0 }, nil],
|
||||
[[:v], -> { receive on(Numeric, &id), on(TIMEOUT, :timeout), timeout: 0 }, :timeout],
|
||||
[[:v], -> { receive on(Numeric, &succ), on(TIMEOUT, :timeout), timeout: 0 }, :timeout],
|
||||
|
||||
[[:v, 1], -> { receive on(Numeric, &id), on(TIMEOUT, nil), timeout: 1 }, 1],
|
||||
[[:v, 1], -> { receive on(Numeric, &succ), on(TIMEOUT, nil), timeout: 1 }, 2],
|
||||
[[:v, 1], -> { receive on(Numeric, &id), on(TIMEOUT, :timeout), timeout: 1 }, 1],
|
||||
[[:v, 1], -> { receive on(Numeric, &succ), on(TIMEOUT, :timeout), timeout: 1 }, 2],
|
||||
].each_with_index do |(messages, body, result), i|
|
||||
a = Concurrent::ErlangActor.spawn(type, &body)
|
||||
messages.each { |m| a.tell m }
|
||||
expect(a.terminated.value!).to eq(result), "body: #{body}"
|
||||
end
|
||||
end
|
||||
|
||||
specify 'pid has name' do
|
||||
actor = Concurrent::ErlangActor.spawn(type, name: 'test') {}
|
||||
expect(actor.to_s).to match(/test/)
|
||||
expect(actor.inspect).to match(/test/)
|
||||
end
|
||||
|
||||
specify "receives message" do
|
||||
actor = Concurrent::ErlangActor.spawn(type,
|
||||
&{ on_thread: -> { receive },
|
||||
on_pool: -> { receive on(ANY, &identity) } }.fetch(type))
|
||||
actor.tell :v
|
||||
expect(actor.terminated.value!).to eq :v
|
||||
end
|
||||
|
||||
specify "receives message with matchers" do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
[receive(on(Symbol, &identity)),
|
||||
receive(on(Numeric, &:succ)),
|
||||
receive(on(Numeric, :got_it), timeout: 0, timeout_value: :nothing)]
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
@arr = []
|
||||
receive(on(Symbol) do |v1|
|
||||
@arr.push v1
|
||||
receive(on(Numeric) do |v2|
|
||||
@arr << v2.succ
|
||||
receive(on(Numeric, :got_it), on(TIMEOUT) { @arr << :nothing; @arr }, timeout: 0)
|
||||
end)
|
||||
end)
|
||||
end }
|
||||
actor = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
actor.tell 'junk'
|
||||
actor.tell 1
|
||||
actor.tell :v
|
||||
expect(actor.terminated.value!).to eq [:v, 2, :nothing]
|
||||
end
|
||||
|
||||
describe "monitoring" do
|
||||
specify "(de)monitor" do
|
||||
body_receive = { on_thread:
|
||||
-> { receive },
|
||||
on_pool:
|
||||
-> { receive { |v| v } } }
|
||||
|
||||
body = { on_thread:
|
||||
-> do
|
||||
actor = receive
|
||||
reference = monitor actor
|
||||
monitored = monitoring? reference
|
||||
demonitor reference
|
||||
result = [monitored, monitoring?(reference)]
|
||||
actor.tell :finish
|
||||
result
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
receive do |actor|
|
||||
reference = monitor actor
|
||||
monitored = monitoring? reference
|
||||
demonitor reference
|
||||
result = [monitored, monitoring?(reference)]
|
||||
actor.tell :finish
|
||||
result
|
||||
end
|
||||
end }
|
||||
a1 = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
a2 = Concurrent::ErlangActor.spawn(type, &body_receive.fetch(type))
|
||||
a1.tell a2
|
||||
expect(a1.terminated.value!).to eq [true, false]
|
||||
expect(a2.terminated.value!).to eq :finish
|
||||
end
|
||||
|
||||
specify "demonitor" do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
actor = receive
|
||||
reference = monitor actor
|
||||
monitored = monitoring? reference
|
||||
actor.tell :done
|
||||
actor.terminated.wait
|
||||
demonitor = demonitor reference, :flush, :info
|
||||
[monitored, monitoring?(reference), demonitor, receive(timeout: 0)]
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
receive do |actor|
|
||||
reference = monitor actor
|
||||
monitored = monitoring? reference
|
||||
actor.tell :done
|
||||
actor.terminated.wait
|
||||
demonitor = demonitor reference, :flush, :info
|
||||
results = [monitored, monitoring?(reference), demonitor]
|
||||
receive(on(ANY) { |v| [*results, v] },
|
||||
on(TIMEOUT) { [*results, nil] },
|
||||
timeout: 0)
|
||||
end
|
||||
end }
|
||||
|
||||
a1 = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
body = { on_thread: -> { receive },
|
||||
on_pool: -> { receive(&identity) } }
|
||||
a2 = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
a1.tell a2
|
||||
|
||||
a1.terminated.wait
|
||||
expect(a1.terminated.value!).to eq [true, false, false, nil]
|
||||
expect(a2.terminated.value!).to eq :done
|
||||
end
|
||||
|
||||
specify "demonitor should leave the down message in the inbox if it's already there" do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
actor = receive
|
||||
reference = monitor actor
|
||||
monitored = monitoring? reference
|
||||
actor.tell :done
|
||||
actor.terminated.wait
|
||||
demonitor = demonitor reference, :info
|
||||
[reference, monitored, monitoring?(reference), demonitor, receive(timeout: 0)]
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
receive do |actor|
|
||||
reference = monitor actor
|
||||
monitored = monitoring? reference
|
||||
actor.tell :done
|
||||
actor.terminated.wait
|
||||
demonitor = demonitor reference, :info
|
||||
results = [reference, monitored, monitoring?(reference), demonitor]
|
||||
receive(on(ANY) { |v| [*results, v] },
|
||||
on(TIMEOUT) { [*results, nil] },
|
||||
timeout: 0)
|
||||
end
|
||||
end }
|
||||
|
||||
a1 = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
body = { on_thread: -> { receive },
|
||||
on_pool: -> { receive(&identity) } }
|
||||
a2 = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
a1.tell a2
|
||||
|
||||
reference, monitored, monitoring, demonitor, message = a1.terminated.value!
|
||||
expect(monitored).to eq true
|
||||
expect(monitoring).to eq false
|
||||
expect(demonitor).to eq false
|
||||
expect(message).to eq Concurrent::ErlangActor::Down.new(a2, reference, :normal)
|
||||
expect(a2.terminated.value!).to eq :done
|
||||
end
|
||||
|
||||
specify "notifications 1" do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
b = spawn { [:done, receive] }
|
||||
ref = monitor b
|
||||
b.tell 42
|
||||
[b, ref, receive]
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
b = spawn { receive on(ANY) { |v| [:done, v] } }
|
||||
ref = monitor b
|
||||
b.tell 42
|
||||
receive on(ANY) { |v| [b, ref, v] }
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
b, ref, down = a.terminated.value!
|
||||
expect(down).to eq Concurrent::ErlangActor::Down.new(b, ref, :normal)
|
||||
expect(b.terminated.value!).to eq [:done, 42]
|
||||
end
|
||||
|
||||
specify "notifications 2" do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
b = spawn { :done }
|
||||
b.terminated.wait
|
||||
ref = monitor b
|
||||
[b, ref, receive(timeout: 0.01, timeout_value: :timeout)]
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
b = spawn { :done }
|
||||
b.terminated.wait
|
||||
ref = monitor b
|
||||
receive(timeout: 0.01) { |v| [b, ref, v] }
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
b, ref, down = a.terminated.value!
|
||||
expect(down).to eq Concurrent::ErlangActor::Down.new(b, ref, Concurrent::ErlangActor::NoActor.new(b))
|
||||
expect(b.terminated.value!).to eq :done
|
||||
end
|
||||
|
||||
# FIXME (pitr-ch 20-Jan-2019): test concurrent exit and monitor(), same for link
|
||||
end
|
||||
|
||||
describe 'linking' do
|
||||
body_receive_test_linked = { on_thread:
|
||||
-> { linked?(receive) },
|
||||
on_pool:
|
||||
-> { receive { |a| linked? a } } }
|
||||
|
||||
specify 'links' do
|
||||
body1 = { on_thread:
|
||||
-> do
|
||||
actor = receive
|
||||
link actor
|
||||
linked = linked? actor
|
||||
actor.tell pid
|
||||
linked
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
receive do |actor|
|
||||
link actor
|
||||
linked = linked? actor
|
||||
actor.tell pid
|
||||
linked
|
||||
end
|
||||
end }
|
||||
|
||||
a1 = Concurrent::ErlangActor.spawn(type, &body1.fetch(type))
|
||||
a2 = Concurrent::ErlangActor.spawn(type, &body_receive_test_linked.fetch(type))
|
||||
|
||||
a1.tell a2
|
||||
expect(a1.terminated.value!).to be_truthy
|
||||
expect(a2.terminated.value!).to be_truthy
|
||||
end
|
||||
|
||||
specify 'unlinks' do
|
||||
body1 = { on_thread:
|
||||
-> do
|
||||
actor = receive
|
||||
link actor
|
||||
unlink actor
|
||||
linked = linked? actor
|
||||
actor.tell pid
|
||||
linked
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
receive do |actor|
|
||||
link actor
|
||||
unlink actor
|
||||
linked = linked? actor
|
||||
actor.tell pid
|
||||
linked
|
||||
end
|
||||
end }
|
||||
|
||||
a1 = Concurrent::ErlangActor.spawn(type, &body1.fetch(type))
|
||||
a2 = Concurrent::ErlangActor.spawn(type, &body_receive_test_linked.fetch(type))
|
||||
a1.tell a2
|
||||
expect(a1.terminated.value!).to be_falsey
|
||||
expect(a2.terminated.value!).to be_falsey
|
||||
end
|
||||
|
||||
specify 'link dead' do
|
||||
a = Concurrent::ErlangActor.spawn(type) do
|
||||
b = spawn { :done }
|
||||
b.terminated.wait
|
||||
link b
|
||||
end
|
||||
expect { a.terminated.value! }.to raise_error Concurrent::ErlangActor::NoActor
|
||||
end
|
||||
|
||||
specify 'link dead when trapping' do
|
||||
body1 = { on_thread:
|
||||
-> do
|
||||
b = spawn { :done }
|
||||
b.terminated.wait
|
||||
sleep 0.01
|
||||
trap
|
||||
link b
|
||||
[b, receive]
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
b = spawn { :done }
|
||||
b.terminated.wait
|
||||
sleep 0.01
|
||||
trap
|
||||
link b
|
||||
receive { |v| [b, v] }
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body1.fetch(type))
|
||||
|
||||
b, captured = a.terminated.value!
|
||||
expect(captured).to eq Concurrent::ErlangActor::Exit.new(b, Concurrent::ErlangActor::NoActor.new(b))
|
||||
end
|
||||
|
||||
|
||||
describe 'exit/1 when linked' do
|
||||
# https://learnyousomeerlang.com/errors-and-processes#links
|
||||
specify 1 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
b = spawn(link: true) { :ok }
|
||||
[receive(timeout: 0.01), b]
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
b = spawn(link: true) { :ok }
|
||||
receive(on(ANY) { |v| [v, b] },
|
||||
on(TIMEOUT) { |v| [nil, b] },
|
||||
timeout: 0.01)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
message, b = a.terminated.value!
|
||||
expect(message).to eq nil
|
||||
expect(b.terminated.value!).to eq :ok
|
||||
end
|
||||
|
||||
specify 2 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
b = spawn(link: true) { :ok }
|
||||
trap
|
||||
[receive, b]
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
b = spawn(link: true) { :ok }
|
||||
trap
|
||||
receive(on(ANY) { |v| [v, b] },
|
||||
on(TIMEOUT) { |v| [nil, b] },
|
||||
timeout: 0.01)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
message, b = a.terminated.value!
|
||||
expect(message).to eq Concurrent::ErlangActor::Exit.new(b, :normal)
|
||||
expect(b.terminated.value!).to eq :ok
|
||||
end
|
||||
|
||||
specify 3 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
spawn(link: true) { terminate :boom }
|
||||
receive(timeout: 0.01)
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
spawn(link: true) { terminate :boom }
|
||||
receive(on(ANY) { |v| [v, b] },
|
||||
on(TIMEOUT) { |v| [nil, b] },
|
||||
timeout: 0.01)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
expect(a.terminated.reason).to eq :boom
|
||||
end
|
||||
|
||||
specify 4 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
b = spawn(link: true) { terminate :boom }
|
||||
trap
|
||||
[receive(timeout: 0.01), b]
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
b = spawn(link: true) { terminate :boom }
|
||||
trap
|
||||
receive(on(ANY) { |v| [v, b] },
|
||||
on(TIMEOUT) { |v| [nil, b] },
|
||||
timeout: 0.01)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
trapped_exit, b = a.terminated.value!
|
||||
expect(trapped_exit).to eq Concurrent::ErlangActor::Exit.new(b, :boom)
|
||||
expect(b.terminated.reason).to eq :boom
|
||||
end
|
||||
|
||||
specify 5 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
b = spawn(link: true) { terminate :normal, value: :ok }
|
||||
[receive(timeout: 0.01), b]
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
b = spawn(link: true) { terminate :normal, value: :ok }
|
||||
receive(on(ANY) { |v| [v, b] },
|
||||
on(TIMEOUT) { |v| [nil, b] },
|
||||
timeout: 0.01)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
message, b = a.terminated.value!
|
||||
expect(message).to eq nil
|
||||
expect(b.terminated.value!).to eq :ok
|
||||
end
|
||||
|
||||
specify 6 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
b = spawn(link: true) { terminate :normal, value: :ok }
|
||||
trap
|
||||
[receive, b]
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
b = spawn(link: true) { terminate :normal, value: :ok }
|
||||
trap
|
||||
receive(on(ANY) { |v| [v, b] },
|
||||
on(TIMEOUT) { |v| [nil, b] },
|
||||
timeout: 0.01)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
message, b = a.terminated.value!
|
||||
expect(message).to eq Concurrent::ErlangActor::Exit.new(b, :normal)
|
||||
expect(b.terminated.value!).to eq :ok
|
||||
end
|
||||
|
||||
specify 7 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
spawn(link: true) { raise 'err' }
|
||||
receive(timeout: 0.01)
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
spawn(link: true) { raise 'err' }
|
||||
receive(on(ANY) { |v| [v, b] },
|
||||
on(TIMEOUT) { |v| [nil, b] },
|
||||
timeout: 0.01)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
expect { a.terminated.value! }.to raise_error(RuntimeError, 'err')
|
||||
end
|
||||
|
||||
specify 8 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
b = spawn(link: true) { raise 'err' }
|
||||
trap
|
||||
[receive(timeout: 0.01), b]
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
b = spawn(link: true) { raise 'err' }
|
||||
trap
|
||||
receive(on(ANY) { |v| [v, b] },
|
||||
on(TIMEOUT) { |v| [nil, b] },
|
||||
timeout: 0.01)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
trapped_exit, b = a.terminated.value!
|
||||
expect(trapped_exit).to be_a Concurrent::ErlangActor::Exit
|
||||
expect(trapped_exit.from).to eq b
|
||||
expect(trapped_exit.reason).to eq b.terminated.reason
|
||||
expect(trapped_exit.reason).to be_a RuntimeError
|
||||
expect(trapped_exit.reason.message).to eq 'err'
|
||||
end
|
||||
|
||||
specify 9 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
b = spawn(link: true) { throw :uncaught }
|
||||
trap
|
||||
[receive(timeout: 0.01), b]
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
b = spawn(link: true) { throw :uncaught }
|
||||
trap
|
||||
receive(on(ANY) { |v| [v, b] },
|
||||
on(TIMEOUT) { |v| [nil, b] },
|
||||
timeout: 0.01)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
trapped_exit, b = a.terminated.value!
|
||||
expect(trapped_exit).to be_a Concurrent::ErlangActor::Exit
|
||||
expect(trapped_exit.from).to eq b
|
||||
expect(trapped_exit.reason).to eq b.terminated.reason
|
||||
expect(trapped_exit.reason).to be_a UncaughtThrowError
|
||||
expect(trapped_exit.reason.message).to eq 'uncaught throw :uncaught'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'exit/2 when linked' do
|
||||
# https://learnyousomeerlang.com/errors-and-processes#links
|
||||
specify 1 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
terminate pid, :normal # sends the signal to mailbox
|
||||
# TODO (pitr-ch 17-Jan-2019): does erlang require receive to process signals?
|
||||
receive(timeout: 0.01)
|
||||
:continued
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
terminate pid, :normal # sends the signal to mailbox
|
||||
receive(on(ANY, :continued),
|
||||
on(TIMEOUT, :timeout),
|
||||
timeout: 1)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
expect(a.terminated.value!).to eq nil
|
||||
end
|
||||
|
||||
specify 2 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
terminate pid, :normal
|
||||
trap
|
||||
receive(timeout: 0.01)
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
terminate pid, :normal
|
||||
trap
|
||||
receive(on(ANY, &identity), on(TIMEOUT, nil), timeout: 0.01)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
captured_exit = a.terminated.value!
|
||||
expect(captured_exit).to eq Concurrent::ErlangActor::Exit.new(a, :normal)
|
||||
end
|
||||
|
||||
specify 3 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
b = spawn(link: true) { receive timeout: 0.01, timeout_value: :timeout }
|
||||
terminate b, :normal
|
||||
b
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
b = spawn(link: true) do
|
||||
receive(on(ANY, :not_happening),
|
||||
on(TIMEOUT, :timeout),
|
||||
timeout: 0.01)
|
||||
end
|
||||
|
||||
terminate b, :normal
|
||||
b
|
||||
end }
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
b = a.terminated.value!
|
||||
expect(b.terminated.value!).to eq :timeout
|
||||
end
|
||||
|
||||
specify 4 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
b = spawn(link: true) { trap; receive timeout: 0.01, timeout_value: :timeout }
|
||||
terminate b, :normal
|
||||
b
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
b = spawn(link: true) do
|
||||
trap
|
||||
receive(on(ANY, &identity),
|
||||
on(TIMEOUT, :timeout),
|
||||
timeout: 0.01)
|
||||
end
|
||||
|
||||
terminate b, :normal
|
||||
b
|
||||
end }
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
b = a.terminated.value!
|
||||
expect(b.terminated.value!).to eq Concurrent::ErlangActor::Exit.new(a, :normal)
|
||||
end
|
||||
|
||||
specify 5 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
b = spawn(link: true) { receive timeout: 0.01; terminate :continued }
|
||||
terminate b, :normal
|
||||
trap
|
||||
[b, receive(timeout: 0.02)]
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
b = spawn(link: true) do
|
||||
receive(on(ANY, :not_happening),
|
||||
on(TIMEOUT) { terminate :continued },
|
||||
timeout: 0.01)
|
||||
end
|
||||
|
||||
terminate b, :normal
|
||||
trap
|
||||
receive(on(ANY) { |v| [b, v] }, on(TIMEOUT, :timeout), timeout: 1)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
b, captured = a.terminated.value!
|
||||
expect(b.terminated.reason).to eq :continued
|
||||
# normal is never send from b to a back
|
||||
expect(captured).to eq Concurrent::ErlangActor::Exit.new(b, :continued)
|
||||
end
|
||||
|
||||
specify 6 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
b = spawn(link: true) { receive timeout: 1; :done }
|
||||
terminate b, :remote_err
|
||||
receive timeout: 1
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
b = spawn(link: true) { receive(on(ANY, :done), on(TIMEOUT, :timeout), timeout: 1) }
|
||||
terminate b, :remote_err
|
||||
receive(on(ANY) { |v| [b, v] },
|
||||
on(TIMEOUT, :timeout),
|
||||
timeout: 1)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
a.terminated.wait
|
||||
expect(a.terminated.reason).to eq :remote_err
|
||||
end
|
||||
|
||||
specify 7 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
b = spawn(link: true) { receive timeout: 1; :done }
|
||||
terminate b, :remote_err
|
||||
trap
|
||||
[b, receive(timeout: 1)]
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
b = spawn(link: true) { receive(on(ANY, :done), on(TIMEOUT, :timeout), timeout: 1) }
|
||||
terminate b, :remote_err
|
||||
trap
|
||||
receive(on(ANY) { |v| [b, v] },
|
||||
on(TIMEOUT, :timeout),
|
||||
timeout: 1)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
b, captured = a.terminated.value!
|
||||
expect(b.terminated.reason).to eq :remote_err
|
||||
expect(captured.reason).to eq :remote_err
|
||||
end
|
||||
|
||||
specify 8 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
b = spawn(link: true) { receive timeout: 1; :done }
|
||||
terminate b, :kill
|
||||
receive timeout: 1
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
b = spawn(link: true) { receive(on(ANY, :done), on(TIMEOUT, :done), timeout: 1) }
|
||||
terminate b, :kill
|
||||
receive(on(ANY, &identity), on(TIMEOUT, :timeout), timeout: 1)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
expect(a.terminated.reason).to eq :killed
|
||||
end
|
||||
|
||||
specify 9 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
b = spawn(link: true) { receive timeout: 0.01; :done }
|
||||
terminate b, :kill
|
||||
trap
|
||||
[b, receive(timeout: 0.01)]
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
b = spawn(link: true) { receive(on(ANY, :done), on(TIMEOUT, :done), timeout: 1) }
|
||||
terminate b, :kill
|
||||
trap
|
||||
receive(on(ANY) { |v| [b, v] }, on(TIMEOUT, :timeout), timeout: 1)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
b, captured = a.terminated.value!
|
||||
expect(b.terminated.reason).to eq :killed
|
||||
expect(captured.reason).to eq :killed
|
||||
end
|
||||
|
||||
specify 10 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
terminate pid, :kill
|
||||
receive timeout: 0.01
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
terminate pid, :kill
|
||||
receive(on(ANY, :continued), on(TIMEOUT, :timeout), timeout: 1)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
expect(a.terminated.reason).to eq :killed
|
||||
end
|
||||
|
||||
specify 11 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
terminate pid, :kill
|
||||
trap
|
||||
receive timeout: 0.01
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
terminate pid, :kill
|
||||
trap
|
||||
receive(on(ANY, &identity), on(TIMEOUT, :timeout), timeout: 1)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
expect(a.terminated.reason).to eq :killed
|
||||
end
|
||||
|
||||
# explained in
|
||||
# http://erlang.org/pipermail/erlang-questions/2009-October/047241.html
|
||||
|
||||
specify 12 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
spawn(link: true) { terminate :kill }
|
||||
receive timeout: 1
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
spawn(link: true) { terminate :kill }
|
||||
receive(on(ANY, :continued),
|
||||
on(TIMEOUT, :timeout),
|
||||
timeout: 1)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
expect(a.terminated.reason).to eq :kill
|
||||
end
|
||||
|
||||
specify 13 do
|
||||
body = { on_thread:
|
||||
-> do
|
||||
b = spawn(link: true) { terminate :kill }
|
||||
trap
|
||||
[b, receive(timeout: 1)]
|
||||
end,
|
||||
on_pool:
|
||||
-> do
|
||||
b = spawn(link: true) { terminate :kill }
|
||||
trap
|
||||
receive(on(ANY) { |v| [b, v] },
|
||||
on(TIMEOUT, :timeout),
|
||||
timeout: 1)
|
||||
end }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
|
||||
b, captured = a.terminated.value!
|
||||
|
||||
expect(b.terminated.reason).to eq :kill
|
||||
expect(captured).to eq Concurrent::ErlangActor::Exit.new(b, :kill)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
specify 'spawn(link: true)' do
|
||||
a = Concurrent::ErlangActor.spawn(type) do
|
||||
b = spawn(link: true) { :v }
|
||||
linked? b
|
||||
end
|
||||
expect(a.terminated.value!).to be_truthy
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type) do
|
||||
b = spawn { :v }
|
||||
linked? b
|
||||
end
|
||||
expect(a.terminated.value!).to be_falsey
|
||||
end
|
||||
|
||||
specify 'termination' do
|
||||
a = Concurrent::ErlangActor.spawn(type) { :v }
|
||||
expect(a.terminated.value!).to eq :v
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type) { raise 'err' }
|
||||
expect { a.terminated.value! }.to raise_error(RuntimeError, 'err')
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type) { terminate :normal, value: :val }
|
||||
expect(a.terminated.value!).to eq :val
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type) { terminate :er }
|
||||
expect(a.terminated.reason).to eq :er
|
||||
end
|
||||
|
||||
describe 'asking' do
|
||||
specify "replies" do
|
||||
body = { on_thread: -> { reply receive },
|
||||
on_pool: -> { receive { |v| reply v } } }
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
expect(a.ask(:v)).to eq :v
|
||||
|
||||
body = { on_thread: -> { v = receive; reply v; reply v; },
|
||||
on_pool: -> { receive { |v| reply v; reply v } } }
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
expect(a.ask(:v)).to eq :v
|
||||
|
||||
expect(a.terminated.reason).to be_a_kind_of Concurrent::MultipleAssignmentError
|
||||
body = { on_thread: -> { v = receive; reply v; reply_resolution true, v, nil, false },
|
||||
on_pool: -> { receive { |v| reply v; reply_resolution true, v, nil, false } } }
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
expect(a.ask(:v)).to eq :v
|
||||
expect(a.terminated.value!).to be_falsey
|
||||
|
||||
body = { on_thread: -> { reply_resolution false, nil, receive },
|
||||
on_pool: -> { receive { |v| reply_resolution false, nil, v } } }
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
expect { a.ask(:err) }.to raise_error StandardError, 'err'
|
||||
|
||||
body = { on_thread: -> { reply_resolution false, nil, receive },
|
||||
on_pool: -> { receive { |v| reply_resolution false, nil, v } } }
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
expect(a.ask_op(:err).reason).to eq :err
|
||||
end
|
||||
|
||||
specify "rejects on no reply" do
|
||||
body = { on_thread: -> { receive; receive },
|
||||
on_pool: -> { receive { receive {} } } }
|
||||
|
||||
a = Concurrent::ErlangActor.spawn(type, &body.fetch(type))
|
||||
expect(a.ask_op(:v).reason).to eq Concurrent::ErlangActor::NoReply
|
||||
expect { raise a.ask_op(:v).wait }.to raise_error Concurrent::ErlangActor::NoReply
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
describe 'on thread' do
|
||||
let(:type) { :on_thread }
|
||||
it_behaves_like 'erlang actor'
|
||||
end
|
||||
|
||||
describe 'event based' do
|
||||
let(:type) { :on_pool }
|
||||
it_behaves_like 'erlang actor'
|
||||
|
||||
specify "receives message repeatedly with keep" do
|
||||
actor = Concurrent::ErlangActor.spawn(:on_pool) do
|
||||
receive on(ANY) { |v| v == :done ? terminate(:normal, value: 42) : reply(v) },
|
||||
keep: true
|
||||
end
|
||||
expect(actor.ask(1)).to eq 1
|
||||
expect(actor.ask(2)).to eq 2
|
||||
actor.tell :done
|
||||
expect(actor.terminated.value!).to eq 42
|
||||
end
|
||||
|
||||
specify "class defined" do
|
||||
definition_module = Module.new do
|
||||
def start
|
||||
@sum = 0
|
||||
receive on(Numeric, &method(:count)),
|
||||
on(:done, &method(:stop)),
|
||||
on(TIMEOUT, &method(:fail)),
|
||||
keep: true,
|
||||
timeout: 0.1
|
||||
end
|
||||
|
||||
def count(message)
|
||||
reply @sum += message
|
||||
end
|
||||
|
||||
def stop(_message)
|
||||
terminate :normal, value: @sum
|
||||
end
|
||||
|
||||
def fail(_message)
|
||||
terminate :timeout
|
||||
end
|
||||
end
|
||||
definition_class = Class.new Concurrent::ErlangActor::Environment do
|
||||
include definition_module
|
||||
end
|
||||
|
||||
actor = Concurrent::ErlangActor.spawn(:on_pool, environment: definition_class) { start }
|
||||
actor.tell 1
|
||||
expect(actor.ask(2)).to eq 3
|
||||
actor.tell :done
|
||||
expect(actor.terminated.value!).to eq 3
|
||||
|
||||
actor = Concurrent::ErlangActor.spawn(:on_pool, environment: definition_module)
|
||||
actor.tell 1
|
||||
expect(actor.ask(2)).to eq 3
|
||||
expect(actor.terminated.reason).to eq :timeout
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -692,6 +692,17 @@ RSpec.describe 'Concurrent::Promises' do
|
|||
value!).to eq 6
|
||||
end
|
||||
|
||||
it 'with erlang actor' do
|
||||
actor = Concurrent::ErlangActor.spawn :on_thread do
|
||||
reply receive * 2
|
||||
end
|
||||
|
||||
expect(future { 2 }.
|
||||
then_ask(actor).
|
||||
then { |v| v + 2 }.
|
||||
value!).to eq 6
|
||||
end
|
||||
|
||||
it 'with channel' do
|
||||
ch1 = Concurrent::Promises::Channel.new
|
||||
ch2 = Concurrent::Promises::Channel.new
|
||||
|
|
|
@ -4,7 +4,8 @@ module YARD
|
|||
|
||||
module Templates::Helpers
|
||||
|
||||
# make sure the signatures are complete not simplified with ...
|
||||
# make sure the signatures are complete not simplified with
|
||||
# '...' and '?' instead of nil
|
||||
module HtmlHelper
|
||||
def signature_types(meth, link = true)
|
||||
meth = convert_method_to_overload(meth)
|
||||
|
|
Loading…
Reference in New Issue