@@ -4,24 +4,25 @@ The simplest example is to use the actor as an asynchronous execution.
44Although, ` Promises.future { 1 + 1 } ` is better suited for that purpose.
55
66``` ruby
7- actor = Concurrent ::ErlangActor .spawn (:on_thread , name: ' addition' ) { 1 + 1 }
7+ actor = Concurrent ::ErlangActor .spawn (type: :on_thread , name: ' addition' ) { 1 + 1 }
88actor.terminated.value!
99```
1010
1111Let's send some messages and maintain some internal state
1212which is what actors are good for.
1313
1414``` ruby
15- actor = Concurrent ::ErlangActor .spawn (:on_thread , name: ' sum' ) do
15+ actor = Concurrent ::ErlangActor .spawn (type: :on_thread , name: ' sum' ) do
1616 sum = 0 # internal state
1717 # receive and sum the messages until the actor gets :done
1818 while true
1919 message = receive
2020 break if message == :done
2121 # if the message is asked and not only told,
22- # reply with a current sum
22+ # reply with the current sum (has no effect if actor was not asked)
2323 reply sum += message
2424 end
25+ # The final value of the actor
2526 sum
2627end
2728```
@@ -36,15 +37,74 @@ actor.tell(1).tell(1)
3637actor.ask 10
3738# stop the actor
3839actor.tell :done
40+ # The final value of the actor
3941actor.terminated.value!
4042```
4143
42- ### Receiving
44+ ### Actor types
45+
46+ There are two types of actors.
47+ The type is specified when calling spawn as a first argument,
48+ ` Concurrent::ErlangActor.spawn(type: :on_thread, ... ` or
49+ ` Concurrent::ErlangActor.spawn(type: :on_pool, ... ` .
50+
51+ The main difference is in how receive method returns.
52+
53+ - ` :on_thread ` it blocks the thread until message is available,
54+ then it returns or calls the provided block first.
55+
56+ - However, ` :on_pool ` it has to free up the thread on the receive
57+ call back to the pool. Therefore the call to receive ends the
58+ execution of current scope. The receive has to be given block
59+ or blocks that act as a continuations and are called
60+ when there is message available.
61+
62+ Let's have a look at how the bodies of actors differ between the types:
63+
64+ ``` ruby
65+ ping = Concurrent ::ErlangActor .spawn (type: :on_thread ) { reply receive }
66+ ping.ask 42
67+ ```
68+
69+ It first calls receive, which blocks the thread of the actor.
70+ When it returns the received message is passed an an argument to reply,
71+ which replies the same value back to the ask method.
72+ Then the actor terminates normally, because there is nothing else to do.
73+
74+ However when running on pool a block with code which should be evaluated
75+ after the message is received has to be provided.
76+
77+ ``` ruby
78+ ping = Concurrent ::ErlangActor .spawn (type: :on_pool ) { receive { |m | reply m } }
79+ ping.ask 42
80+ ```
81+
82+ It starts by calling receive which will remember the given block for later
83+ execution when a message is available and stops executing the current scope.
84+ Later when a message becomes available the previously provided block is given
85+ the message and called. The result of the block is the final value of the
86+ normally terminated actor.
87+
88+ The direct blocking style of ` :on_thread ` is simpler to write and more straight
89+ forward however it has limitations. Each ` :on_thread ` actor creates a Thread
90+ taking time and resources.
91+ There is also a limited number of threads the Ruby process can create
92+ so you may hit the limit and fail to create more threads and therefore actors.
93+
94+ Since the ` :on_pool ` actor runs on a poll of threads, its creations
95+ is faster and cheaper and it does not create new threads.
96+ Therefore there is no limit (only RAM) on how many actors can be created.
97+
98+ To simplify, if you need only few actors ` :on_thread ` is fine.
99+ However if you will be creating hundreds of actors or
100+ they will be short-lived ` :on_pool ` should be used.
101+
102+ ### Receiving messages
43103
44104Simplest message receive.
45105
46106``` ruby
47- actor = Concurrent ::ErlangActor .spawn (:on_thread ) { receive }
107+ actor = Concurrent ::ErlangActor .spawn (type: :on_thread ) { receive }
48108actor.tell :m
49109actor.terminated.value!
50110```
@@ -53,9 +113,9 @@ which also works for actor on pool,
53113because if no block is given it will use a default block ` { |v| v } `
54114
55115``` ruby
56- actor = Concurrent ::ErlangActor .spawn (:on_pool ) { receive { |v | v } }
116+ actor = Concurrent ::ErlangActor .spawn (type: :on_pool ) { receive { |v | v } }
57117# can simply be following
58- actor = Concurrent ::ErlangActor .spawn (:on_pool ) { receive }
118+ actor = Concurrent ::ErlangActor .spawn (type: :on_pool ) { receive }
59119actor.tell :m
60120actor.terminated.value!
61121```
@@ -64,7 +124,7 @@ The received message type can be limited.
64124
65125``` ruby
66126Concurrent ::ErlangActor .
67- spawn (:on_thread ) { receive(Numeric ).succ }.
127+ spawn (type: :on_thread ) { receive(Numeric ).succ }.
68128 tell(' junk' ). # ignored message
69129 tell(42 ).
70130 terminated.value!
@@ -74,7 +134,7 @@ On pool it requires a block.
74134
75135``` ruby
76136Concurrent ::ErlangActor .
77- spawn (:on_pool ) { receive(Numeric ) { |v | v.succ } }.
137+ spawn (type: :on_pool ) { receive(Numeric ) { |v | v.succ } }.
78138 tell(' junk' ). # ignored message
79139 tell(42 ).
80140 terminated.value!
@@ -85,7 +145,7 @@ as well.
85145
86146``` ruby
87147Concurrent ::ErlangActor .
88- spawn (:on_thread ) { receive(Numeric ) { |v | v.succ } }.
148+ spawn (type: :on_thread ) { receive(Numeric ) { |v | v.succ } }.
89149 tell(' junk' ). # ignored message
90150 tell(42 ).
91151 terminated.value!
@@ -94,7 +154,7 @@ Concurrent::ErlangActor.
94154The ` receive ` method can be also used to dispatch based on the received message.
95155
96156``` ruby
97- actor = Concurrent ::ErlangActor .spawn (:on_thread ) do
157+ actor = Concurrent ::ErlangActor .spawn (type: :on_thread ) do
98158 while true
99159 receive(on(Symbol ) { |s | reply s.to_s },
100160 on(And [Numeric , -> v { v >= 0 }]) { |v | reply v.succ },
@@ -137,7 +197,7 @@ module Behaviour
137197 end
138198end
139199
140- actor = Concurrent ::ErlangActor .spawn (:on_pool , environment: Behaviour ) { body }
200+ actor = Concurrent ::ErlangActor .spawn (type: :on_pool , environment: Behaviour ) { body }
141201actor.ask 1
142202actor.ask 2
143203actor.ask :value
@@ -153,7 +213,7 @@ that will keep the receive rules until another receive is called
153213replacing the kept rules.
154214
155215``` ruby
156- actor = Concurrent ::ErlangActor .spawn (:on_pool ) do
216+ actor = Concurrent ::ErlangActor .spawn (type: :on_pool ) do
157217 receive(on(Symbol ) { |s | reply s.to_s },
158218 on(And [Numeric , -> v { v >= 0 }]) { |v | reply v.succ },
159219 # put last works as else
@@ -173,71 +233,13 @@ actor.ask "junk" rescue $!
173233actor.terminated.result
174234```
175235
176- ### Actor types
177-
178- There are two types of actors.
179- The type is specified when calling spawn as a first argument,
180- ` Concurrent::ErlangActor.spawn(:on_thread, ... ` or
181- ` Concurrent::ErlangActor.spawn(:on_pool, ... ` .
182-
183- The main difference is in how receive method returns.
184-
185- - ` :on_thread ` it blocks the thread until message is available,
186- then it returns or calls the provided block first.
187-
188- - However, ` :on_pool ` it has to free up the thread on the receive
189- call back to the pool. Therefore the call to receive ends the
190- execution of current scope. The receive has to be given block
191- or blocks that act as a continuations and are called
192- when there is message available.
193-
194- Let's have a look at how the bodies of actors differ between the types:
195-
196- ``` ruby
197- ping = Concurrent ::ErlangActor .spawn (:on_thread ) { reply receive }
198- ping.ask 42
199- ```
200-
201- It first calls receive, which blocks the thread of the actor.
202- When it returns the received message is passed an an argument to reply,
203- which replies the same value back to the ask method.
204- Then the actor terminates normally, because there is nothing else to do.
205-
206- However when running on pool a block with code which should be evaluated
207- after the message is received has to be provided.
208-
209- ``` ruby
210- ping = Concurrent ::ErlangActor .spawn (:on_pool ) { receive { |m | reply m } }
211- ping.ask 42
212- ```
213-
214- It starts by calling receive which will remember the given block for later
215- execution when a message is available and stops executing the current scope.
216- Later when a message becomes available the previously provided block is given
217- the message and called. The result of the block is the final value of the
218- normally terminated actor.
219-
220- The direct blocking style of ` :on_thread ` is simpler to write and more straight
221- forward however it has limitations. Each ` :on_thread ` actor creates a Thread
222- taking time and resources.
223- There is also a limited number of threads the Ruby process can create
224- so you may hit the limit and fail to create more threads and therefore actors.
225-
226- Since the ` :on_pool ` actor runs on a poll of threads, its creations
227- is faster and cheaper and it does not create new threads.
228- Therefore there is no limit (only RAM) on how many actors can be created.
229-
230- To simplify, if you need only few actors ` :on_thread ` is fine.
231- However if you will be creating hundreds of actors or
232- they will be short-lived ` :on_pool ` should be used.
233-
234236### Erlang behaviour
235237
236238The actor matches Erlang processes in behaviour.
237239Therefore it supports the usual Erlang actor linking, monitoring, exit behaviour, etc.
238240
239241``` ruby
240- actor = Concurrent ::ErlangActor .spawn (:on_thread ) do
242+ actor = Concurrent ::ErlangActor .spawn (type: :on_thread ) do
241243 spawn (link: true ) do # equivalent of spawn_link in Erlang
242244 terminate :err # equivalent of exit in Erlang
243245 end
@@ -247,13 +249,30 @@ end
247249actor.terminated.value!
248250```
249251
250- ### TODO
251-
252- * receives
253- * More erlang behaviour examples
254- * Back pressure with bounded mailbox
255- * _ op methods
256- * types of actors
257- * always use timeout
258- * drop and log unrecognized messages, or just terminate
259- * Functions module
252+ The methods have same or very similar name to be easily found.
253+ The one exception from the original Erlang naming is exit.
254+ To avoid clashing with ` Kernel#exit ` it's called ` terminate ` .
255+
256+ Until there is more information available here, the chapters listed below from
257+ a book [ lern you some Erlang] ( https://learnyousomeerlang.com )
258+ are excellent source of information.
259+ The Ruby ErlangActor implementation has same behaviour.
260+
261+ - [ Links] ( https://learnyousomeerlang.com/errors-and-processes#links )
262+ - [ It's a trap] ( https://learnyousomeerlang.com/errors-and-processes#its-a-trap )
263+ - [ Monitors] ( https://learnyousomeerlang.com/errors-and-processes#monitors )
264+
265+ If anything behaves differently than in Erlang, please file an issue.
266+
267+ ### Chapters or points to be added
268+
269+ * More erlang behaviour examples.
270+ * The mailbox can be bounded in size,
271+ then the tell and ask will block until there is space available in the mailbox.
272+ Useful for building systems with backpressure.
273+ * ` #tell_op ` and ` ask_op ` method examples, integration with promises.
274+ * Best practice: always use timeout,
275+ and do something if the message does not arrive, don't leave the actor stuck.
276+ * Best practice: drop and log unrecognized messages,
277+ or be even more defensive and terminate.
278+ * Environment definition for actors.
0 commit comments