-Modern concurrency tools for Ruby. Inspired by
-Erlang,
-Clojure,
-Scala,
-Haskell,
-F#,
-C#,
-Java,
-and classic concurrency patterns.
-
-
-The design goals of this gem are:
-
-
Be an 'unopinionated' toolbox that provides useful utilities without debating which is better or why
-
Remain free of external gem dependencies
-
Stay true to the spirit of the languages providing inspiration
-
But implement in a way that makes sense for Ruby
-
Keep the semantics as idiomatic Ruby as possible
-
Support features that make sense in Ruby
-
Exclude features that don't make sense in Ruby
-
Be small, lean, and loosely coupled
-
-
-
-
-
-
-
+
+
+
+ Modern concurrency tools for Ruby. Inspired by
+ Erlang,
+ Clojure,
+ Scala,
+ Haskell,
+ F#,
+ C#,
+ Java,
+ and classic concurrency patterns.
+
+
+ The design goals of this gem are:
+
+
Be an 'unopinionated' toolbox that provides useful utilities without debating which is better or why
+
Remain free of external gem dependencies
+
Stay true to the spirit of the languages providing inspiration
+
But implement in a way that makes sense for Ruby
+
Keep the semantics as idiomatic Ruby as possible
+
Support features that make sense in Ruby
+
Exclude features that don't make sense in Ruby
+
Be small, lean, and loosely coupled
+
+
+
+
+
+
+
-### Install
+## Install
```shell
gem install concurrent-ruby
```
+
or add the following line to Gemfile:
```ruby
gem 'concurrent-ruby'
```
+
and run `bundle install` from your shell.
-*NOTE: There is an old gem from 2007 called "concurrent" that does not appear to be under active development. That isn't us. Please do not run* `gem install concurrent`*. It is not the droid you are looking for.*
+_NOTE: There is an old gem from 2007 called "concurrent" that does not appear to be under active development. That isn't us. Please do not run* `gem install concurrent`*. It is not the droid you are looking for._
## Features & Documentation
Please see the [Concurrent Ruby Wiki](https://github.com/ruby-concurrency/concurrent-ruby/wiki)
-or the [API documentation](http://rubydoc.info/github/ruby-concurrency/concurrent-ruby/master/frames)
+or the [API documentation](http://ruby-concurrency.github.io/concurrent-ruby/frames.html))
for more information or join our [mailing list](http://groups.google.com/group/concurrent-ruby).
There are many concurrency abstractions in this library. These abstractions can be broadly categorized
@@ -74,6 +76,7 @@ into several general groups:
* Thread synchronization classes and algorithms including [dataflow](https://github.com/ruby-concurrency/concurrent-ruby/wiki/Dataflow),
timeout, condition, countdown latch, dependency counter, and event
* Java-inspired [thread pools](https://github.com/ruby-concurrency/concurrent-ruby/wiki/Thread%20Pools)
+* New fast light-weighted [Actor model](http://ruby-concurrency.github.io/concurrent-ruby/frames.html#!Concurrent/Actress.html) implementation.
* And many more...
### Semantic Versioning
@@ -91,7 +94,7 @@ It should be fully compatible with any interpreter that is compliant with Ruby 1
Many more code examples can be found in the documentation for each class (linked above).
This one simple example shows some of the power of this gem.
-```ruby
+```ruby
require 'concurrent'
require 'thread' # for Queue
require 'open-uri' # for open(uri)
diff --git a/doc/actress/celluloid_benchmark.rb b/doc/actress/celluloid_benchmark.rb
new file mode 100644
index 000000000..6444c0175
--- /dev/null
+++ b/doc/actress/celluloid_benchmark.rb
@@ -0,0 +1,96 @@
+require 'benchmark'
+require 'concurrent/actress'
+Concurrent::Actress.i_know_it_is_experimental!
+require 'celluloid'
+require 'celluloid/autostart'
+
+logger = Logger.new($stderr)
+logger.level = Logger::INFO
+Concurrent.configuration.logger = lambda do |level, progname, message = nil, &block|
+ logger.add level, message, progname, &block
+end
+
+scale = 1
+ADD_TO = (100 * scale).to_i
+counts_size = (500 * scale).to_i
+adders_size = (500 * scale).to_i
+
+class Counter
+ include Celluloid
+
+ def initialize(adders, i)
+ @adders = adders
+ @i = i
+ end
+
+ def counting(count, ivar)
+ if count < ADD_TO
+ @adders[(@i+1) % @adders.size].counting count+1, ivar
+ else
+ ivar.set count
+ end
+ end
+end
+
+threads = []
+
+Benchmark.bmbm(10) do |b|
+ [2, adders_size, adders_size*2, adders_size*3].each do |adders_size|
+
+ b.report(format('%5d %4d %s', ADD_TO*counts_size, adders_size, 'actress')) do
+ counts = Array.new(counts_size) { [0, Concurrent::IVar.new] }
+ adders = Array.new(adders_size) do |i|
+ Concurrent::Actress::AdHoc.spawn("adder#{i}") do
+ lambda do |(count, ivar)|
+ if count.nil?
+ terminate!
+ else
+ if count < ADD_TO
+ adders[(i+1) % adders_size].tell [count+1, ivar]
+ else
+ ivar.set count
+ end
+ end
+ end
+ end
+ end
+
+ counts.each_with_index do |count, i|
+ adders[i % adders_size].tell count
+ end
+
+ counts.each do |count, ivar|
+ raise unless ivar.value >= ADD_TO
+ end
+
+ threads << Thread.list.size
+
+ adders.each { |a| a << [nil, nil] }
+ end
+
+ b.report(format('%5d %4d %s', ADD_TO*counts_size, adders_size, 'celluloid')) do
+ counts = []
+ counts_size.times { counts << [0, Concurrent::IVar.new] }
+
+ adders = []
+ adders_size.times do |i|
+ adders << Counter.new(adders, i)
+ end
+
+ counts.each_with_index do |count, i|
+ adders[i % adders_size].counting *count
+ end
+
+ counts.each do |count, ivar|
+ raise unless ivar.value >= ADD_TO
+ end
+
+ threads << Thread.list.size
+
+ adders.each(&:terminate)
+ end
+ end
+end
+
+p threads
+
diff --git a/doc/actress/format.rb b/doc/actress/format.rb
new file mode 100644
index 000000000..8d6a457e1
--- /dev/null
+++ b/doc/actress/format.rb
@@ -0,0 +1,75 @@
+require 'rubygems'
+require 'bundler/setup'
+require 'pry'
+require 'pp'
+
+input_paths = if ARGV.empty?
+ Dir.glob("#{File.dirname(__FILE__)}/*.in.rb")
+ else
+ ARGV
+ end.map { |p| File.expand_path p }
+
+input_paths.each_with_index do |input_path, i|
+
+ pid = fork do
+ require_relative 'init.rb'
+
+ begin
+ output_path = input_path.gsub /\.in\.rb$/, '.out.rb'
+ input = File.readlines(input_path)
+
+ chunks = []
+ line = ''
+
+ while !input.empty?
+ line += input.shift
+ if Pry::Code.complete_expression? line
+ chunks << line
+ line = ''
+ end
+ end
+
+ raise unless line.empty?
+
+ chunks.map! { |chunk| [chunk, [chunk.split($/).size, 1].max] }
+ environment = Module.new.send :binding
+ evaluate = ->(code, line) do
+ eval(code, environment, input_path, line)
+ end
+
+ indent = 50
+
+ line_count = 1
+ output = ''
+ chunks.each do |chunk, lines|
+ result = evaluate.(chunk, line_count)
+ unless chunk.strip.empty? || chunk =~ /\A *#/
+ pre_lines = chunk.lines.to_a
+ last_line = pre_lines.pop
+ output << pre_lines.join
+
+ if last_line =~ /\#$/
+ output << last_line.gsub(/\#$/, '')
+ else
+ if last_line.size < indent && result.inspect.size < indent
+ output << "%-#{indent}s %s" % [last_line.chomp, "# => #{result.inspect}\n"]
+ else
+ output << last_line << " # => #{result.inspect}\n"
+ end
+ end
+ else
+ output << chunk
+ end
+ line_count += lines
+ end
+
+ puts "#{input_path}\n -> #{output_path}"
+ #puts output
+ File.write(output_path, output)
+ rescue => ex
+ puts "#{ex} (#{ex.class})\n#{ex.backtrace * "\n"}"
+ end
+ end
+
+ Process.wait pid
+end
diff --git a/doc/actress/init.rb b/doc/actress/init.rb
new file mode 100644
index 000000000..7ee9f06ba
--- /dev/null
+++ b/doc/actress/init.rb
@@ -0,0 +1,2 @@
+require 'concurrent/actress'
+Concurrent::Actress.i_know_it_is_experimental!
diff --git a/doc/actress/quick.in.rb b/doc/actress/quick.in.rb
new file mode 100644
index 000000000..096d1a10c
--- /dev/null
+++ b/doc/actress/quick.in.rb
@@ -0,0 +1,84 @@
+class Counter
+ # Include context of an actor which gives this class access to reference and other information
+ # about the actor, see CoreDelegations.
+ include Concurrent::Actress::Context
+
+ # use initialize as you wish
+ def initialize(initial_value)
+ @count = initial_value
+ end
+
+ # override on_message to define actor's behaviour
+ def on_message(message)
+ case message
+ when Integer
+ @count += message
+ when :terminate
+ terminate!
+ else
+ raise 'unknown'
+ end
+ end
+end
+
+# Create new actor naming the instance 'first'.
+# Return value is a reference to the actor, the actual actor is never returned.
+counter = Counter.spawn(:first, 5)
+
+# Tell a message and forget returning self.
+counter.tell(1)
+counter << 1
+# (First counter now contains 7.)
+
+# Send a messages asking for a result.
+counter.ask(0).class
+counter.ask(0).value
+
+# Terminate the actor.
+counter.tell(:terminate)
+# Not terminated yet, it takes a while until the message is processed.
+counter.terminated?
+# Waiting for the termination.
+counter.terminated.class
+counter.terminated.wait
+counter.terminated?
+# Any subsequent messages are rejected.
+counter.ask(5).wait.rejected?
+
+# Failure on message processing terminates the actor.
+counter = Counter.spawn(:first, 0)
+counter.ask('boom').wait.rejected?
+counter.terminated?
+
+
+# Lets define an actor creating children actors.
+class Node
+ include Concurrent::Actress::Context
+
+ def on_message(message)
+ case message
+ when :new_child
+ spawn self.class, :child
+ when :how_many_children
+ children.size
+ when :terminate
+ terminate!
+ else
+ raise 'unknown'
+ end
+ end
+end
+
+# Actors are tracking parent-child relationships
+parent = Node.spawn :parent
+child = parent.tell(:new_child).ask!(:new_child)
+child.parent
+parent.ask!(:how_many_children)
+
+# There is a special root actor which is used for all actors spawned outside any actor.
+parent.parent
+
+# Termination of an parent will also terminate all children.
+parent.ask('boom').wait #
+parent.terminated?
+child.terminated?
diff --git a/doc/actress/quick.out.rb b/doc/actress/quick.out.rb
new file mode 100644
index 000000000..9f10dda3b
--- /dev/null
+++ b/doc/actress/quick.out.rb
@@ -0,0 +1,91 @@
+class Counter
+ # Include context of an actor which gives this class access to reference and other information
+ # about the actor, see CoreDelegations.
+ include Concurrent::Actress::Context
+
+ # use initialize as you wish
+ def initialize(initial_value)
+ @count = initial_value
+ end
+
+ # override on_message to define actor's behaviour
+ def on_message(message)
+ case message
+ when Integer
+ @count += message
+ when :terminate
+ terminate!
+ else
+ raise 'unknown'
+ end
+ end
+end # => :on_message
+
+# Create new actor naming the instance 'first'.
+# Return value is a reference to the actor, the actual actor is never returned.
+counter = Counter.spawn(:first, 5)
+ # => #
+
+# Tell a message and forget returning self.
+counter.tell(1)
+ # => #
+counter << 1
+ # => #
+# (First counter now contains 7.)
+
+# Send a messages asking for a result.
+counter.ask(0).class # => Concurrent::IVar
+counter.ask(0).value # => 7
+
+# Terminate the actor.
+counter.tell(:terminate)
+ # => #
+# Not terminated yet, it takes a while until the message is processed.
+counter.terminated? # => false
+# Waiting for the termination.
+counter.terminated.class # => Concurrent::Event
+counter.terminated.wait # => true
+counter.terminated? # => true
+# Any subsequent messages are rejected.
+counter.ask(5).wait.rejected? # => true
+
+# Failure on message processing terminates the actor.
+counter = Counter.spawn(:first, 0)
+ # => #
+counter.ask('boom').wait.rejected? # => true
+counter.terminated? # => true
+
+
+# Lets define an actor creating children actors.
+class Node
+ include Concurrent::Actress::Context
+
+ def on_message(message)
+ case message
+ when :new_child
+ spawn self.class, :child
+ when :how_many_children
+ children.size
+ when :terminate
+ terminate!
+ else
+ raise 'unknown'
+ end
+ end
+end # => :on_message
+
+# Actors are tracking parent-child relationships
+parent = Node.spawn :parent # => #
+child = parent.tell(:new_child).ask!(:new_child)
+ # => #
+child.parent # => #
+parent.ask!(:how_many_children) # => 2
+
+# There is a special root actor which is used for all actors spawned outside any actor.
+parent.parent
+ # => #
+
+# Termination of an parent will also terminate all children.
+parent.ask('boom').wait
+parent.terminated? # => true
+child.terminated? # => true
diff --git a/lib/concurrent/actress.rb b/lib/concurrent/actress.rb
index 2013def07..9880d9ed5 100644
--- a/lib/concurrent/actress.rb
+++ b/lib/concurrent/actress.rb
@@ -1,11 +1,133 @@
require 'concurrent/configuration'
-require 'concurrent/executor/one_by_one'
+require 'concurrent/executor/serialized_execution'
require 'concurrent/ivar'
require 'concurrent/logging'
module Concurrent
- # {include:file:lib/concurrent/actress/doc.md}
+ # # Actor model
+ #
+ # - Light-weighted.
+ # - Inspired by Akka and Erlang.
+ #
+ # Actors are sharing a thread-pool by default which makes them very cheap to create and discard.
+ # Thousands of actors can be created allowing to brake the program to small maintainable pieces
+ # without breaking single responsibility principles.
+ #
+ # ## What is an actor model?
+ #
+ # [Wiki](http://en.wikipedia.org/wiki/Actor_model) says:
+ # The actor model in computer science is a mathematical model of concurrent computation
+ # that treats _actors_ as the universal primitives of concurrent digital computation:
+ # in response to a message that it receives, an actor can make local decisions,
+ # create more actors, send more messages, and determine how to respond to the next
+ # message received.
+ #
+ # ## Why?
+ #
+ # Concurrency is hard this is one of many ways how to simplify the problem.
+ # It is simpler to reason about actors then about locks (and all their possible states).
+ #
+ # ## How to use it
+ #
+ # {include:file:doc/actress/quick.out.rb}
+ #
+ # ## Messaging
+ #
+ # Messages are processed in same order as they are sent by a sender. It may interleaved with
+ # messages form other senders though. There is also a contract in actor model that
+ # messages sent between actors should be immutable. Gems like
+ #
+ # - [Algebrick](https://github.com/pitr-ch/algebrick) - Typed struct on steroids based on
+ # algebraic types and pattern matching
+ # - [Hamster](https://github.com/hamstergem/hamster) - Efficient, Immutable, Thread-Safe
+ # Collection classes for Ruby
+ #
+ # are very useful.
+ #
+ # ## Architecture
+ #
+ # Actors are running on shared thread poll which allows user to create many actors cheaply.
+ # Downside is that these actors cannot be directly used to do IO or other blocking operations.
+ # Blocking operations could starve the `default_task_pool`. However there are two options:
+ #
+ # - Create an regular actor which will schedule blocking operations in `global_operation_pool`
+ # (which is intended for blocking operations) sending results back to self in messages.
+ # - Create an actor using `global_operation_pool` instead of `global_task_pool`, e.g.
+ # `AnIOActor.spawn name: :blocking, executor: Concurrent.configuration.global_operation_pool`.
+ #
+ # Each actor is composed from 3 objects:
+ #
+ # ### {Reference}
+ # {include:Actress::Reference}
+ #
+ # ### {Core}
+ # {include:Actress::Core}
+ #
+ # ### {Context}
+ # {include:Actress::Context}
+ #
+ # ## Speed
+ #
+ # Simple benchmark Actress vs Celluloid, the numbers are looking good
+ # but you know how it is with benchmarks. Source code is in
+ # `examples/actress/celluloid_benchmark.rb`. It sends numbers between x actors
+ # and adding 1 until certain limit is reached.
+ #
+ # Benchmark legend:
+ #
+ # - mes. - number of messages send between the actors
+ # - act. - number of actors exchanging the messages
+ # - impl. - which gem is used
+ #
+ # ### JRUBY
+ #
+ # Rehearsal --------------------------------------------------------
+ # 50000 2 actress 24.110000 0.800000 24.910000 ( 7.728000)
+ # 50000 2 celluloid 28.510000 4.780000 33.290000 ( 14.782000)
+ # 50000 500 actress 13.700000 0.280000 13.980000 ( 4.307000)
+ # 50000 500 celluloid 14.520000 11.740000 26.260000 ( 12.258000)
+ # 50000 1000 actress 10.890000 0.220000 11.110000 ( 3.760000)
+ # 50000 1000 celluloid 15.600000 21.690000 37.290000 ( 18.512000)
+ # 50000 1500 actress 10.580000 0.270000 10.850000 ( 3.646000)
+ # 50000 1500 celluloid 14.490000 29.790000 44.280000 ( 26.043000)
+ # --------------------------------------------- total: 201.970000sec
+ #
+ # mes. act. impl. user system total real
+ # 50000 2 actress 9.820000 0.510000 10.330000 ( 5.735000)
+ # 50000 2 celluloid 10.390000 4.030000 14.420000 ( 7.494000)
+ # 50000 500 actress 9.880000 0.200000 10.080000 ( 3.310000)
+ # 50000 500 celluloid 12.430000 11.310000 23.740000 ( 11.727000)
+ # 50000 1000 actress 10.590000 0.190000 10.780000 ( 4.029000)
+ # 50000 1000 celluloid 14.950000 23.260000 38.210000 ( 20.841000)
+ # 50000 1500 actress 10.710000 0.250000 10.960000 ( 3.892000)
+ # 50000 1500 celluloid 13.280000 30.030000 43.310000 ( 24.620000) (1)
+ #
+ # ### MRI 2.1.0
+ #
+ # Rehearsal --------------------------------------------------------
+ # 50000 2 actress 4.640000 0.080000 4.720000 ( 4.852390)
+ # 50000 2 celluloid 6.110000 2.300000 8.410000 ( 7.898069)
+ # 50000 500 actress 6.260000 2.210000 8.470000 ( 7.400573)
+ # 50000 500 celluloid 10.250000 4.930000 15.180000 ( 14.174329)
+ # 50000 1000 actress 6.300000 1.860000 8.160000 ( 7.303162)
+ # 50000 1000 celluloid 12.300000 7.090000 19.390000 ( 17.962621)
+ # 50000 1500 actress 7.410000 2.610000 10.020000 ( 8.887396)
+ # 50000 1500 celluloid 14.850000 10.690000 25.540000 ( 24.489796)
+ # ---------------------------------------------- total: 99.890000sec
+ #
+ # mes. act. impl. user system total real
+ # 50000 2 actress 4.190000 0.070000 4.260000 ( 4.306386)
+ # 50000 2 celluloid 6.490000 2.210000 8.700000 ( 8.280051)
+ # 50000 500 actress 7.060000 2.520000 9.580000 ( 8.518707)
+ # 50000 500 celluloid 10.550000 4.980000 15.530000 ( 14.699962)
+ # 50000 1000 actress 6.440000 1.870000 8.310000 ( 7.571059)
+ # 50000 1000 celluloid 12.340000 7.510000 19.850000 ( 18.793591)
+ # 50000 1500 actress 6.720000 2.160000 8.880000 ( 7.929630)
+ # 50000 1500 celluloid 14.140000 10.130000 24.270000 ( 22.775288) (1)
+ #
+ # *Note (1):* Celluloid is using thread per actor so this bench is creating about 1500
+ # native threads. Actress is using constant number of threads.
module Actress
require 'concurrent/actress/type_check'
@@ -20,7 +142,7 @@ module Actress
# @return [Reference, nil] current executing actor if any
def self.current
- Thread.current[:__current_actress__]
+ Thread.current[:__current_actor__]
end
# implements ROOT
@@ -39,10 +161,26 @@ def on_message(message)
# A root actor, a default parent of all actors spawned outside an actor
ROOT = Core.new(parent: nil, name: '/', class: Root).reference
+ # Spawns a new actor.
+ #
+ # @example simple
+ # Actress.spawn(AdHoc, :ping1) { -> message { message } }
+ #
+ # @example complex
+ # Actress.spawn name: :ping3,
+ # class: AdHoc,
+ # args: [1]
+ # executor: Concurrent.configuration.global_task_pool do |add|
+ # lambda { |number| number + add }
+ # end
+ #
# @param block for actress_class instantiation
# @param args see {.spawn_optionify}
+ # @return [Reference] never the actual actor
def self.spawn(*args, &block)
- warn '[EXPERIMENTAL] A full release of `Actress`, renamed `Actor`, is expected in the 0.7.0 release.'
+ experimental_acknowledged? or
+ warn '[EXPERIMENTAL] A full release of `Actress`, renamed `Actor`, is expected in the 0.7.0 release.'
+
if Actress.current
Core.new(spawn_optionify(*args).merge(parent: Actress.current), &block).reference
else
@@ -52,13 +190,12 @@ def self.spawn(*args, &block)
# as {.spawn} but it'll raise when Actor not initialized properly
def self.spawn!(*args, &block)
- warn '[EXPERIMENTAL] A full release of `Actress`, renamed `Actor`, is expected in the 0.7.0 release.'
spawn(spawn_optionify(*args).merge(initialized: ivar = IVar.new), &block).tap { ivar.no_error! }
end
# @overload spawn_optionify(actress_class, name, *args)
# @param [Context] actress_class to be spawned
- # @param [String, Symbol] name of the instance, it's used to generate the path of the actor
+ # @param [String, Symbol] name of the instance, it's used to generate the {Core#path} of the actor
# @param args for actress_class instantiation
# @overload spawn_optionify(opts)
# see {Core#initialize} opts
@@ -71,5 +208,14 @@ def self.spawn_optionify(*args)
args: args[2..-1] }
end
end
+
+ # call this to disable experimental warning
+ def self.i_know_it_is_experimental!
+ @experimental_acknowledged = true
+ end
+
+ def self.experimental_acknowledged?
+ !!@experimental_acknowledged
+ end
end
end
diff --git a/lib/concurrent/actress/ad_hoc.rb b/lib/concurrent/actress/ad_hoc.rb
index d4b49a386..f4c237f85 100644
--- a/lib/concurrent/actress/ad_hoc.rb
+++ b/lib/concurrent/actress/ad_hoc.rb
@@ -1,5 +1,11 @@
module Concurrent
module Actress
+ # Allows quick creation of actors with behaviour defined by blocks.
+ # @example ping
+ # AdHoc.spawn :forward, an_actor do |where|
+ # # this block has to return proc defining #on_message behaviour
+ # -> message { where.tell message }
+ # end
class AdHoc
include Context
def initialize(*args, &initializer)
diff --git a/lib/concurrent/actress/context.rb b/lib/concurrent/actress/context.rb
index b5b48ec22..49a86ee3b 100644
--- a/lib/concurrent/actress/context.rb
+++ b/lib/concurrent/actress/context.rb
@@ -1,7 +1,8 @@
module Concurrent
module Actress
- # module used to define actor behaviours
+ # This module is used to define actors. It can be included in any class,
+ # only requirement is to override {Context#on_message} method.
# @example ping
# class Ping
# include Context
@@ -26,10 +27,6 @@ def on_message(message)
raise NotImplementedError
end
- def logger
- core.logger
- end
-
# @api private
def on_envelope(envelope)
@envelope = envelope
@@ -53,9 +50,14 @@ def terminate!
core.terminate!
end
+ # delegates to core.log
+ # @see Logging#log
+ def log(level, progname, message = nil, &block)
+ core.log(level, progname, message, &block)
+ end
+
private
- # @api private
def initialize_core(core)
@core = Type! core, Core
end
@@ -71,12 +73,12 @@ def self.included(base)
end
module ClassMethods
- # behaves as {Actress.spawn} but class_name is omitted
+ # behaves as {Concurrent::Actress.spawn} but class_name is auto-inserted based on receiver
def spawn(name_or_opts, *args, &block)
Actress.spawn spawn_optionify(name_or_opts, *args), &block
end
- # behaves as {Actress.spawn!} but class_name is omitted
+ # behaves as {Concurrent::Actress.spawn!} but class_name is auto-inserted based on receiver
def spawn!(name_or_opts, *args, &block)
Actress.spawn! spawn_optionify(name_or_opts, *args), &block
end
diff --git a/lib/concurrent/actress/core.rb b/lib/concurrent/actress/core.rb
index f51b8855c..95321df14 100644
--- a/lib/concurrent/actress/core.rb
+++ b/lib/concurrent/actress/core.rb
@@ -4,33 +4,50 @@ module Actress
require 'set'
# Core of the actor
- # @api private
+ # @note Whole class should be considered private. An user should use {Context}s and {Reference}s only.
# @note devel: core should not block on anything, e.g. it cannot wait on children to terminate
# that would eat up all threads in task pool and deadlock
class Core
include TypeCheck
include Concurrent::Logging
- attr_reader :reference, :name, :path, :executor, :terminated
+ # @!attribute [r] reference
+ # @return [Reference] reference to this actor which can be safely passed around
+ # @!attribute [r] name
+ # @return [String] the name of this instance, it should be uniq (not enforced right now)
+ # @!attribute [r] path
+ # @return [String] a path of this actor. It is used for easier orientation and logging.
+ # Path is constructed recursively with: `parent.path + self.name` up to a {Actress::ROOT},
+ # e.g. `/an_actor/its_child`.
+ # (It will also probably form a supervision path (failures will be reported up to parents)
+ # in future versions.)
+ # @!attribute [r] executor
+ # @return [Executor] which is used to process messages
+ # @!attribute [r] terminated
+ # @return [Event] event which will become set when actor is terminated.
+ # @!attribute [r] actor_class
+ # @return [Context] a class including {Context} representing Actor's behaviour
+ attr_reader :reference, :name, :path, :executor, :terminated, :actor_class
# @option opts [String] name
# @option opts [Reference, nil] parent of an actor spawning this one
- # @option opts [Context] actress_class a class to be instantiated defining Actor's behaviour
- # @option opts [Array