Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Celluloid, Celluloid::IO and Friends

4,428 views

Published on

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.

Celluloid, Celluloid::IO and Friends

  1. 1. CELLULOID, CELLULOID::IO AND FRIENDS Marcelo Correia Pinheiro @salizzar Monday, July 15, 13
  2. 2. TOPICS • Reactor Pattern / Actor Model revisited • Celluloid • Celluloid::IO • DCell • Reel • #TODO Monday, July 15, 13
  3. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
  14. 14. CELLULOID irb(main):001:0> supervisor = Devops.supervise "@salizzar" => #<Celluloid::ActorProxy(Celluloid::SupervisionGroup:0x91e6b4) (...) irb(main):002:0> salizzar = supervisor.actors.first => #<Celluloid::ActorProxy(Devops:0x907c84) @name="@salizzar"> irb(main):003:0> salizzar.async.up_to_no_good => nil E, [2013-07-10T23:26:54.507455 #2467] ERROR -- : Devops crashed! Errno::ENOENT: No such file or directory - rm-f / ! /vagrant/examples/supervisor.rb:14:in ``' ! /vagrant/examples/supervisor.rb:14:in `up_to_no_good' ! (...) irb(main):004:0> salizzar => #<Celluloid::ActorProxy(Devops) dead> irb(main):005:0> salizzar.terminate Celluloid::DeadActorError: actor already terminated (...) irb(main):006:0> salizzar = supervisor.actors.first => #<Celluloid::ActorProxy(Devops:0x4e99b8) @name="@salizzar"> Monday, July 15, 13
  15. 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. 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
  17. 17. CELLULOID irb(main):001:0> eye_of_sauron = EyeOfSauron.run! => #<Celluloid::ActorProxy(EyeOfSauron:0xecaab8) @members=[#<Celluloid::SupervisionGroup::Member:0x00000001d89720 (...) irb(main):002:0> fred = Celluloid::Actor[:fred] => #<Celluloid::ActorProxy(FredFlinstone:0xec39d4)> irb(main):003:0> devops = Celluloid::Actor[:devops_pool] => #<Celluloid::ActorProxy(Devops:0xe3f0e4) @name="hipster"> irb(main):004:0> Monday, July 15, 13
  18. 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. 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. 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
  21. 21. CELLULOID irb(main):001:0> galvao = GalvaoBueno.new => #<Celluloid::ActorProxy(GalvaoBueno:0x12e6aac)> irb(main):002:0> baggio = RobertoBaggio.new => #<Celluloid::ActorProxy(RobertoBaggio:0x1312e68)> irb(main):003:0> galvao.link baggio => #<Celluloid::ActorProxy(RobertoBaggio:0x1312e68)> irb(main):004:0> baggio.async.kick_penalty => nil E, [2013-07-11T00:05:36.212336 #2586] ERROR -- : RobertoBaggio crashed! RobertoBaggio::KickedFarAwayError: OH MAMMA MIA! :'( ! /vagrant/examples/baggio.rb:11:in `kick_penalty' ! (...) irb(main):005:0> #<Celluloid::ActorProxy(RobertoBaggio) dead> will kick and... RobertoBaggio::KickedFarAwayError! ACABOOOOOOU! ACABOOOOOOU! EH TETRAAAA! EH TETRAAAA! EH TETRAAAA! Monday, July 15, 13
  22. 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. 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
  24. 24. CELLULOID irb(main):001:0> consumer = LazyConsumer.new => #<Celluloid::ActorProxy(LazyConsumer:0x12f4210)> irb(main):002:0> future = consumer.future.retrieve => #<Celluloid::Future:0x000000025957e8> irb(main):003:0> future.value => "<!DOCTYPE html>n<html dir="ltr" lang="pt-BR">n<head>nt <meta http- equiv="content-type" content="text/html; charset=utf-8" />n <meta name= "robots" content="index, follow" />n (...) ” Monday, July 15, 13
  25. 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. 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
  27. 27. CELLULOID irb(main):001:0> Celluloid::Actor.all.size => 0 irb(main):002:0> consumer = LazyConsumer.pool size: 4 => #<Celluloid::ActorProxy(LazyConsumer:0xc69bc0)> irb(main):003:0> Celluloid::Actor.all.size => 5 Monday, July 15, 13
  28. 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
  29. 29. CELLULOID::IO • Stream-based: • Celluloid::IO::TCPSocket • Celluloid::IO::UnixSocket • Celluloid::IO::TCPServer • Celluloid::IO::UnixServer • SSL • Celluloid::IO::SSLSocket • Celluloid::IO::SSLServer • UDP • Celluloid::IO::UDPSocket Monday, July 15, 13
  30. 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. 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. 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. 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. 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. 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. 36. REEL • Let’s show code from https://github.com/salizzar/reel-example Monday, July 15, 13
  37. 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
  38. 38. DOUBTS? • Questions are free as beer :) Monday, July 15, 13
  39. 39. THANKYOU! :) Monday, July 15, 13

×