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 ďŹrst 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 notiďŹed 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 deďŹne 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 speciďŹc, 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