11require 'logger'
22
3- module Concurrent
4- module Actress
5- Error = Class . new ( StandardError )
6-
7- module TypeCheck
8- # taken from Algebrick
9-
10- def Type? ( value , *types )
11- types . any? { |t | value . is_a? t }
12- end
13-
14- def Type! ( value , *types )
15- Type? ( value , *types ) or
16- TypeCheck . error ( value , 'is not' , types )
17- value
18- end
19-
20- def Match? ( value , *types )
21- types . any? { |t | t === value }
22- end
23-
24- def Match! ( value , *types )
25- Match? ( value , *types ) or
26- TypeCheck . error ( value , 'is not matching' , types )
27- value
28- end
29-
30- def Child? ( value , *types )
31- Type? ( value , Class ) &&
32- types . any? { |t | value <= t }
33- end
34-
35- def Child! ( value , *types )
36- Child? ( value , *types ) or
37- TypeCheck . error ( value , 'is not child' , types )
38- value
39- end
40-
41- private
3+ require 'concurrent/configuration'
4+ require 'concurrent/executor/one_by_one'
5+ require 'concurrent/ivar'
426
43- def self . error ( value , message , types )
44- raise TypeError ,
45- "Value (#{ value . class } ) '#{ value } ' #{ message } any of: #{ types . join ( '; ' ) } ."
46- end
47- end
7+ module Concurrent
488
49- class ActressTerminated < Error
50- include TypeCheck
9+ # TODO broader description with examples
10+ #
11+ # @example ping
12+ # class Ping
13+ # include Context
14+ # def on_message(message)
15+ # message
16+ # end
17+ # end
18+ # Ping.spawn(:ping1).ask(:m).value #=> :m
19+ module Actress
5120
52- def initialize ( reference )
53- Type! reference , Reference
54- super reference . path
55- end
56- end
21+ require 'concurrent/actress/type_check'
22+ require 'concurrent/actress/errors'
23+ require 'concurrent/actress/core_delegations'
24+ require 'concurrent/actress/envelope'
25+ require 'concurrent/actress/reference'
26+ require 'concurrent/actress/core'
27+ require 'concurrent/actress/context'
5728
29+ # @return [Reference, nil] current executing actor if any
5830 def self . current
5931 Thread . current [ :__current_actress__ ]
6032 end
6133
62- module CoreDelegations
63- def path
64- core . path
65- end
66-
67- def parent
68- core . parent
69- end
70-
71- def terminated?
72- core . terminated?
73- end
74-
75- def reference
76- core . reference
77- end
78-
79- alias_method :ref , :reference
80- end
81-
82- class Reference
83- include TypeCheck
84- include CoreDelegations
85-
86- attr_reader :core
87- private :core
88-
89- def initialize ( core )
90- @core = Type! core , Core
91- end
92-
93- def tell ( message )
94- message message , nil
95- end
96-
97- alias_method :<< , :tell
98-
99- def ask ( message , ivar = IVar . new )
100- message message , ivar
101- end
102-
103- # **warning** - can lead to deadlocks
104- def ask! ( message , ivar = IVar . new )
105- ask ( message , ivar ) . value!
106- end
107-
108- def message ( message , ivar = nil )
109- core . on_envelope Envelope . new ( message , ivar , Actress . current )
110- return ivar || self
111- end
112-
113- def to_s
114- "#<#{ self . class } #{ path } >"
115- end
116-
117- alias_method :inspect , :to_s
118-
119- def ==( other )
120- Type? other , self . class and other . send ( :core ) == core
121- end
122- end
123-
124- Envelope = Struct . new :message , :ivar , :sender do
125- include TypeCheck
126-
127- def initialize ( message , ivar , sender )
128- super message ,
129- ( Type! ivar , IVar , NilClass ) ,
130- ( Type! sender , Reference , NilClass )
131- end
132-
133- def sender_path
134- if sender
135- sender . path
136- else
137- 'outside-actress'
138- end
139- end
140-
141- def reject! ( error )
142- ivar . fail error unless ivar . nil?
143- end
144- end
145-
146- class Core
147- include TypeCheck
148-
149- attr_reader :reference , :name , :path , :logger , :parent_core
150- private :parent_core
151-
152- def initialize ( parent , name , actress_class , *args , &block )
153- @mailbox = Array . new
154- @one_by_one = OneByOne . new
155- @executor = Concurrent . configuration . global_task_pool # TODO make configurable
156- @parent_core = ( Type! parent , Reference , NilClass ) && parent . send ( :core )
157- @name = ( Type! name , String , Symbol ) . to_s
158- @children = [ ]
159- @path = @parent_core ? File . join ( @parent_core . path , @name ) : @name
160- @logger = Logger . new ( $stderr) # TODO add proper logging
161- @logger . progname = @path
162- @reference = Reference . new self
163- # noinspection RubyArgCount
164- @terminated = Event . new
165-
166- parent_core . add_child reference if parent_core
167-
168- @actress_class = Child! actress_class , ActorContext
169- schedule_execution do
170- begin
171- @actress = actress_class . new *args , &block
172- @actress . send :initialize_core , self
173- rescue => ex
174- puts "#{ ex } (#{ ex . class } )\n #{ ex . backtrace . join ( "\n " ) } "
175- terminate! # TODO test that this is ok
176- end
177- end
178- end
179-
180- def parent
181- @parent_core . reference
182- end
183-
184- def children
185- guard!
186- @children
187- end
188-
189- def add_child ( child )
190- guard!
191- @children << ( Type! child , Reference )
192- self
193- end
194-
195- def remove_child ( child )
196- schedule_execution do
197- Type! child , Reference
198- @children . delete child
199- end
200- self
201- end
202-
203- def on_envelope ( envelope )
204- schedule_execution { execute_on_envelope envelope }
205- end
206-
207- def terminated?
208- @terminated . set?
209- end
210-
211- def terminate!
212- guard!
213- @terminated . set
214- parent_core . remove_child reference if parent_core
215- @mailbox . each do |envelope |
216- reject_envelope envelope
217- logger . debug "rejected #{ envelope . message } from #{ envelope . sender_path } "
218- end
219- @mailbox . clear
220- # TODO terminate all children
221- end
222-
223- def guard!
224- unless Actress . current == reference
225- raise "can be called only inside actor #{ reference } but was #{ Actress . current } "
226- end
227- end
228-
229- private
230-
231- def process?
232- unless @mailbox . empty? || @receive_envelope_scheduled
233- @receive_envelope_scheduled = true
234- schedule_execution { receive_envelope }
235- end
236- end
237-
238- def receive_envelope
239- envelope = @mailbox . shift
240-
241- logger . debug "received #{ envelope . message } from #{ envelope . sender_path } "
242-
243- result = @actress . on_envelope envelope
244- envelope . ivar . set result unless envelope . ivar . nil?
245- rescue => error
246- logger . error error
247- envelope . ivar . fail error unless envelope . ivar . nil?
248- ensure
249- @receive_envelope_scheduled = false
250- process?
251- end
252-
253- def schedule_execution
254- @one_by_one . post ( @executor ) do
255- begin
256- Thread . current [ :__current_actress__ ] = reference
257- yield
258- rescue => e
259- puts e
260- ensure
261- Thread . current [ :__current_actress__ ] = nil
262- end
263- end
264- end
265-
266- def execute_on_envelope ( envelope )
267- if terminated?
268- reject_envelope envelope
269- else
270- @mailbox . push envelope
271- end
272- process?
273- end
274-
275- def reject_envelope ( envelope )
276- envelope . reject! ActressTerminated . new ( reference )
277- end
278- end
279-
280- module ActorContext
281- include TypeCheck
282- extend TypeCheck
283- include CoreDelegations
284-
285- attr_reader :core
286-
287- def on_message ( message )
288- raise NotImplementedError
289- end
290-
291- def logger
292- core . logger
293- end
294-
295- def on_envelope ( envelope )
296- @envelope = envelope
297- on_message envelope . message
298- ensure
299- @envelope = nil
300- end
301-
302- # TODO add basic supervision
303- def spawn ( actress_class , name , *args , &block )
304- Actress . spawn ( actress_class , name , *args , &block )
305- end
306-
307- def children
308- core . children
309- end
310-
311- def terminate!
312- core . terminate!
313- end
314-
315- private
316-
317- def initialize_core ( core )
318- @core = Type! core , Core
319- end
320-
321- def envelope
322- @envelope or raise 'envelope not set'
323- end
324- end
325-
34+ # implements ROOT
32635 class Root
327- include ActorContext
36+ include Context
37+ # to allow spawning of new actors, spawn needs to be called inside the parent Actor
32838 def on_message ( message )
32939 case message . first
33040 when :spawn
@@ -335,8 +45,13 @@ def on_message(message)
33545 end
33646 end
33747
48+ # A root actor, a default parent of all actors spawned outside an actor
33849 ROOT = Core . new ( nil , '/' , Root ) . reference
33950
51+ # @param [Context] actress_class to be spawned
52+ # @param [String, Symbol] name of the instance, it's used to generate the path of the actor
53+ # @param args for actress_class instantiation
54+ # @param block for actress_class instantiation
34055 def self . spawn ( actress_class , name , *args , &block )
34156 if Actress . current
34257 Core . new ( Actress . current , name , actress_class , *args , &block ) . reference
0 commit comments