A introduction about main functionalities of Celluloid, a Ruby Actor Model implementation, and some of famous libraries that uses it - Celluloid::IO, DCell and Reel.
2. TOPICS
• Reactor Pattern / Actor Model revisited
• Celluloid
• Celluloid::IO
• DCell
• Reel
• #TODO
Monday, July 15, 13
3. REACTOR PATTERN REVISITED
• Event Handling for concurrent requests
• Multiplex
• X inputs are combined to a single channel
• Demultiplex
• Single channel is converted to X inputs
• Aka Synchronous Event Loop
Monday, July 15, 13
4. REACTOR PATTERN REVISITED
• Dispatcher
• Dispatch resources from Demultiplexer to related request
handler
• Request Handler
• An app that handles request
Monday, July 15, 13
5. ACTOR MODEL REVISITED
• Carl Hewitt paper from 1973
• Mathematical model of Concurrent Computation
• Known first languages:
• Cosmic Cube
• J-Machine
• Most popular implementation: Erlang
Monday, July 15, 13
6. ACTOR MODEL REVISITED
• Actor is a entity that interact with other actors sending /
receiving messages (mailbox)
• Each actor runs as a independent process
• No shared state
Monday, July 15, 13
7. CELLULOID
• Ruby Actor Model implementation
• Created byTony Arcieri - @bascule
• Need Fibers support
• MRI 1.9
• Rubinius / JRuby with 1.9 mode enabled
• Use. Only.Thread. Safe. Libs. For.Your. Sanity.
• Heavily inspired on Erlang concurrency approach
Monday, July 15, 13
8. CELLULOID
• Automatic Synchronization
• Don’t worry with semaphores / mutex, Celluloid manages :)
• Remember: each actor runs in a thread
• Method dispatch using Fibers
• If method call other actors, Fiber is suspended until call chain
returns something
• Example: I/O waiting
Monday, July 15, 13
9. CELLULOID
# -*- encoding: UTF-8 -*-
require 'celluloid'
class FredFlinstone
include Celluloid
def scream(to)
@scream = "#{to}#{to[-1] * 10}"
@screamed_at = Time.now
end
def resume
"Screamed [#{@scream}] at #{@screamed_at}"
end
end
Monday, July 15, 13
10. CELLULOID
irb(main):001:0> fred = FredFlinstone.new
=> #<Celluloid::ActorProxy(FredFlinstone:0x9cd7cc)>
irb(main):002:0> fred.async.scream "Wilma"
=> nil
irb(main):003:0> fred.resume
=> "Screamed [Wilmaaaaaaaaaaa] at 2013-07-10 23:01:29 -0300"
Monday, July 15, 13
11. CELLULOID
• Fault-tolerance
• Erlang philosophy: let it crash
• Celluloid handles crashed actors with these mechanisms:
• Supervisors
• Supervision groups
• Linking
Monday, July 15, 13
12. CELLULOID
• Supervisors
• How actors crash? Simple: unhandled exceptions
• Warning #1: async calls that raises an error crashes the message
receiver; posterior calls NOT RAISES ANYTHING.
• Warning #2: actors spawns a nativeThread, that are not
automatically cleaned by GC; you *must* explicitly terminate
them if not crashed.
• Supervise to the rescue
Monday, July 15, 13
13. CELLULOID
# -*- encoding: UTF-8 -*-
require 'celluloid'
class Devops
include Celluloid
def initialize(name)
@name = name
end
def up_to_no_good
@bad_cmd = 'rm-f /'
@command = `#{@bad_cmd}`,
@executed_at = Time.now
end
end
Monday, July 15, 13
15. CELLULOID
• Supervision Groups
• Supervise many actors at once
• Able to supervise other groups too
• You can create pools of supervised actors
• Transparent GC cleaning (automatic terminate all supervised
actors)
Monday, July 15, 13
16. CELLULOID
# -*- encoding: UTF-8 -*-
require 'celluloid'
class EyeOfSauron < Celluloid::SupervisionGroup
supervise FredFlinstone, as: :fred
pool Devops, as: :devops_pool
end
Monday, July 15, 13
18. CELLULOID
• Linking
• Suppose that you have two interdependent actors and want
to be notified if one fails
• Association by linking actor that commonly dies and the
receiver enables a simple callback when failure occurs
• Very useful to terminate broken actors manually
Monday, July 15, 13
19. CELLULOID
# -*- encoding: UTF-8 -*-
require 'celluloid'
class RobertoBaggio
include Celluloid
class KickedFarAwayError < StandardError; end
def kick_penalty
raise KickedFarAwayError, "OH MAMMA MIA! :'("
end
end
Monday, July 15, 13
20. CELLULOID
# -*- encoding: UTF-8 -*-
require 'celluloid'
class GalvaoBueno
include Celluloid
trap_exit :penalty_kick
def penalty_kick(player, reason)
puts "#{player.inspect} will kick and... #{reason.class}!"
2.times { puts "ACABOOOOOOU! "; sleep(1) }
3.times { puts "EH TETRAAAA! "; sleep(1) }
end
end
Monday, July 15, 13
22. CELLULOID
• Futures
• Kind of lazy computation: request a future on method call
and only execute it when needed
• When value is required, Celluloid internal threadpool
executes method synchronously and returns the result
• Transparent error raising
• No need to explicitly clean up pool, let GC work
Monday, July 15, 13
23. CELLULOID
# -*- encoding: UTF-8 -*-
require 'celluloid'
require 'restclient'
class LazyConsumer
include Celluloid
def retrieve
RestClient.get('http://www.locaweb.com.br').body
end
end
Monday, July 15, 13
25. CELLULOID
• Pools
• You can define a pool of actors (ORLY?);
• Default size: cores available on machine (Celluloid.cores)
• Delegates method call to a worker on pool to execute it
• Not sooo great due for GIL on MRI, but is OK when you have async I/O :)
• Main tips:
• Synchronous calls if concurrent access to a resource (via Actor.<#method> or
Actor.future.<#method>)
• Asynchronous calls if parallel computation (via Actor.async.<#method>)
Monday, July 15, 13
26. CELLULOID
# -*- encoding: UTF-8 -*-
require 'celluloid'
require 'restclient'
class LazyConsumer
include Celluloid
def retrieve
RestClient.get('http://www.locaweb.com.br').body
end
end
Monday, July 15, 13
28. CELLULOID::IO
• Celluloid plus Evented I/O = Celluloid::IO
• Celluloid with steroids =P
• Uses nio4r (libev native extension) as a Reactor to manage
Celluloid Actor Mailboxes
• Great with most-idle connections (sockets, websockets and
friends)
• Multiplex message processing and I/O in a transparent way
Monday, July 15, 13
30. CELLULOID::IO
# -*- encoding: UTF-8 -*-
require 'celluloid/io'
class WhoisServer
include Celluloid::IO
def initialize(host, port)
@server = TCPServer.new host, port
end
def start ; run ; end
def stop ; @server.close if @server ; end
def run ; loop { async.handle_connection @server.accept } ; end
def handle_connection(socket)
_, port, host = socket.peeraddr
domain_id = socket.read.strip
socket.write("I received a query to #{domain_id} at #{Time.now}n")
ensure
socket.close
end
end
Monday, July 15, 13
31. CELLULOID::IO
irb(main):001:0> ws = WhoisServer.new '0.0.0.0', 4343
=> #<Celluloid::ActorProxy(WhoisServer:0xa2f404)
@server=#<Celluloid::IO::TCPServer:0x00000001652358 @server=#<TCPServer:fd 10>>>
irb(main):002:0> ws.async.start
=> nil
vagrant@vagrant-debian-wheezy:~$ whois -h localhost -p 4343 xalala.com.br
I received a query to xelele.com.br at 2013-07-11 01:00:17 -0300
vagrant@vagrant-debian-wheezy:~$ whois -h localhost -p 4343 xirubiru.com.br
I received a query to xirubiru.com.br at 2013-07-11 01:01:09 -0300
Monday, July 15, 13
32. DCELL
• Distributed Ruby (wat) objects as network services
• DCell != DRb (Distributed Ruby)
• DRb comes with Ruby STDLIB
• Ruby specific, not interoperatable with CORBA, RMI, etc
• DCell is built on top of Celluloid::ZMQ
• ØMQ protocol implementation with Celluloid Actors
Monday, July 15, 13
33. DCELL
# -*- encoding: UTF-8 -*-
# example from https://github.com/celluloid/dcell :)
require 'dcell'
DCell.start id: 'itchy', addr: 'tcp://127.0.0.1:9001'
class Itchy
include Celluloid
def initialize
puts "Ready for mayhem!"
@n = 0
end
def fight
@n = (@n % 6) + 1
puts(@n <= 3 ? "Bite!" : "Fight!")
end
end
Itchy.supervise_as :itchy ; sleep
Monday, July 15, 13
34. DCELL
# -*- encoding: UTF-8 -*-
# example from https://github.com/celluloid/dcell :)
require 'dcell'
DCell.start id: 'scratchy', addr: 'tcp://127.0.0.1:9002'
itchy_node = DCell::Node['itchy']
puts "Fighting itchy! (check itchy's output)"
6.times do
itchy_node[:itchy].fight
sleep 1
end
Monday, July 15, 13
35. REEL
• Celluloid::IO web server powered
• Similar syntax to EventMachine
• And, of course, weird and potentially ugly after some time
• Rack support is experimental
• Good with websockets
• Not so fast:
• Goliath < Reel <<<<Thin <<< Node.js
Monday, July 15, 13
36. REEL
• Let’s show code from https://github.com/salizzar/reel-example
Monday, July 15, 13
37. #TODO
• Great opportunity to create a DCell similar gem using AMQP
• Stable Rack support for Reel
• Not sure, low usage at this time
• Other wrappers are welcome
• Celluloid::Redis is a great example
• Celluloid wrap != EventMachine wrap
• Use Dependency Injection API (if possible) to wrap sockets with
Celluloid::IO instead of STDLIB sockets
Monday, July 15, 13