Actor
Implementation of the actor computational model for ruby.
Basic Usage
Installation
To install actor
via rubygems.org, you will have to refer to the gem as ntl-actor
when running gem install
or adding the gem to Bundler.
gem install ntl-actor
Bundler:
gem 'ntl-actor', require: 'actor'
If you add https://repo.fury.io/ntl/
to your list of gem sources, you can install the library by its proper name:
gem install actor
Bundler:
gem 'actor'
Defining an Actor
class Factorial
include Actor
attr_reader :number, :reply_address
def initialize number, reply_address
@number, @reply_address = number, reply_address
end
handle :start do
if number == 1
reply 1
else
Factorial.start number - 1, address
end
end
handle :result do |previous_result|
value = previous_result.value * number
reply value
end
def reply value
result = Result.new value, number
send.(result, reply_address)
:stop
end
Result = Struct.new :value, :number do
include Actor::Messaging::Message
end
end
Starting an Actor
result_address = Actor::Messaging::Address.build
Factorial.start 42, result_address
result = Actor::Messaging::Read.(result_address)
puts "fac(42) = #{result.value}"
Handling Custom Messages
You can send any ruby object that includes Actor::Messaging::Message
to the actor with Actor::Messaging::Send
; though mutable objects aren’t recommended, as messages will be read by other threads. Handlers can be defined for those messages through the handle
class macro on the Actor class. The class of the message is generally passed to handle
, but an underscore cased symbol can be used as well. For example:
class SomeActor
include Actor
# ...
handle :some_message do |message|
# do something
end
handle OtherMessage do |message|
# do something else
end
end
# Start an actor and send a custom message to it
address = Actor.start
Actor::Messaging::Send.(SomeMessage.new, address)
Actor::Messaging::Send.(OtherMessage.new, address)
Also, every Actor comes equipped with a send
dependency which is just an instance of Actor::Messaging::Send
. When any actor is instantiated directly through its initialize
method, the send dependency is an inert substitute. When the actor is constructed through the .start
class method, the send dependency will actually deliver messages to other actors.
Errors
When an actor raises an error, its thread immediately stops, but the rest of the ruby program remains unaffected. If you call #join
on the thread object returned by .start
, the error will be re-raised. The actor will not restart itself or deliver an exception notification. When using a supervisor (see below), any errors raised by actors will be re-raised by the supervisor. It should go without saying that errors are undesirable and Actor makes no effort to make them easier to work with. "Don’t let it crash" is the idea.
Supervisor
In production, actors are best run within the context of a supervisor that keeps track of all actors, watches out for crashed actors, and gracefully shuts down actors when it’s time to stop the show. See examples/interactive.rb
for an example. Here is a snippet extracted from that file:
Actor::Supervisor.start do |supervisor|
InteractiveExample::Prompt.start
Signal.trap 'INT' do
puts "\n\n** Received SIGINT; shutting down supervisor **\n\n"
Actor::Messaging::Send.(:shutdown, supervisor.address)
end
end
The supervisor also publishes all messages it handles via the observer
library that ships with ruby. An observer is defined by including Actor::Supervisor::Observer
in a class. The handle
macro is available to define handlers for any message sent to the supervisor. For instance, the following observer prints out a message whenever an actor is started:
class SomeObserver
include Actor::Supervisor::Observer
handle Actor::Messages::ActorStarted do |msg|
puts "An actor was started: #{msg.address} is its address"
end
end
Actor::Supervisor.start do |supervisor|
some_observer = SomeObserver.new
supervisor.add_observer some_observer
# Etc.
end
Version Scheme
Actor follows a version scheme with three numbers separated by dots, similar to SemVer, but the numbers have a slightly different meaning. The first number indicates the major product version, or epoch. The second number is increased for breaking changes, otherwise the third number is increased.