1
1
require 'logger'
2
2
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'
42
6
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
48
8
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
51
20
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'
57
28
29
+ # @return [Reference, nil] current executing actor if any
58
30
def self . current
59
31
Thread . current [ :__current_actress__ ]
60
32
end
61
33
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
326
35
class Root
327
- include ActorContext
36
+ include Context
37
+ # to allow spawning of new actors, spawn needs to be called inside the parent Actor
328
38
def on_message ( message )
329
39
case message . first
330
40
when :spawn
@@ -335,8 +45,13 @@ def on_message(message)
335
45
end
336
46
end
337
47
48
+ # A root actor, a default parent of all actors spawned outside an actor
338
49
ROOT = Core . new ( nil , '/' , Root ) . reference
339
50
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
340
55
def self . spawn ( actress_class , name , *args , &block )
341
56
if Actress . current
342
57
Core . new ( Actress . current , name , actress_class , *args , &block ) . reference
0 commit comments