Skip to content

Commit f473249

Browse files
committed
Split files and add documentation
1 parent c20520b commit f473249

File tree

11 files changed

+535
-369
lines changed

11 files changed

+535
-369
lines changed

lib/concurrent/actress.rb

Lines changed: 31 additions & 316 deletions
Original file line numberDiff line numberDiff line change
@@ -1,330 +1,40 @@
11
require '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

Comments
 (0)