Skip to content

Introduction to ECell

benzrf edited this page Jul 19, 2016 · 5 revisions

WIP

ECell is a framework built on Celluloid and ØMQ for building concurrent, networked, service-based systems.

ECell systems are built out of services called Pieces, which occupy entire Ruby processes (a Piece works with process-global state; this may or may not be changed). Each Piece has one Subject, which is its "primary" actor. Subjects instantiate the class Subject, which includes a state machine governing the life cycle of the Piece. We define a type of Piece we can run by writing a subclass of Subject; such a subclass is called a Sketch. Here's a simple Sketch:

require 'ecell/elements/subject'
require 'ecell/base/designs/follower'
require 'ecell/base/designs/answerer'

class Adder < ECell::Elements::Subject
  def initialize(configuration={})
    design! ECell::Base::Designs::Follower,
            ECell::Base::Designs::Answerer
    super(configuration)
  end

  module RPC
    def add(x, y)
      x + y
    end
  end
  include RPC
end

This Piece makes use of the Follower and Answerer Designs. A Design is a reusable bundle of functionality that extends Pieces with some kind of behavior or ability. Designs can do a number of things, including spawning auxiliary actors called Figures, making connections and bindings, adding methods to Subjects, and registering callbacks for various events. The Follower Design extends a Piece to follow a mesh leader, allowing it to be monitored, managed, and giving it the ability to log to the leader's centralized log. The Answerer Design extends the Piece to answer RPCs. It serves any methods on the Subject that were defined in the Sketch's RPC module.

This Sketch can be run using the ECell::Run.run! method as follows (assuming that monitor is the ID of a mesh leader Piece):

require 'ecell/run'

bindings = {monitor: {}} # this should be filled with the interface and ports that the monitor Piece is using
ECell::Run.run! Adder, piece_id: :adder, bindings: bindings, leader: :monitor

This will bring an instance of the Answerer sketch online, ready to answer calls.

Full Mesh Example

Here is an example of a self-contained, working mesh:

#!/usr/bin/env ruby
require 'ecell/elements/subject'
require 'ecell/base/designs/follower'
require 'ecell/base/designs/answerer'
require 'ecell/base/designs/caller'
require 'ecell/base/sketches/monitor' # a basic leader sketch
require 'ecell/run'

class Adder < ECell::Elements::Subject
  def initialize(configuration={})
    design! ECell::Base::Designs::Follower,
            ECell::Base::Designs::Answerer
    super(configuration)
  end

  module RPC
    def add(x, y)
      x + y
    end
  end
  include RPC
end

class Fib < ECell::Elements::Subject
  def initialize(configuration={})
    design! ECell::Base::Designs::Follower,
            ECell::Base::Designs::Caller
    super(configuration)
    @adder_id = configuration[:adder_id]
  end

  def at_running # hook called by the state machine governing the Piece
    super
    a, b = 0, 1
    every(1) do # Celluloid `every` method
      # synchronously place a call to the piece with the id `@adder_id`
      answer = ECell.call_sync(@adder_id).add(a, b)
      a, b = b, answer.returns
      info(message: a)
    end
  end
end

monitor_base = 7000
bindings = {
  monitor: {
    interface: "127.0.0.1", # put an externally accessible IP if you want to run each Piece on a separate machine
    awareness_subscribe: monitor_base,
    logging_pull: monitor_base += 1,
    management_router: monitor_base += 1,
    management_publish: monitor_base += 1,
    calling_router2: monitor_base += 1,
    calling_router: monitor_base += 1
  }
}
# each piece connects to the leader, so it can route calls
# without the pieces needing to make pre-known bindings,
# so they don't need to be configured here.

log_dir = File.expand_path("../logs", __FILE__)

basic_conf = {bindings: bindings, leader: :monitor, log_dir: log_dir}

case ARGV[0]
when "monitor"
  ECell::Run.run! ECell::Base::Sketches::Monitor,
    basic_conf.merge(piece_id: :monitor, followers: [:adder, :fib])
when "adder"
  ECell::Run.run! Adder,
    basic_conf.merge(piece_id: :adder)
when "fib"
  ECell::Run.run! Fib,
    basic_conf.merge(piece_id: :fib, adder_id: :adder)
end

Save this as mesh.rb and then make a logs directory next to it. If your current ruby command is supplied by Rubinius, you should be able to just run ./mesh.rb monitor, ./mesh.rb adder, and ./mesh.rb fib (in no particular order, as long as they're all started reasonably close to one another), even on different machines from each other. Check the logs for monitor - they should contain the expected periodic messages from fib.

Clone this wiki locally