celluloid-io 0.12.1 → 0.13.0.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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