actor
actor copied to clipboard
Actor pattern implementation for ruby
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.
[source,sh]
gem install ntl-actor
Bundler:
[source,ruby]
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:
[source,sh]
gem install actor
Bundler:
[source,ruby]
gem 'actor'
=== Defining an Actor
[source,ruby]
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
[source,ruby]
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:
[source,ruby]
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:
[source,ruby]
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:
[source,ruby]
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.
=== License
Actor is licensed under the link:doc/MIT-License.txt[MIT license]
Copyright © Nathan Ladd