@@ -4,24 +4,25 @@ The simplest example is to use the actor as an asynchronous execution.
4
4
Although, ` Promises.future { 1 + 1 } ` is better suited for that purpose.
5
5
6
6
``` ruby
7
- actor = Concurrent ::ErlangActor .spawn (:on_thread , name: ' addition' ) { 1 + 1 }
7
+ actor = Concurrent ::ErlangActor .spawn (type: :on_thread , name: ' addition' ) { 1 + 1 }
8
8
actor.terminated.value!
9
9
```
10
10
11
11
Let's send some messages and maintain some internal state
12
12
which is what actors are good for.
13
13
14
14
``` ruby
15
- actor = Concurrent ::ErlangActor .spawn (:on_thread , name: ' sum' ) do
15
+ actor = Concurrent ::ErlangActor .spawn (type: :on_thread , name: ' sum' ) do
16
16
sum = 0 # internal state
17
17
# receive and sum the messages until the actor gets :done
18
18
while true
19
19
message = receive
20
20
break if message == :done
21
21
# 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)
23
23
reply sum += message
24
24
end
25
+ # The final value of the actor
25
26
sum
26
27
end
27
28
```
@@ -36,15 +37,74 @@ actor.tell(1).tell(1)
36
37
actor.ask 10
37
38
# stop the actor
38
39
actor.tell :done
40
+ # The final value of the actor
39
41
actor.terminated.value!
40
42
```
41
43
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
43
103
44
104
Simplest message receive.
45
105
46
106
``` ruby
47
- actor = Concurrent ::ErlangActor .spawn (:on_thread ) { receive }
107
+ actor = Concurrent ::ErlangActor .spawn (type: :on_thread ) { receive }
48
108
actor.tell :m
49
109
actor.terminated.value!
50
110
```
@@ -53,9 +113,9 @@ which also works for actor on pool,
53
113
because if no block is given it will use a default block ` { |v| v } `
54
114
55
115
``` ruby
56
- actor = Concurrent ::ErlangActor .spawn (:on_pool ) { receive { |v | v } }
116
+ actor = Concurrent ::ErlangActor .spawn (type: :on_pool ) { receive { |v | v } }
57
117
# can simply be following
58
- actor = Concurrent ::ErlangActor .spawn (:on_pool ) { receive }
118
+ actor = Concurrent ::ErlangActor .spawn (type: :on_pool ) { receive }
59
119
actor.tell :m
60
120
actor.terminated.value!
61
121
```
@@ -64,7 +124,7 @@ The received message type can be limited.
64
124
65
125
``` ruby
66
126
Concurrent ::ErlangActor .
67
- spawn (:on_thread ) { receive(Numeric ).succ }.
127
+ spawn (type: :on_thread ) { receive(Numeric ).succ }.
68
128
tell(' junk' ). # ignored message
69
129
tell(42 ).
70
130
terminated.value!
@@ -74,7 +134,7 @@ On pool it requires a block.
74
134
75
135
``` ruby
76
136
Concurrent ::ErlangActor .
77
- spawn (:on_pool ) { receive(Numeric ) { |v | v.succ } }.
137
+ spawn (type: :on_pool ) { receive(Numeric ) { |v | v.succ } }.
78
138
tell(' junk' ). # ignored message
79
139
tell(42 ).
80
140
terminated.value!
@@ -85,7 +145,7 @@ as well.
85
145
86
146
``` ruby
87
147
Concurrent ::ErlangActor .
88
- spawn (:on_thread ) { receive(Numeric ) { |v | v.succ } }.
148
+ spawn (type: :on_thread ) { receive(Numeric ) { |v | v.succ } }.
89
149
tell(' junk' ). # ignored message
90
150
tell(42 ).
91
151
terminated.value!
@@ -94,7 +154,7 @@ Concurrent::ErlangActor.
94
154
The ` receive ` method can be also used to dispatch based on the received message.
95
155
96
156
``` ruby
97
- actor = Concurrent ::ErlangActor .spawn (:on_thread ) do
157
+ actor = Concurrent ::ErlangActor .spawn (type: :on_thread ) do
98
158
while true
99
159
receive(on(Symbol ) { |s | reply s.to_s },
100
160
on(And [Numeric , -> v { v >= 0 }]) { |v | reply v.succ },
@@ -137,7 +197,7 @@ module Behaviour
137
197
end
138
198
end
139
199
140
- actor = Concurrent ::ErlangActor .spawn (:on_pool , environment: Behaviour ) { body }
200
+ actor = Concurrent ::ErlangActor .spawn (type: :on_pool , environment: Behaviour ) { body }
141
201
actor.ask 1
142
202
actor.ask 2
143
203
actor.ask :value
@@ -153,7 +213,7 @@ that will keep the receive rules until another receive is called
153
213
replacing the kept rules.
154
214
155
215
``` ruby
156
- actor = Concurrent ::ErlangActor .spawn (:on_pool ) do
216
+ actor = Concurrent ::ErlangActor .spawn (type: :on_pool ) do
157
217
receive(on(Symbol ) { |s | reply s.to_s },
158
218
on(And [Numeric , -> v { v >= 0 }]) { |v | reply v.succ },
159
219
# put last works as else
@@ -173,71 +233,13 @@ actor.ask "junk" rescue $!
173
233
actor.terminated.result
174
234
```
175
235
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
-
234
236
### Erlang behaviour
235
237
236
238
The actor matches Erlang processes in behaviour.
237
239
Therefore it supports the usual Erlang actor linking, monitoring, exit behaviour, etc.
238
240
239
241
``` ruby
240
- actor = Concurrent ::ErlangActor .spawn (:on_thread ) do
242
+ actor = Concurrent ::ErlangActor .spawn (type: :on_thread ) do
241
243
spawn (link: true ) do # equivalent of spawn_link in Erlang
242
244
terminate :err # equivalent of exit in Erlang
243
245
end
@@ -247,13 +249,30 @@ end
247
249
actor.terminated.value!
248
250
```
249
251
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