Skip to content

Commit b898a47

Browse files
committed
Improving documentation
1 parent 0969f9c commit b898a47

24 files changed

+550
-305
lines changed

.yardopts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
--protected
2-
--no-private
2+
--private
33
--embed-mixins
44
--output-dir ./yardoc
55
--markup markdown

doc/actor/celluloid_benchmark.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
require 'benchmark'
22
require 'concurrent/actor'
3-
Concurrent::Actor.i_know_it_is_experimental!
43

54
require 'celluloid'
65
require 'celluloid/autostart'

doc/actor/define.in.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
require 'concurrent'
2+
3+
Message = Struct.new :action, :value #
4+
5+
class AnActor < Concurrent::Actor::RestartingContext
6+
def initialize(init)
7+
@counter = init
8+
end
9+
10+
# override #on_message to define actor's behaviour on message received
11+
def on_message(message)
12+
case message.action
13+
when :add
14+
@counter = @counter + message.value
15+
when :subtract
16+
@counter = @counter - message.value
17+
when :value
18+
@counter
19+
else
20+
pass
21+
end
22+
end
23+
24+
# set counter to zero when there is an error
25+
def on_event(event)
26+
if event == :reset
27+
@counter = 0 # ignore initial value
28+
end
29+
end
30+
end #
31+
32+
an_actor = AnActor.spawn name: 'an_actor', args: 10, supervise: true #
33+
an_actor << Message.new(:add, 1) << Message.new(:subtract, 2) #
34+
an_actor.ask!(Message.new(:value, nil))
35+
an_actor << :boo << Message.new(:add, 1) #
36+
an_actor.ask!(Message.new(:value, nil))
37+
an_actor << :terminate!
38+

doc/actor/define.out.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
require 'concurrent' # => true
2+
3+
Message = Struct.new :action, :value
4+
5+
class AnActor < Concurrent::Actor::RestartingContext
6+
def initialize(init)
7+
@counter = init
8+
end
9+
10+
# override #on_message to define actor's behaviour on message received
11+
def on_message(message)
12+
case message.action
13+
when :add
14+
@counter = @counter + message.value
15+
when :subtract
16+
@counter = @counter - message.value
17+
when :value
18+
@counter
19+
else
20+
pass
21+
end
22+
end
23+
24+
# set counter to zero when there is an error
25+
def on_event(event)
26+
if event == :reset
27+
@counter = 0 # ignore initial value
28+
end
29+
end
30+
end
31+
32+
an_actor = AnActor.spawn name: 'an_actor', args: 10, supervise: true
33+
an_actor << Message.new(:add, 1) << Message.new(:subtract, 2)
34+
an_actor.ask!(Message.new(:value, nil)) # => 9
35+
an_actor << :boo << Message.new(:add, 1)
36+
an_actor.ask!(Message.new(:value, nil)) # => 1
37+
an_actor << :terminate!
38+
# => #<Concurrent::Actor::Reference /an_actor (AnActor)>
39+

doc/actor/main.md

Lines changed: 119 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,92 @@
11
# Actor model
22

3-
- Light-weighted.
3+
- Light-weighted running on thread-pool.
44
- Inspired by Akka and Erlang.
55
- Modular.
66

7-
Actors are sharing a thread-pool by default which makes them very cheap to create and discard.
8-
Thousands of actors can be created, allowing you to break the program into small maintainable pieces,
9-
without violating the single responsibility principle.
7+
This Actor model implementation makes makes actors very cheap to create and discard.
8+
Thousands of actors can be created, allowing you to break the program into smaller
9+
maintainable pieces, without violating the single responsibility principle.
1010

1111
## What is an actor model?
1212

13-
[Wiki](http://en.wikipedia.org/wiki/Actor_model) says:
14-
The actor model in computer science is a mathematical model of concurrent computation
15-
that treats _actors_ as the universal primitives of concurrent digital computation:
13+
Actor-based concurrency is all the rage in some circles. Originally described in 1973, the actor model is a paradigm
14+
for creating asynchronous, concurrent objects that is becoming increasingly popular. Much has changed since actors
15+
were first written about four decades ago, which has led to a serious fragmentation within the actor community.
16+
There is *no* universally accepted, strict definition of "actor" and actor implementations differ widely between
17+
languages and libraries.
18+
19+
[Wiki](http://en.wikipedia.org/wiki/Actor_model) definition is pretty good:
20+
_The actor model in computer science is a mathematical model of concurrent computation
21+
that treats **actors** as the universal primitives of concurrent digital computation:
1622
in response to a message that it receives, an actor can make local decisions,
1723
create more actors, send more messages, and determine how to respond to the next
18-
message received.
24+
message received._
1925

2026
## Why?
2127

22-
Concurrency is hard this is one of many ways how to simplify the problem.
23-
It is simpler to reason about actors than about locks (and all their possible states).
28+
Concurrency is hard to get right, actors are one of many ways how to simplify the problem.
2429

25-
## How to use it
30+
## Quick example
2631

2732
{include:file:doc/actor/quick.out.rb}
2833

29-
## Messaging
34+
## Spawning actors
35+
36+
- {Concurrent::Actor.spawn} and {Concurrent::Actor.spawn!}
37+
- {Concurrent::Actor::AbstractContext.spawn} and {Concurrent::Actor::AbstractContext.spawn!}
38+
39+
## Sending messages
40+
41+
- {Concurrent::Actor::Reference#tell}
42+
{include:Concurrent::Actor::Reference#tell}
43+
- {Concurrent::Actor::Reference#ask}
44+
{include:Concurrent::Actor::Reference#ask}
45+
- {Concurrent::Actor::Reference#ask!}
46+
{include:Concurrent::Actor::Reference#ask!}
3047

3148
Messages are processed in same order as they are sent by a sender. It may interleaved with
32-
messages form other senders though. There is also a contract in actor model that
33-
messages sent between actors should be immutable. Gems like
49+
messages from other senders though.
50+
51+
### Immutability
52+
53+
Messages sent between actors should be **immutable**. Gems like
3454

3555
- [Algebrick](https://github.com/pitr-ch/algebrick) - Typed struct on steroids based on
3656
algebraic types and pattern matching
3757
- [Hamster](https://github.com/hamstergem/hamster) - Efficient, Immutable, Thread-Safe
3858
Collection classes for Ruby
3959

40-
are very useful.
60+
are very helpful.
4161

42-
### Dead letter routing
62+
{include:file:doc/actor/messaging.out.rb}
4363

44-
see {AbstractContext#dead_letter_routing} description:
64+
## Actor definition
4565

46-
> {include:Actor::AbstractContext#dead_letter_routing}
66+
{include:Concurrent::Actor::AbstractContext}
67+
68+
## Reference
69+
70+
{include:Actor::Reference}
71+
72+
## Garbage collection
73+
74+
Spawned actor cannot be garbage-collected until it's terminated. There is a reference held in the parent actor.
75+
76+
## Parent-child relationship, name, and path
77+
78+
- {Core#name}
79+
{include:Actor::Core#name}
80+
- {Core#path}
81+
{include:Actor::Core#path}
82+
- {Core#parent}
83+
{include:Actor::Core#parent}
4784

48-
## Architecture
85+
## Behaviour
86+
87+
{include:Actor::Behaviour}
88+
89+
## IO cooperation
4990

5091
Actors are running on shared thread poll which allows user to create many actors cheaply.
5192
Downside is that these actors cannot be directly used to do IO or other blocking operations.
@@ -56,19 +97,31 @@ Blocking operations could starve the `default_task_pool`. However there are two
5697
- Create an actor using `global_operation_pool` instead of `global_task_pool`, e.g.
5798
`AnIOActor.spawn name: :blocking, executor: Concurrent.configuration.global_operation_pool`.
5899

59-
Each actor is composed from 4 parts:
100+
## Dead letter routing
60101

61-
### {Reference}
62-
{include:Actor::Reference}
102+
see {AbstractContext#dead_letter_routing} description:
63103

64-
### {Core}
65-
{include:Actor::Core}
104+
> {include:Actor::AbstractContext#dead_letter_routing}
66105
67-
### {AbstractContext}
68-
{include:Actor::AbstractContext}
106+
## FAQ
69107

70-
### {Behaviour}
71-
{include:Actor::Behaviour}
108+
### What happens if I try to supervise using a normal Context?
109+
110+
Alleged supervisor will receive errors from its supervised actors. They'll have to be handled manually.
111+
112+
### How to change supervision strategy?
113+
114+
Use option `behaviour_definition: Behaviour.restarting_behaviour_definition(:resume!)` or
115+
`behaviour_definition: Behaviour.restarting_behaviour_definition(:reset!, :one_for_all)`
116+
117+
### How to change behaviors?
118+
119+
Any existing behavior can be subclassed
120+
121+
### How to implement custom restarting?
122+
123+
By subclassing {Behaviour::Pausing} and overriding {Behaviour::Pausing#restart!}. Implementing
124+
{AbstractContext#on_event} could be also considered.
72125

73126
## Speed
74127

@@ -85,49 +138,49 @@ Benchmark legend:
85138

86139
### JRUBY
87140

88-
Rehearsal --------------------------------------------------------
89-
50000 2 concurrent 24.110000 0.800000 24.910000 ( 7.728000)
90-
50000 2 celluloid 28.510000 4.780000 33.290000 ( 14.782000)
91-
50000 500 concurrent 13.700000 0.280000 13.980000 ( 4.307000)
92-
50000 500 celluloid 14.520000 11.740000 26.260000 ( 12.258000)
93-
50000 1000 concurrent 10.890000 0.220000 11.110000 ( 3.760000)
94-
50000 1000 celluloid 15.600000 21.690000 37.290000 ( 18.512000)
95-
50000 1500 concurrent 10.580000 0.270000 10.850000 ( 3.646000)
96-
50000 1500 celluloid 14.490000 29.790000 44.280000 ( 26.043000)
97-
--------------------------------------------- total: 201.970000sec
141+
Rehearsal ---------------------------------------------------------
142+
50000 2 concurrent 26.140000 0.610000 26.750000 ( 7.761000)
143+
50000 2 celluloid 41.440000 5.270000 46.710000 ( 17.535000)
144+
50000 500 concurrent 11.340000 0.180000 11.520000 ( 3.498000)
145+
50000 500 celluloid 19.310000 10.680000 29.990000 ( 14.619000)
146+
50000 1000 concurrent 10.640000 0.180000 10.820000 ( 3.563000)
147+
50000 1000 celluloid 17.840000 19.850000 37.690000 ( 18.892000)
148+
50000 1500 concurrent 14.120000 0.290000 14.410000 ( 4.618000)
149+
50000 1500 celluloid 19.060000 28.920000 47.980000 ( 25.185000)
150+
---------------------------------------------- total: 225.870000sec
98151

99-
mes. act. impl. user system total real
100-
50000 2 concurrent 9.820000 0.510000 10.330000 ( 5.735000)
101-
50000 2 celluloid 10.390000 4.030000 14.420000 ( 7.494000)
102-
50000 500 concurrent 9.880000 0.200000 10.080000 ( 3.310000)
103-
50000 500 celluloid 12.430000 11.310000 23.740000 ( 11.727000)
104-
50000 1000 concurrent 10.590000 0.190000 10.780000 ( 4.029000)
105-
50000 1000 celluloid 14.950000 23.260000 38.210000 ( 20.841000)
106-
50000 1500 concurrent 10.710000 0.250000 10.960000 ( 3.892000)
107-
50000 1500 celluloid 13.280000 30.030000 43.310000 ( 24.620000) (1)
152+
mes. act. impl. user system total real
153+
50000 2 concurrent 7.320000 0.530000 7.850000 ( 3.637000)
154+
50000 2 celluloid 13.780000 4.730000 18.510000 ( 10.756000)
155+
50000 500 concurrent 9.270000 0.140000 9.410000 ( 3.020000)
156+
50000 500 celluloid 16.540000 10.920000 27.460000 ( 14.308000)
157+
50000 1000 concurrent 9.970000 0.160000 10.130000 ( 3.445000)
158+
50000 1000 celluloid 15.930000 20.840000 36.770000 ( 18.272000)
159+
50000 1500 concurrent 11.580000 0.240000 11.820000 ( 3.723000)
160+
50000 1500 celluloid 19.440000 29.060000 48.500000 ( 25.227000) (1)
108161

109162
### MRI 2.1.0
110163

111-
Rehearsal --------------------------------------------------------
112-
50000 2 concurrent 4.640000 0.080000 4.720000 ( 4.852390)
113-
50000 2 celluloid 6.110000 2.300000 8.410000 ( 7.898069)
114-
50000 500 concurrent 6.260000 2.210000 8.470000 ( 7.400573)
115-
50000 500 celluloid 10.250000 4.930000 15.180000 ( 14.174329)
116-
50000 1000 concurrent 6.300000 1.860000 8.160000 ( 7.303162)
117-
50000 1000 celluloid 12.300000 7.090000 19.390000 ( 17.962621)
118-
50000 1500 concurrent 7.410000 2.610000 10.020000 ( 8.887396)
119-
50000 1500 celluloid 14.850000 10.690000 25.540000 ( 24.489796)
120-
---------------------------------------------- total: 99.890000sec
164+
Rehearsal ---------------------------------------------------------
165+
50000 2 concurrent 4.180000 0.080000 4.260000 ( 4.269435)
166+
50000 2 celluloid 7.740000 3.100000 10.840000 ( 10.043875)
167+
50000 500 concurrent 5.900000 1.310000 7.210000 ( 6.565067)
168+
50000 500 celluloid 12.820000 5.810000 18.630000 ( 17.320765)
169+
50000 1000 concurrent 6.080000 1.640000 7.720000 ( 6.931294)
170+
50000 1000 celluloid 17.130000 8.320000 25.450000 ( 23.786146)
171+
50000 1500 concurrent 6.940000 2.030000 8.970000 ( 7.927330)
172+
50000 1500 celluloid 20.980000 12.040000 33.020000 ( 30.849578)
173+
---------------------------------------------- total: 116.100000sec
121174

122-
mes. act. impl. user system total real
123-
50000 2 concurrent 4.190000 0.070000 4.260000 ( 4.306386)
124-
50000 2 celluloid 6.490000 2.210000 8.700000 ( 8.280051)
125-
50000 500 concurrent 7.060000 2.520000 9.580000 ( 8.518707)
126-
50000 500 celluloid 10.550000 4.980000 15.530000 ( 14.699962)
127-
50000 1000 concurrent 6.440000 1.870000 8.310000 ( 7.571059)
128-
50000 1000 celluloid 12.340000 7.510000 19.850000 ( 18.793591)
129-
50000 1500 concurrent 6.720000 2.160000 8.880000 ( 7.929630)
130-
50000 1500 celluloid 14.140000 10.130000 24.270000 ( 22.775288) (1)
175+
mes. act. impl. user system total real
176+
50000 2 concurrent 3.730000 0.100000 3.830000 ( 3.822688)
177+
50000 2 celluloid 7.900000 2.910000 10.810000 ( 9.924014)
178+
50000 500 concurrent 5.420000 1.230000 6.650000 ( 6.025579)
179+
50000 500 celluloid 12.720000 5.540000 18.260000 ( 16.889517)
180+
50000 1000 concurrent 5.420000 0.910000 6.330000 ( 5.896689)
181+
50000 1000 celluloid 16.090000 8.040000 24.130000 ( 22.347102)
182+
50000 1500 concurrent 5.580000 0.760000 6.340000 ( 6.038535)
183+
50000 1500 celluloid 20.000000 11.680000 31.680000 ( 29.590774) (1)
131184

132185
*Note (1):* Celluloid is using thread per actor so this bench is creating about 1500
133186
native threads. Actor is using constant number of threads.

doc/actor/messaging.in.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
require 'concurrent'
2+
require 'algebrick'
3+
4+
# Actor message protocol definition with Algebrick
5+
Protocol = Algebrick.type do
6+
variants Add = type { fields! a: Numeric, b: Numeric },
7+
Subtract = type { fields! a: Numeric, b: Numeric }
8+
end
9+
10+
class Calculator < Concurrent::Actor::RestartingContext
11+
include Algebrick::Matching
12+
13+
def on_message(message)
14+
# pattern matching on the message with deconstruction
15+
# ~ marks values which are passed to the block
16+
match message,
17+
(on Add.(~any, ~any) do |a, b|
18+
a + b
19+
end),
20+
# or use multi-assignment
21+
(on ~Subtract do |(a, b)|
22+
a - b
23+
end)
24+
end
25+
end #
26+
27+
calculator = Calculator.spawn('calculator')
28+
calculator.ask! Add[1, 2]
29+
calculator.ask! Subtract[1, 0.5]
30+
calculator << :terminate!

doc/actor/messaging.out.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
require 'concurrent' # => true
2+
require 'algebrick' # => true
3+
4+
# Actor message protocol definition with Algebrick
5+
Protocol = Algebrick.type do
6+
variants Add = type { fields! a: Numeric, b: Numeric },
7+
Subtract = type { fields! a: Numeric, b: Numeric }
8+
end # => Protocol(Add | Subtract)
9+
10+
class Calculator < Concurrent::Actor::RestartingContext
11+
include Algebrick::Matching
12+
13+
def on_message(message)
14+
# pattern matching on the message with deconstruction
15+
# ~ marks values which are passed to the block
16+
match message,
17+
(on Add.(~any, ~any) do |a, b|
18+
a + b
19+
end),
20+
# or use multi-assignment
21+
(on ~Subtract do |(a, b)|
22+
a - b
23+
end)
24+
end
25+
end
26+
27+
calculator = Calculator.spawn('calculator')
28+
# => #<Concurrent::Actor::Reference /calculator (Calculator)>
29+
calculator.ask! Add[1, 2] # => 3
30+
calculator.ask! Subtract[1, 0.5] # => 0.5
31+
calculator << :terminate!
32+
# => #<Concurrent::Actor::Reference /calculator (Calculator)>

0 commit comments

Comments
 (0)