celluloid-io 0.12.1 → 0.13.0.pre

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -3,12 +3,13 @@ rvm:
3
3
  - 1.9.3
4
4
  - ruby-head
5
5
  - jruby-19mode
6
+ - rbx-19mode
7
+ - jruby-head
6
8
 
7
- # Getting a deadlock in the nonblocking connect code :(
8
- # - jruby-head
9
-
10
- # See https://github.com/rubinius/rubinius/issues/1611
11
- # - rbx-19mode
9
+ matrix:
10
+ allow_failures:
11
+ - rvm: ruby-head
12
+ - rvm: jruby-head
12
13
 
13
14
  notifications:
14
15
  irc: "irc.freenode.org#celluloid"
data/CHANGES.md CHANGED
@@ -1,6 +1,8 @@
1
- 0.12.1
2
- ------
3
- * Non-blocking connect workaround for JRuby
1
+ 0.13.0.pre
2
+ ----------
3
+ * Initial SSL support via Celluloid::IO::SSLSocket and
4
+ Celluloid::IO::SSLServer
5
+ * Celluloid 0.13 compatibility fixes
4
6
 
5
7
  0.12.0
6
8
  ------
data/Gemfile CHANGED
@@ -1,6 +1,6 @@
1
1
  source :rubygems
2
2
 
3
- gem 'celluloid', :git => 'git://github.com/celluloid/celluloid'
3
+ #gem 'celluloid', :git => 'git://github.com/celluloid/celluloid'
4
4
 
5
5
  # Specify your gem's dependencies in celluloid-io.gemspec
6
6
  gemspec
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard 'rspec', :cli => '--format documentation' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec/" }
5
+ end
data/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
  =============
3
3
  [![Build Status](https://secure.travis-ci.org/celluloid/celluloid-io.png?branch=master)](http://travis-ci.org/celluloid/celluloid-io)
4
4
  [![Dependency Status](https://gemnasium.com/celluloid/celluloid-io.png)](https://gemnasium.com/celluloid/celluloid-io)
5
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/celluloid/celluloid-io)
5
6
 
6
7
  You don't have to choose between threaded and evented IO! Celluloid::IO
7
8
  provides an event-driven IO system for building fast, scalable network
@@ -35,6 +36,37 @@ and kqueue.
35
36
 
36
37
  Like Celluloid::IO? [Join the Google Group](http://groups.google.com/group/celluloid-ruby)
37
38
 
39
+ When should I use Celluloid::IO?
40
+ --------------------------------
41
+
42
+ Unlike systems like Node.js, Celluloid does not require that all I/O be
43
+ "evented". Celluloid fully supports any libraries that support blocking I/O
44
+ and for the *overwhelming majority* of use cases blocking I/O is more than
45
+ sufficient. Using blocking I/O means that any Ruby library you want will
46
+ Just Work without resorting to any kind of theatrics.
47
+
48
+ Celluloid::IO exists for a few reasons:
49
+
50
+ * During a blocking I/O operation, Celluloid actors cannot respond to incoming
51
+ messages to their mailboxes. They will process messages as soon as the
52
+ method containing a blocking I/O operation completes, however until this
53
+ happens the entire actor is blocked. If you would like to multiplex both
54
+ message processing and I/O operations, you will want to use Celluloid::IO.
55
+ This is especially important for *indefinite* blocking operations, such as
56
+ listening for incoming TCP connections.
57
+ * Celluloid uses a native thread per actor. While native threads aren't
58
+ particularly expensive in Ruby (~20kB of RAM), you can use less RAM using
59
+ Celluloid::IO. You might consider using Celluloid::IO over an
60
+ actor-per-connection if you are dealing with 10,000 connections or more.
61
+ * The goal of Celluloid::IO is to fully integrate it into the Celluloid
62
+ ecosystem, including DCell. DCell will hopefully eventually support
63
+ serializable I/O handles that you can seamlessly transfer between nodes.
64
+
65
+ All that said, if you are just starting out with Celluloid, you probably want
66
+ to start off using blocking I/O until you understand the fundamentals of
67
+ Celluloid and have encountered one of the above reasons for switching
68
+ over to Celluloid::IO.
69
+
38
70
  Supported Platforms
39
71
  -------------------
40
72
 
data/benchmarks/actor.rb CHANGED
@@ -2,12 +2,25 @@
2
2
 
3
3
  require 'rubygems'
4
4
  require 'bundler/setup'
5
- require 'celluloid/io'
5
+ require 'celluloid'
6
6
  require 'benchmark/ips'
7
7
 
8
8
  class ExampleActor
9
9
  include Celluloid::IO
10
+
11
+ def initialize
12
+ @condition = Condition.new
13
+ end
14
+
10
15
  def example_method; end
16
+
17
+ def finished
18
+ @condition.signal
19
+ end
20
+
21
+ def wait_until_finished
22
+ @condition.wait
23
+ end
11
24
  end
12
25
 
13
26
  example_actor = ExampleActor.new
@@ -24,11 +37,18 @@ end
24
37
 
25
38
  Benchmark.ips do |ips|
26
39
  ips.report("spawn") { ExampleActor.new.terminate }
27
- ips.report("calls") { example_actor.example_method }
28
-
29
- # FIXME: deadlock?! o_O
30
- ips.report("async calls") { example_actor.example_method! } unless RUBY_ENGINE == 'ruby'
31
40
 
41
+ ips.report("calls") { example_actor.example_method }
42
+
43
+ ips.report("async calls") do |n|
44
+ waiter = example_actor.future.wait_until_finished
45
+
46
+ for i in 1..n; example_actor.async.example_method; end
47
+ example_actor.async.finished
48
+
49
+ waiter.value
50
+ end
51
+
32
52
  ips.report("messages") do |n|
33
53
  latch_in << n
34
54
  for i in 0..n; mailbox << :message; end
data/celluloid-io.gemspec CHANGED
@@ -15,10 +15,12 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Celluloid::IO::VERSION
17
17
 
18
- gem.add_dependency 'celluloid', '~> 0.12.0'
18
+ gem.add_dependency 'celluloid', '>= 0.13.0.pre'
19
19
  gem.add_dependency 'nio4r', '>= 0.4.0'
20
-
20
+
21
21
  gem.add_development_dependency 'rake'
22
22
  gem.add_development_dependency 'rspec'
23
23
  gem.add_development_dependency 'benchmark_suite'
24
+ gem.add_development_dependency 'guard-rspec'
25
+ gem.add_development_dependency 'rb-fsevent', '~> 0.9.1' if RUBY_PLATFORM =~ /darwin/
24
26
  end
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $:.push File.expand_path('../../lib', __FILE__)
3
+ require 'bundler/setup'
4
4
  require 'celluloid/io'
5
5
 
6
6
  class EchoServer
@@ -0,0 +1,28 @@
1
+ require 'bundler/setup'
2
+ require 'celluloid/io'
3
+
4
+ class EchoUNIXClient
5
+ include Celluloid::IO
6
+
7
+ def initialize(socket_path)
8
+ puts "*** connecting to #{socket_path}"
9
+ @socket_path = socket_path
10
+ @socket = UNIXSocket.open(socket_path)
11
+ end
12
+
13
+ def echo(msg)
14
+ puts "*** send to server: '#{msg}'"
15
+ @socket.puts(msg)
16
+ data = @socket.readline.chomp
17
+ puts "*** server unswer '#{data}'"
18
+ data
19
+ end
20
+
21
+ def finalize
22
+ @socket.close if @socket
23
+ end
24
+
25
+ end
26
+
27
+ c = EchoUNIXClient.new("/tmp/sock_test")
28
+ c.echo("DATA")
@@ -0,0 +1,43 @@
1
+ require 'bundler/setup'
2
+ require 'celluloid/io'
3
+
4
+ class EchoUNIXServer
5
+ include Celluloid::IO
6
+
7
+ attr_reader :socket_path, :server
8
+
9
+ def initialize(socket_path)
10
+ puts "*** start server #{socket_path}"
11
+ @socket_path = socket_path
12
+ @server = UNIXServer.open(socket_path)
13
+ end
14
+
15
+ def run
16
+ loop { handle_connection! @server.accept }
17
+ end
18
+
19
+ def handle_connection(socket)
20
+ loop do
21
+ data = socket.readline
22
+ puts "*** gets data #{data}"
23
+ socket.write(data)
24
+ end
25
+
26
+ rescue EOFError
27
+ puts "*** disconnected"
28
+
29
+ ensure
30
+ socket.close
31
+ end
32
+
33
+ def finalize
34
+ if @server
35
+ @server.close
36
+ File.delete(@socket_path)
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ s = EchoUNIXServer.new("/tmp/sock_test")
43
+ s.run
@@ -4,7 +4,7 @@ module Celluloid
4
4
  module CommonMethods
5
5
  # Are we inside of a Celluloid::IO actor?
6
6
  def evented?
7
- actor = Thread.current[:actor]
7
+ actor = Thread.current[:celluloid_actor]
8
8
  actor && actor.mailbox.is_a?(Celluloid::IO::Mailbox)
9
9
  end
10
10
 
@@ -29,7 +29,7 @@ module Celluloid
29
29
  # Request exclusive control for a particular operation
30
30
  # Type should be one of :r (read) or :w (write)
31
31
  def acquire_ownership(type)
32
- return unless Thread.current[:actor]
32
+ return unless Thread.current[:celluloid_actor]
33
33
 
34
34
  case type
35
35
  when :r
@@ -40,14 +40,14 @@ module Celluloid
40
40
  end
41
41
 
42
42
  # Celluloid needs a better API here o_O
43
- Thread.current[:actor].wait(self) while instance_variable_get(ivar)
43
+ Thread.current[:celluloid_actor].wait(self) while instance_variable_defined?(ivar) && instance_variable_get(ivar)
44
44
  instance_variable_set(ivar, Task.current)
45
45
  end
46
46
 
47
47
  # Release ownership for a particular operation
48
48
  # Type should be one of :r (read) or :w (write)
49
49
  def release_ownership(type)
50
- return unless Thread.current[:actor]
50
+ return unless Thread.current[:celluloid_actor]
51
51
 
52
52
  case type
53
53
  when :r
@@ -57,13 +57,13 @@ module Celluloid
57
57
  else raise ArgumentError, "invalid ownership type: #{type}"
58
58
  end
59
59
 
60
- raise "not owner" unless instance_variable_get(ivar) == Task.current
60
+ raise "not owner" unless instance_variable_defined?(ivar) && instance_variable_get(ivar) == Task.current
61
61
  instance_variable_set(ivar, nil)
62
- Thread.current[:actor].signal(self)
62
+ Thread.current[:celluloid_actor].signal(self)
63
63
  end
64
64
 
65
65
  def read(length = nil, buffer = nil)
66
- buffer ||= ''
66
+ buffer ||= ''.force_encoding(Encoding::ASCII_8BIT)
67
67
  remaining = length
68
68
 
69
69
  acquire_ownership :r
@@ -97,7 +97,7 @@ module Celluloid
97
97
  end
98
98
 
99
99
  def readpartial(length, buffer = nil)
100
- buffer ||= ''
100
+ buffer ||= ''.force_encoding(Encoding::ASCII_8BIT)
101
101
 
102
102
  begin
103
103
  read_nonblock(length, buffer)
@@ -136,7 +136,11 @@ module Celluloid
136
136
 
137
137
  total_written
138
138
  end
139
- alias_method :<<, :write
139
+
140
+ def <<(string)
141
+ write string
142
+ self
143
+ end
140
144
  end
141
145
  end
142
146
  end
@@ -5,8 +5,8 @@ module Celluloid
5
5
  attr_reader :reactor
6
6
 
7
7
  def initialize(reactor = nil)
8
- @messages = []
9
- @mutex = Mutex.new
8
+ super()
9
+ # @condition won't be used in the class.
10
10
  @reactor = reactor || Reactor.new
11
11
  end
12
12
 
@@ -26,7 +26,7 @@ module Celluloid
26
26
  @messages << message
27
27
  end
28
28
 
29
- current_actor = Thread.current[:actor]
29
+ current_actor = Thread.current[:celluloid_actor]
30
30
  @reactor.wakeup unless current_actor && current_actor.mailbox == self
31
31
  rescue IOError
32
32
  raise MailboxError, "dead recipient"
@@ -29,14 +29,14 @@ module Celluloid
29
29
  # Wait for the given IO operation to complete
30
30
  def wait(io, set)
31
31
  # zomg ugly type conversion :(
32
- unless io.is_a?(::IO)
32
+ unless io.is_a?(::IO) or io.is_a?(OpenSSL::SSL::SSLSocket)
33
33
  if io.respond_to? :to_io
34
34
  io = io.to_io
35
35
  elsif ::IO.respond_to? :try_convert
36
36
  io = ::IO.try_convert(io)
37
37
  end
38
38
 
39
- raise TypeError, "can't convert #{io.class} into IO" unless io.is_a? IO
39
+ raise TypeError, "can't convert #{io.class} into IO" unless io.is_a?(::IO)
40
40
  end
41
41
 
42
42
  monitor = @selector.register(io, set)
@@ -0,0 +1,36 @@
1
+ require 'socket'
2
+
3
+ module Celluloid
4
+ module IO
5
+ # SSLServer wraps a TCPServer to provide immediate SSL accept
6
+ class SSLServer
7
+ extend Forwardable
8
+ def_delegators :@tcp_server, :listen, :shutdown, :close, :closed?, :to_io, :evented?
9
+
10
+ attr_accessor :start_immediately
11
+ attr_reader :tcp_server
12
+
13
+ def initialize(server, ctx)
14
+ if server.is_a?(::TCPServer)
15
+ server = Celluloid::IO::TCPServer.from_ruby_server(server)
16
+ end
17
+ @tcp_server = server
18
+ @ctx = ctx
19
+ @start_immediately = true
20
+ end
21
+
22
+ def accept
23
+ sock = @tcp_server.accept
24
+ begin
25
+ ssl = Celluloid::IO::SSLSocket.new(sock, @ctx)
26
+ ssl.accept if @start_immediately
27
+ ssl
28
+ rescue SSLError
29
+ sock.close
30
+ raise
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,39 @@
1
+ require 'openssl'
2
+
3
+ module Celluloid
4
+ module IO
5
+ # SSLSocket with Celluloid::IO support
6
+ class SSLSocket
7
+ include CommonMethods
8
+ extend Forwardable
9
+
10
+ def_delegators :@socket, :read_nonblock, :write_nonblock, :close, :closed?,
11
+ :cert, :cipher, :client_ca, :peer_cert, :peer_cert_chain, :verify_result
12
+
13
+ def initialize(io, ctx = OpenSSL::SSL::SSLContext.new)
14
+ @context = ctx
15
+ @socket = OpenSSL::SSL::SSLSocket.new(::IO.try_convert(io), @context)
16
+ end
17
+
18
+ def connect
19
+ @socket.connect_nonblock
20
+ rescue ::IO::WaitReadable
21
+ wait_readable
22
+ retry
23
+ end
24
+
25
+ def accept
26
+ @socket.accept_nonblock
27
+ self
28
+ rescue ::IO::WaitReadable
29
+ wait_readable
30
+ retry
31
+ rescue ::IO::WaitWritable
32
+ wait_writable
33
+ retry
34
+ end
35
+
36
+ def to_io; @socket; end
37
+ end
38
+ end
39
+ end
@@ -5,14 +5,14 @@ module Celluloid
5
5
  # TCPServer with combined blocking and evented support
6
6
  class TCPServer
7
7
  extend Forwardable
8
- def_delegators :@server, :listen, :sysaccept, :close, :closed?
8
+ def_delegators :@server, :listen, :sysaccept, :close, :closed?, :addr
9
9
 
10
10
  def initialize(hostname, port)
11
11
  @server = ::TCPServer.new(hostname, port)
12
12
  end
13
13
 
14
14
  def accept
15
- actor = Thread.current[:actor]
15
+ actor = Thread.current[:celluloid_actor]
16
16
 
17
17
  if evented?
18
18
  Celluloid.current_actor.wait_readable @server
@@ -32,9 +32,16 @@ module Celluloid
32
32
 
33
33
  # Are we inside a Celluloid ::IO actor?
34
34
  def evented?
35
- actor = Thread.current[:actor]
35
+ actor = Thread.current[:celluloid_actor]
36
36
  actor && actor.mailbox.is_a?(Celluloid::IO::Mailbox)
37
37
  end
38
+
39
+ # Convert a Ruby TCPServer into a Celluloid::IO::TCPServer
40
+ def self.from_ruby_server(ruby_server)
41
+ server = allocate
42
+ server.instance_variable_set(:@server, ruby_server)
43
+ server
44
+ end
38
45
  end
39
46
  end
40
47
  end
@@ -39,8 +39,6 @@ module Celluloid
39
39
 
40
40
  # Guess it's not an IP address, so let's try DNS
41
41
  unless @addr
42
- # TODO: suppport asynchronous DNS
43
- # Even EventMachine doesn't do async DNS by default o_O
44
42
  addrs = Array(DNSResolver.new.resolve(remote_host))
45
43
  raise Resolv::ResolvError, "DNS result has no information for #{remote_host}" if addrs.empty?
46
44
 
@@ -11,7 +11,7 @@ module Celluloid
11
11
 
12
12
  # Are we inside of a Celluloid::IO actor?
13
13
  def evented?
14
- actor = Thread.current[:actor]
14
+ actor = Thread.current[:celluloid_actor]
15
15
  actor && actor.mailbox.is_a?(Celluloid::IO::Mailbox)
16
16
  end
17
17
 
@@ -0,0 +1,44 @@
1
+ require 'socket'
2
+
3
+ module Celluloid
4
+ module IO
5
+ # UNIXServer with combined blocking and evented support
6
+ class UNIXServer
7
+ extend Forwardable
8
+ def_delegators :@server, :listen, :sysaccept, :close, :closed?
9
+
10
+ def self.open(socket_path)
11
+ self.new(socket_path)
12
+ end
13
+
14
+ def initialize(socket_path)
15
+ @server = ::UNIXServer.new(socket_path)
16
+ end
17
+
18
+ def accept
19
+ actor = Thread.current[:celluloid_actor]
20
+
21
+ if evented?
22
+ Celluloid.current_actor.wait_readable @server
23
+ accept_nonblock
24
+ else
25
+ Celluloid::IO::UNIXSocket.from_ruby_socket @server.accept
26
+ end
27
+ end
28
+
29
+ def accept_nonblock
30
+ Celluloid::IO::UNIXSocket.from_ruby_socket @server.accept_nonblock
31
+ end
32
+
33
+ def to_io
34
+ @server
35
+ end
36
+
37
+ # Are we inside a Celluloid ::IO actor?
38
+ def evented?
39
+ actor = Thread.current[:celluloid_actor]
40
+ actor && actor.mailbox.is_a?(Celluloid::IO::Mailbox)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,40 @@
1
+ require 'socket'
2
+
3
+ module Celluloid
4
+ module IO
5
+ # UNIXSocket with combined blocking and evented support
6
+ class UNIXSocket
7
+ include CommonMethods
8
+ extend Forwardable
9
+
10
+ def_delegators :@socket, :read_nonblock, :write_nonblock, :close, :closed?, :readline, :puts, :addr
11
+
12
+ # Convert a Ruby UNIXSocket into a Celluloid::IO::UNIXSocket
13
+ def self.from_ruby_socket(ruby_socket)
14
+ # Some hax here, but whatever ;)
15
+ socket = allocate
16
+ socket.instance_variable_set(:@socket, ruby_socket)
17
+ socket
18
+ end
19
+
20
+ # Open a UNIX connection.
21
+ def self.open(socket_path, &block)
22
+ self.new(socket_path, &block)
23
+ end
24
+
25
+ # Open a UNIX connection.
26
+ def initialize(socket_path, &block)
27
+ # FIXME: not doing non-blocking connect
28
+ @socket = if block
29
+ ::UNIXSocket.open(socket_path, &block)
30
+ else
31
+ ::UNIXSocket.new(socket_path)
32
+ end
33
+ end
34
+
35
+ def to_io
36
+ @socket
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,5 +1,5 @@
1
1
  module Celluloid
2
2
  module IO
3
- VERSION = "0.12.1"
3
+ VERSION = "0.13.0.pre"
4
4
  end
5
5
  end
data/lib/celluloid/io.rb CHANGED
@@ -10,13 +10,18 @@ require 'celluloid/io/reactor'
10
10
  require 'celluloid/io/tcp_server'
11
11
  require 'celluloid/io/tcp_socket'
12
12
  require 'celluloid/io/udp_socket'
13
+ require 'celluloid/io/unix_server'
14
+ require 'celluloid/io/unix_socket'
15
+
16
+ require 'celluloid/io/ssl_server'
17
+ require 'celluloid/io/ssl_socket'
13
18
 
14
19
  module Celluloid
15
20
  # Actors with evented IO support
16
21
  module IO
17
22
  def self.included(klass)
18
23
  klass.send :include, Celluloid
19
- klass.use_mailbox Celluloid::IO::Mailbox
24
+ klass.mailbox_class Celluloid::IO::Mailbox
20
25
  end
21
26
 
22
27
  extend Forwardable
data/log/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.log