Skip to content

Commit bd65cd7

Browse files
committed
Add spawn! method which raises when Context initialization fails
opts[:initialized] is also added to be able to inject IVar to be set after Context Initialization
1 parent c643e88 commit bd65cd7

File tree

4 files changed

+37
-10
lines changed

4 files changed

+37
-10
lines changed

lib/concurrent/actress.rb

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77

88
module Concurrent
99

10-
# TODO broader description with examples
11-
#
1210
# @example ping
1311
# class Ping
1412
# include Context
@@ -39,8 +37,7 @@ class Root
3937
include Context
4038
# to allow spawning of new actors, spawn needs to be called inside the parent Actor
4139
def on_message(message)
42-
case message.first
43-
when :spawn
40+
if message.is_a?(Array) && message.first == :spawn
4441
spawn message[1], &message[2]
4542
else
4643
# ignore
@@ -52,6 +49,7 @@ def on_message(message)
5249
ROOT = Core.new(parent: nil, name: '/', class: Root).reference
5350

5451
# @param block for actress_class instantiation
52+
# @param args see {#spawn_optionify}
5553
def self.spawn(*args, &block)
5654
if Actress.current
5755
Core.new(spawn_optionify(*args).merge(parent: Actress.current), &block).reference
@@ -60,6 +58,11 @@ def self.spawn(*args, &block)
6058
end
6159
end
6260

61+
# as {#spawn} but it'll raise when Actor not initialized properly
62+
def self.spawn!(*args, &block)
63+
spawn(spawn_optionify(*args).merge(initialized: ivar = IVar.new), &block).tap { ivar.no_error! }
64+
end
65+
6366
# @overload spawn_optionify(actress_class, name, *args)
6467
# @param [Context] actress_class to be spawned
6568
# @param [String, Symbol] name of the instance, it's used to generate the path of the actor

lib/concurrent/actress/context.rb

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ module Context
2020
# @abstract override to define Actor's behaviour
2121
# @param [Object] message
2222
# @return [Object] a result which will be used to set the IVar supplied to Reference#ask
23+
# @note self should not be returned (or sent to other actors), {#reference} should be used
24+
# instead
2325
def on_message(message)
2426
raise NotImplementedError
2527
end
@@ -71,12 +73,22 @@ def self.included(base)
7173
module ClassMethods
7274
# behaves as {Actress.spawn} but class_name is omitted
7375
def spawn(name_or_opts, *args, &block)
74-
opts = if name_or_opts.is_a? Hash
75-
name_or_opts.merge class: self
76-
else
77-
{ class: self, name: name_or_opts, args: args }
78-
end
79-
Actress.spawn opts, &block
76+
Actress.spawn spawn_optionify(name_or_opts, *args), &block
77+
end
78+
79+
# behaves as {Actress.spawn!} but class_name is omitted
80+
def spawn!(name_or_opts, *args, &block)
81+
Actress.spawn! spawn_optionify(name_or_opts, *args), &block
82+
end
83+
84+
private
85+
86+
def spawn_optionify(name_or_opts, *args)
87+
if name_or_opts.is_a? Hash
88+
name_or_opts.merge class: self
89+
else
90+
{ class: self, name: name_or_opts, args: args }
91+
end
8092
end
8193
end
8294
end

lib/concurrent/actress/core.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ module Actress
55

66
# Core of the actor
77
# @api private
8+
# @note devel: core should not block on anything, e.g. it cannot wait on children to terminate
9+
# that would eat up all threads in task pool and deadlock
810
class Core
911
include TypeCheck
1012
include Concurrent::Logging
@@ -16,6 +18,7 @@ class Core
1618
# @option opts [Context] actress_class a class to be instantiated defining Actor's behaviour
1719
# @option opts [Array<Object>] args arguments for actress_class instantiation
1820
# @option opts [Executor] executor, default is `Concurrent.configuration.global_task_pool`
21+
# @option opts [IVar, nil] initialized, if present it'll be set or failed after {Context} initialization
1922
# @option opts [Proc, nil] logger a proc accepting (level, progname, message = nil, &block) params,
2023
# can be used to hook actor instance to any logging system
2124
# @param [Proc] block for class instantiation
@@ -42,14 +45,17 @@ def initialize(opts = {}, &block)
4245

4346
@actress_class = actress_class = Child! opts.fetch(:class), Context
4447
args = opts.fetch(:args, [])
48+
initialized = Type! opts[:initialized], IVar, NilClass
4549

4650
schedule_execution do
4751
begin
4852
@actress = actress_class.new *args, &block
4953
@actress.send :initialize_core, self
54+
initialized.set true if initialized
5055
rescue => ex
5156
log ERROR, ex
5257
terminate!
58+
initialized.fail ex if initialized
5359
end
5460
end
5561
end

spec/concurrent/actress_spec.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ def assert condition
109109
a.terminated?.should be_true
110110
end
111111

112+
it 'terminates on failed initialization and raises with spawn!' do
113+
expect do
114+
AdHoc.spawn!(name: :fail, logger: Concurrent.configuration.no_logger) { raise 'm' }
115+
end.to raise_error(StandardError, 'm')
116+
end
117+
112118
it 'terminates on failed message processing' do
113119
a = AdHoc.spawn(name: :fail, logger: Concurrent.configuration.no_logger) { -> _ { raise } }
114120
a.ask(nil).wait.rejected?.should be_true

0 commit comments

Comments
 (0)