celluloid-io 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -1,8 +1,10 @@
1
1
  rvm:
2
2
  - 1.9.2
3
3
  - 1.9.3
4
- # - rbx crashing :(
5
- - jruby
6
4
  - ruby-head
5
+ - jruby-19mode
6
+ - jruby-head
7
7
 
8
- env: "JRUBY_OPTS=--1.9"
8
+ # Rubies I would like to support, but they deadlock
9
+ # - rbx-18mode
10
+ # - rbx-19mode
data/CHANGES.md CHANGED
@@ -1,3 +1,10 @@
1
+ 0.9.0
2
+ -----
3
+ * TCPServer, TCPSocket, and UDPSocket classes in Celluloid::IO namespace
4
+ with both evented and blocking I/O support
5
+ * Celluloid::IO::Mailbox.new now takes a single parameter to specify an
6
+ alternative reactor (e.g. Celluloid::ZMQ::Reactor)
7
+
1
8
  0.8.0
2
9
  -----
3
10
  * Switch to nio4r-based reactor
data/README.md CHANGED
@@ -1,82 +1,156 @@
1
- Celluloid::IO
1
+ ![Celluloid](https://github.com/tarcieri/celluloid-io/raw/master/logo.png)
2
2
  =============
3
- [![Build Status](http://travis-ci.org/tarcieri/celluloid-io.png)](http://travis-ci.org/tarcieri/celluloid-io)
4
-
5
- You don't have to choose between threaded and evented IO! Celluloid::IO provides
6
- a simple and easy way to wait for IO events inside of a Celluloid actor, which
7
- runs in its own thread. Any Ruby IO object can be registered and monitored.
8
- It's a somewhat similar idea to Ruby event frameworks like EventMachine and
9
- Cool.io, but Celluloid actors automatically wrap up all IO in Fibers,
10
- resulting in a synchronous API that's duck type compatible with existing IO
11
- objects.
12
-
13
- Unlike EventMachine, you can make as many Celluloid::IO actors as you wish,
14
- each running their own event loop independently from the others. Using many
15
- actors allows your program to scale across multiple CPU cores on Ruby
16
- implementations which don't have a GIL, such as JRuby and Rubinius.
3
+ [![Build Status](https://secure.travis-ci.org/tarcieri/celluloid-io.png?branch=master)](http://travis-ci.org/tarcieri/celluloid-io)
4
+ [![Dependency Status](https://gemnasium.com/tarcieri/celluloid-io.png)](https://gemnasium.com/tarcieri/celluloid-io)
5
+
6
+ You don't have to choose between threaded and evented IO! Celluloid::IO
7
+ provides an event-driven IO system for building fast, scalable network
8
+ applications that integrates directly with the
9
+ [Celluloid actor library](https://github.com/tarcieri/celluloid), making it
10
+ easy to combine both threaded and evented concepts. Celluloid::IO is ideal for
11
+ servers which handle large numbers of mostly-idle connections, such as Websocket
12
+ servers or chat/messaging systems.
13
+
14
+ Celluloid::IO provides a different class of actor: one that's slightly slower
15
+ and heavier than standard Celluloid actors, but one which contains a
16
+ high-performance reactor just like EventMachine or Cool.io. This means
17
+ Celluloid::IO actors have the power of both Celluloid actors and evented
18
+ I/O loops. Unlike certain other evented I/O systems which limit you to a
19
+ single event loop per process, Celluloid::IO lets you make as many actors as
20
+ you want, system resources permitting.
21
+
22
+ Rather than callbacks, Celluloid::IO exposes a synchronous API built on duck
23
+ types of Ruby's own IO classes, such as TCPServer and TCPSocket. These classes
24
+ work identically to their core Ruby counterparts, but in the scope of
25
+ Celluloid::IO actors provide "evented" performance. Since they're drop-in
26
+ replacements for the standard classes, there's no need to rewrite every
27
+ library just to take advantage of Celluloid::IO's event loop and you can
28
+ freely switch between evented and blocking IO even over the lifetime of a
29
+ single connection.
30
+
31
+ Celluloid::IO uses the [nio4r gem](https://github.com/tarcieri/nio4r)
32
+ to monitor IO objects, which provides cross-platform and cross-Ruby
33
+ implementation access to high-performance system calls such as epoll
34
+ and kqueue.
17
35
 
18
36
  Like Celluloid::IO? [Join the Google Group](http://groups.google.com/group/celluloid-ruby)
19
37
 
20
38
  Supported Platforms
21
39
  -------------------
22
40
 
23
- Celluloid works on Ruby 1.9.2+, JRuby 1.6 (in 1.9 mode), and Rubinius 2.0. JRuby
24
- or Rubinius are the preferred platforms as they support true hardware-level
25
- parallelism when running Ruby code, whereas MRI/YARV is constrained by a global
26
- interpreter lock (GIL).
41
+ Celluloid::IO works on Ruby 1.9.2+, JRuby 1.6 (in 1.9 mode), and Rubinius 2.0.
27
42
 
28
43
  To use JRuby in 1.9 mode, you'll need to pass the "--1.9" command line option
29
44
  to the JRuby executable, or set the "JRUBY_OPTS=--1.9" environment variable.
30
45
 
31
- Celluloid works on Rubinius in either 1.8 or 1.9 mode.
32
-
33
46
  Usage
34
47
  -----
35
48
 
36
- To use Celluloid::IO, define a normal Ruby class that includes Celluloid::IO:
49
+ To use Celluloid::IO, define a normal Ruby class that includes Celluloid::IO.
50
+ The following is an example of an echo server:
37
51
 
38
52
  ```ruby
39
53
  require 'celluloid/io'
40
54
 
41
- class MyServer
55
+ class EchoServer
42
56
  include Celluloid::IO
43
57
 
44
- # Bind a TCP server to the given host and port
45
58
  def initialize(host, port)
46
- @server = TCPServer.new host, port
59
+ puts "*** Starting echo server on #{host}:#{port}"
60
+
61
+ # Since we included Celluloid::IO, we're actually making a
62
+ # Celluloid::IO::TCPServer here
63
+ @server = TCPServer.new(host, port)
47
64
  run!
48
65
  end
49
66
 
50
- # Run the TCP server event loop
51
- def run
52
- while true
53
- wait_readable(@server)
54
- on_connect @server.accept
55
- end
67
+ def finalize
68
+ @server.close if @server
56
69
  end
57
70
 
58
- # Terminate this server
59
- def terminate
60
- @server.close
61
- super
71
+ def run
72
+ loop { handle_connection! @server.accept }
62
73
  end
63
74
 
64
- # Called whenever a new connection is opened
65
- def on_connect(connection)
66
- connection.close
75
+ def handle_connection(socket)
76
+ _, port, host = socket.peeraddr
77
+ puts "*** Received connection from #{host}:#{port}"
78
+ loop { socket.write socket.readpartial(4096) }
79
+ rescue EOFError
80
+ puts "*** #{host}:#{port} disconnected"
67
81
  end
68
82
  end
69
83
  ```
70
84
 
85
+ The very first thing including *Celluloid::IO* does is also include the
86
+ *Celluloid* module, which promotes objects of this class to concurrent Celluloid
87
+ actors each running in their own thread. Before trying to use Celluloid::IO
88
+ you may want to [familiarize yourself with Celluloid in general](https://github.com/tarcieri/celluloid/).
89
+ Celluloid actors can each be thought of as being event loops. Celluloid::IO actors
90
+ are heavier but have capabilities similar to other event loop-driven frameworks.
91
+
92
+ While this looks like a normal Ruby TCP server, there aren't any threads, so
93
+ you might expect this server can only handle one connection at a time.
94
+ However, this is all you need to do to build servers that handle as many
95
+ connections as you want, and it happens all within a single thread.
96
+
97
+ The magic in this server which allows it to handle multiple connections
98
+ comes in three forms:
99
+
100
+ * __Replacement classes:__ Celluloid::IO includes replacements for the core
101
+ TCPServer and TCPSocket classes which automatically use an evented mode
102
+ inside of Celluloid::IO actors. They're named Celluloid::IO::TCPServer and
103
+ Celluloid::IO::TCPSocket, so they're automatically available inside
104
+ your class when you include Celluloid::IO.
105
+
106
+ * __Asynchronous method calls:__ You may have noticed that while the methods
107
+ of EchoServer are named *run* and *handle_connection*, they're invoked as
108
+ *run!* and *handle_connection!*. This queues these methods to be executed
109
+ after the current method is complete. You can queue up as many methods as
110
+ you want, allowing asynchronous operation similar to the "call later" or
111
+ "next tick" feature of Twisted, EventMachine, and Node. This echo server
112
+ first kicks off a background task for accepting connections on the server
113
+ socket, then kicks off a background task for each connection.
114
+
115
+ * __Reactor + Fibers:__ Celluloid::IO is a combination of Actor and Reactor
116
+ concepts. The blocking mechanism used by the mailboxes of Celluloid::IO
117
+ actors is an [nio4r-powered reactor](https://github.com/tarcieri/celluloid-io/blob/master/lib/celluloid/io/reactor.rb).
118
+ When the current task needs to make a blocking I/O call, it first makes
119
+ a non-blocking attempt, and if the socket isn't ready the current task
120
+ is suspended until the reactor detects the operation is ready and resumes
121
+ the suspended task.
122
+
123
+ The result is an API for doing evented I/O that looks identical to doing
124
+ synchronous I/O. Adapting existing synchronous libraries to using evented I/O
125
+ is as simple as having them use one of Celluloid::IO's provided replacement
126
+ classes instead of the core Ruby TCPSocket and TCPServer classes.
127
+
128
+ Status
129
+ ------
130
+
131
+ The rudiments of TCPServer and TCPSocket are in place and ready to use.
132
+ Several methods are still missing. Making new connections with
133
+ Celluloid::IO::TCPSocket.new works, however it presently does blocking DNS
134
+ resolution and connect so it can stall the reactor loop.
135
+
136
+ Basic UDPSocket support is in place. On JRuby, recvfrom makes a blocking call
137
+ as the underlying recvfrom_nonblock call is not supported by JRuby.
138
+
139
+ No UNIXSocket support yet, sorry (patches welcome!)
140
+
71
141
  Contributing to Celluloid::IO
72
142
  -----------------------------
73
143
 
74
- * Fork Celluloid on github
144
+ * Fork this repository on github
75
145
  * Make your changes and send me a pull request
76
- * If I like them I'll merge them and give you commit access to my repository
146
+ * If I like them I'll merge them
147
+ * If I've accepted a patch, feel free to ask for a commit bit!
77
148
 
78
149
  License
79
150
  -------
80
151
 
81
152
  Copyright (c) 2011 Tony Arcieri. Distributed under the MIT License. See
82
153
  LICENSE.txt for further details.
154
+
155
+ Contains code originally from the RubySpec project also under the MIT License
156
+ Copyright (c) 2008 Engine Yard, Inc. All rights reserved.
data/celluloid-io.gemspec CHANGED
@@ -15,9 +15,9 @@ 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.8.0'
19
- gem.add_dependency 'nio4r', '>= 0.2.2'
18
+ gem.add_dependency 'celluloid', '~> 0.9.0'
19
+ gem.add_dependency 'nio4r', '>= 0.3.1'
20
20
 
21
21
  gem.add_development_dependency 'rake'
22
- gem.add_development_dependency 'rspec', '~> 2.7.0'
22
+ gem.add_development_dependency 'rspec'
23
23
  end
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.push File.expand_path('../../lib', __FILE__)
4
+ require 'celluloid/io'
5
+
6
+ class EchoServer
7
+ include Celluloid::IO
8
+
9
+ def initialize(host, port)
10
+ puts "*** Starting echo server on #{host}:#{port}"
11
+
12
+ # Since we included Celluloid::IO, we're actually making a
13
+ # Celluloid::IO::TCPServer here
14
+ @server = TCPServer.new(host, port)
15
+ run!
16
+ end
17
+
18
+ def finalize
19
+ @server.close if @server
20
+ end
21
+
22
+ def run
23
+ loop { handle_connection! @server.accept }
24
+ end
25
+
26
+ def handle_connection(socket)
27
+ _, port, host = socket.peeraddr
28
+ puts "*** Received connection from #{host}:#{port}"
29
+ loop { socket.write socket.readpartial(4096) }
30
+ rescue EOFError
31
+ puts "*** #{host}:#{port} disconnected"
32
+ socket.close
33
+ end
34
+ end
35
+
36
+ supervisor = EchoServer.supervise("127.0.0.1", 1234)
37
+ trap("INT") { supervisor.terminate; exit }
38
+ sleep
data/lib/celluloid/io.rb CHANGED
@@ -2,9 +2,14 @@ require 'celluloid/io/version'
2
2
 
3
3
  require 'forwardable'
4
4
  require 'celluloid'
5
+ require 'celluloid/io/common_methods'
5
6
  require 'celluloid/io/mailbox'
6
7
  require 'celluloid/io/reactor'
7
8
 
9
+ require 'celluloid/io/tcp_server'
10
+ require 'celluloid/io/tcp_socket'
11
+ require 'celluloid/io/udp_socket'
12
+
8
13
  module Celluloid
9
14
  # Actors with evented IO support
10
15
  module IO
@@ -0,0 +1,96 @@
1
+ module Celluloid
2
+ module IO
3
+ # Common implementations of methods originall from the IO class
4
+ module CommonMethods
5
+ # Are we inside of a Celluloid::IO actor?
6
+ def evented?
7
+ actor = Thread.current[:actor]
8
+ actor && actor.mailbox.is_a?(Celluloid::IO::Mailbox)
9
+ end
10
+
11
+ # Wait until the current object is readable
12
+ def wait_readable
13
+ if evented?
14
+ Celluloid.current_actor.wait_readable(self.to_io)
15
+ else
16
+ Kernel.select([self.to_io])
17
+ end
18
+ end
19
+
20
+ # Wait until the current object is writable
21
+ def wait_writable
22
+ actor = Thread.current[:actor]
23
+
24
+ if evented?
25
+ Celluloid.current_actor.wait_writable(self.to_io)
26
+ else
27
+ Kernel.select([], [self.to_io])
28
+ end
29
+ end
30
+
31
+ def read(length, buffer = nil)
32
+ buffer ||= ''
33
+ remaining = length
34
+
35
+ until remaining.zero?
36
+ begin
37
+ str = readpartial(remaining)
38
+ rescue EOFError
39
+ return if length == remaining
40
+ return buffer
41
+ end
42
+
43
+ buffer << str
44
+ remaining -= str.length
45
+ end
46
+
47
+ buffer
48
+ end
49
+
50
+ def readpartial(length, buffer = nil)
51
+ buffer ||= ''
52
+
53
+ begin
54
+ read_nonblock(length, buffer)
55
+ rescue ::IO::WaitReadable
56
+ wait_readable
57
+ retry
58
+ end
59
+
60
+ buffer
61
+ end
62
+
63
+ def write(string)
64
+ length = string.length
65
+ total_written = 0
66
+
67
+ remaining = string
68
+ while total_written < length
69
+ begin
70
+ written = write_nonblock(remaining)
71
+ rescue ::IO::WaitWritable
72
+ wait_writable
73
+ retry
74
+ rescue EOFError
75
+ return total_written
76
+ end
77
+
78
+ total_written += written
79
+ if written < remaining.length
80
+ # Avoid mutating string itself, but we can mutate the remaining data
81
+ if remaining == string
82
+ # Copy the remaining data so as to avoid mutating string
83
+ # Note if we have a large amount of data remaining this could be slow
84
+ remaining = string[written..-1]
85
+ else
86
+ remaining.slice!(0, written)
87
+ end
88
+ end
89
+ end
90
+
91
+ total_written
92
+ end
93
+ alias_method :<<, :write
94
+ end
95
+ end
96
+ end
@@ -4,33 +4,34 @@ module Celluloid
4
4
  class Mailbox < Celluloid::Mailbox
5
5
  attr_reader :reactor
6
6
 
7
- def initialize
7
+ def initialize(reactor = nil)
8
8
  @messages = []
9
- @lock = Mutex.new
10
- @reactor = Reactor.new
9
+ @mutex = Mutex.new
10
+ @reactor = reactor || Reactor.new
11
11
  end
12
12
 
13
13
  # Add a message to the Mailbox
14
14
  def <<(message)
15
- @lock.synchronize do
15
+ @mutex.lock
16
+ begin
16
17
  @messages << message
17
18
  @reactor.wakeup
19
+ rescue IOError
20
+ raise MailboxError, "dead recipient"
21
+ ensure @mutex.unlock
18
22
  end
19
23
  nil
20
- rescue IOError
21
- raise MailboxError, "dead recipient"
22
24
  end
23
25
 
24
26
  # Add a high-priority system event to the Mailbox
25
27
  def system_event(event)
26
- @lock.synchronize do
28
+ @mutex.lock
29
+ begin
27
30
  @messages.unshift event
28
-
29
- begin
30
- @reactor.wakeup
31
- rescue IOError
32
- # Silently fail if messages are sent to dead actors
33
- end
31
+ @reactor.wakeup
32
+ rescue IOError
33
+ # Silently fail if messages are sent to dead actors
34
+ ensure @mutex.unlock
34
35
  end
35
36
  nil
36
37
  end
@@ -25,29 +25,36 @@ module Celluloid
25
25
  def wait_writeable(io)
26
26
  wait io, :w
27
27
  end
28
-
28
+
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)
33
- if IO.respond_to? :try_convert
34
- io = IO.try_convert(io)
35
- elsif io.respond_to? :to_io
32
+ unless io.is_a?(::IO)
33
+ if io.respond_to? :to_io
36
34
  io = io.to_io
37
- else raise TypeError, "can't convert #{io.class} into IO"
35
+ elsif ::IO.respond_to? :try_convert
36
+ io = ::IO.try_convert(io)
38
37
  end
38
+
39
+ raise TypeError, "can't convert #{io.class} into IO" unless io.is_a? IO
39
40
  end
40
-
41
+
41
42
  monitor = @selector.register(io, set)
42
43
  monitor.value = Task.current
43
44
  Task.suspend :iowait
44
45
  end
45
-
46
+
46
47
  # Run the reactor, waiting for events or wakeup signal
47
48
  def run_once(timeout = nil)
48
- @selector.select_each(timeout) do |monitor|
49
- monitor.value.resume
49
+ @selector.select(timeout) do |monitor|
50
+ task = monitor.value
50
51
  @selector.deregister(monitor.io)
52
+
53
+ if task.running?
54
+ task.resume
55
+ else
56
+ Logger.warn("reactor attempted to resume a dead task")
57
+ end
51
58
  end
52
59
  end
53
60
  end
@@ -0,0 +1,40 @@
1
+ require 'socket'
2
+
3
+ module Celluloid
4
+ module IO
5
+ # TCPServer with combined blocking and evented support
6
+ class TCPServer
7
+ extend Forwardable
8
+ def_delegators :@server, :listen, :sysaccept, :close, :closed?
9
+
10
+ def initialize(hostname, port)
11
+ @server = ::TCPServer.new(hostname, port)
12
+ end
13
+
14
+ def accept
15
+ actor = Thread.current[:actor]
16
+
17
+ if evented?
18
+ Celluloid.current_actor.wait_readable @server
19
+ accept_nonblock
20
+ else
21
+ Celluloid::IO::TCPSocket.from_ruby_socket @server.accept
22
+ end
23
+ end
24
+
25
+ def accept_nonblock
26
+ Celluloid::IO::TCPSocket.from_ruby_socket @server.accept_nonblock
27
+ end
28
+
29
+ def to_io
30
+ @server
31
+ end
32
+
33
+ # Are we inside a Celluloid ::IO actor?
34
+ def evented?
35
+ actor = Thread.current[:actor]
36
+ actor && actor.mailbox.is_a?(Celluloid::IO::Mailbox)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,34 @@
1
+ require 'socket'
2
+
3
+ module Celluloid
4
+ module IO
5
+ # TCPSocket with combined blocking and evented support
6
+ class TCPSocket
7
+ include CommonMethods
8
+ extend Forwardable
9
+
10
+ def_delegators :@socket, :read_nonblock, :write_nonblock, :close, :closed?
11
+ def_delegators :@socket, :addr, :peeraddr
12
+
13
+ # Convert a Ruby TCPSocket into a Celluloid::IO::TCPSocket
14
+ def self.from_ruby_socket(ruby_socket)
15
+ # Some hax here, but whatever ;)
16
+ socket = allocate
17
+ socket.instance_variable_set(:@socket, ruby_socket)
18
+ socket
19
+ end
20
+
21
+ # Opens a TCP connection to remote_host on remote_port. If local_host
22
+ # and local_port are specified, then those parameters are used on the
23
+ # local end to establish the connection.
24
+ def initialize(remote_host, remote_port, local_host = nil, local_port = nil)
25
+ # FIXME: not using non-blocking connect
26
+ @socket = ::TCPSocket.new(remote_host, remote_port, local_host, local_port)
27
+ end
28
+
29
+ def to_io
30
+ @socket
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,48 @@
1
+ module Celluloid
2
+ module IO
3
+ # UDPSockets with combined blocking and evented support
4
+ class UDPSocket
5
+ extend Forwardable
6
+ def_delegators :@socket, :bind, :send, :recvfrom_nonblock, :close, :closed?
7
+
8
+ def initialize
9
+ @socket = ::UDPSocket.new
10
+ end
11
+
12
+ # Are we inside of a Celluloid::IO actor?
13
+ def evented?
14
+ actor = Thread.current[:actor]
15
+ actor && actor.mailbox.is_a?(Celluloid::IO::Mailbox)
16
+ end
17
+
18
+ # Wait until the socket is readable
19
+ def wait_readable
20
+ if evented?
21
+ Celluloid.current_actor.wait_readable(@socket)
22
+ else
23
+ Kernel.select([@socket])
24
+ end
25
+ end
26
+
27
+ # Receives up to maxlen bytes from socket. flags is zero or more of the
28
+ # MSG_ options. The first element of the results, mesg, is the data
29
+ # received. The second element, sender_addrinfo, contains
30
+ # protocol-specific address information of the sender.
31
+ def recvfrom(maxlen, flags = nil)
32
+ begin
33
+ if @socket.respond_to? :recvfrom_nonblock
34
+ @socket.recvfrom_nonblock(maxlen, flags)
35
+ else
36
+ # FIXME: hax for JRuby
37
+ @socket.recvfrom(maxlen, flags)
38
+ end
39
+ rescue ::IO::WaitReadable
40
+ wait_readable
41
+ retry
42
+ end
43
+ end
44
+
45
+ def to_io; @socket; end
46
+ end
47
+ end
48
+ end
@@ -1,5 +1,5 @@
1
1
  module Celluloid
2
2
  module IO
3
- VERSION = "0.8.0"
3
+ VERSION = "0.9.0"
4
4
  end
5
5
  end
data/logo.png ADDED
Binary file
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::IO::TCPServer do
4
+ describe "#accept" do
5
+ let(:payload) { 'ohai' }
6
+
7
+ context "inside Celluloid::IO" do
8
+ it "should be evented" do
9
+ with_tcp_server do |subject|
10
+ within_io_actor { subject.evented? }.should be_true
11
+ end
12
+ end
13
+
14
+ it "accepts a connection and returns a Celluloid::IO::TCPSocket" do
15
+ with_tcp_server do |subject|
16
+ thread = Thread.new { TCPSocket.new(example_addr, example_port) }
17
+ peer = within_io_actor { subject.accept }
18
+ peer.should be_a Celluloid::IO::TCPSocket
19
+
20
+ client = thread.value
21
+ client.write payload
22
+ peer.read(payload.size).should eq payload
23
+ end
24
+ end
25
+
26
+ context "elsewhere in Ruby" do
27
+ it "should be blocking" do
28
+ with_tcp_server do |subject|
29
+ subject.should_not be_evented
30
+ end
31
+ end
32
+
33
+ it "accepts a connection and returns a Celluloid::IO::TCPSocket" do
34
+ with_tcp_server do |subject|
35
+ thread = Thread.new { TCPSocket.new(example_addr, example_port) }
36
+ peer = subject.accept
37
+ peer.should be_a Celluloid::IO::TCPSocket
38
+
39
+ client = thread.value
40
+ client.write payload
41
+ peer.read(payload.size).should eq payload
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::IO::TCPSocket do
4
+ let(:payload) { 'ohai' }
5
+
6
+ context "inside Celluloid::IO" do
7
+ it "connects to TCP servers" do
8
+ server = ::TCPServer.new example_addr, example_port
9
+ thread = Thread.new { server.accept }
10
+ socket = within_io_actor { Celluloid::IO::TCPSocket.new example_addr, example_port }
11
+ peer = thread.value
12
+
13
+ peer << payload
14
+ within_io_actor { socket.read(payload.size) }.should eq payload
15
+
16
+ server.close
17
+ socket.close
18
+ peer.close
19
+ end
20
+
21
+ it "should be evented" do
22
+ with_connected_sockets do |subject|
23
+ within_io_actor { subject.evented? }.should be_true
24
+ end
25
+ end
26
+
27
+ it "reads data" do
28
+ with_connected_sockets do |subject, peer|
29
+ peer << payload
30
+ within_io_actor { subject.read(payload.size) }.should eq payload
31
+ end
32
+ end
33
+
34
+ it "reads partial data" do
35
+ with_connected_sockets do |subject, peer|
36
+ peer << payload * 2
37
+ within_io_actor { subject.readpartial(payload.size) }.should eq payload
38
+ end
39
+ end
40
+
41
+ it "writes data" do
42
+ with_connected_sockets do |subject, peer|
43
+ within_io_actor { subject << payload }
44
+ peer.read(payload.size).should eq payload
45
+ end
46
+ end
47
+ end
48
+
49
+ context "elsewhere in Ruby" do
50
+ it "connects to TCP servers" do
51
+ server = ::TCPServer.new example_addr, example_port
52
+ thread = Thread.new { server.accept }
53
+ socket = Celluloid::IO::TCPSocket.new example_addr, example_port
54
+ peer = thread.value
55
+
56
+ peer << payload
57
+ socket.read(payload.size).should eq payload
58
+
59
+ server.close
60
+ socket.close
61
+ peer.close
62
+ end
63
+
64
+ it "should be blocking" do
65
+ with_connected_sockets do |subject|
66
+ subject.should_not be_evented
67
+ end
68
+ end
69
+
70
+ it "reads data" do
71
+ with_connected_sockets do |subject, peer|
72
+ peer << payload
73
+ subject.read(payload.size).should eq payload
74
+ end
75
+ end
76
+
77
+ it "reads partial data" do
78
+ with_connected_sockets do |subject, peer|
79
+ peer << payload * 2
80
+ subject.readpartial(payload.size).should eq payload
81
+ end
82
+ end
83
+
84
+ it "writes data" do
85
+ with_connected_sockets do |subject, peer|
86
+ subject << payload
87
+ peer.read(payload.size).should eq payload
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::IO::UDPSocket do
4
+ let(:payload) { 'ohai' }
5
+ subject do
6
+ Celluloid::IO::UDPSocket.new.tap do |sock|
7
+ sock.bind example_addr, example_port
8
+ end
9
+ end
10
+
11
+ after { subject.close }
12
+
13
+ context "inside Celluloid::IO" do
14
+ it "should be evented" do
15
+ within_io_actor { subject.evented? }.should be_true
16
+ end
17
+
18
+ it "sends and receives packets" do
19
+ subject.send payload, 0, example_addr, example_port
20
+ subject.recvfrom(payload.size).first.should == payload
21
+ end
22
+ end
23
+
24
+ context "elsewhere in Ruby" do
25
+ it "should be blocking" do
26
+ subject.should_not be_evented
27
+ end
28
+
29
+ it "sends and receives packets" do
30
+ subject.send payload, 0, example_addr, example_port
31
+ subject.recvfrom(payload.size).first.should == payload
32
+ end
33
+ end
34
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,48 @@
1
1
  require 'rubygems'
2
2
  require 'bundler/setup'
3
3
  require 'celluloid/io'
4
- require 'celluloid/rspec'
4
+ require 'celluloid/rspec'
5
+
6
+ class ExampleActor
7
+ include Celluloid::IO
8
+
9
+ def wrap
10
+ yield
11
+ end
12
+ end
13
+
14
+ EXAMPLE_PORT = 10000 + rand(10000)
15
+
16
+ def example_addr; '127.0.0.1'; end
17
+ def example_port; EXAMPLE_PORT; end
18
+
19
+ def within_io_actor(&block)
20
+ actor = ExampleActor.new
21
+ actor.wrap(&block)
22
+ ensure
23
+ actor.terminate
24
+ end
25
+
26
+ def with_tcp_server
27
+ server = Celluloid::IO::TCPServer.new(example_addr, example_port)
28
+ begin
29
+ yield server
30
+ ensure
31
+ server.close
32
+ end
33
+ end
34
+
35
+ def with_connected_sockets
36
+ with_tcp_server do |server|
37
+ # FIXME: client isn't actually a Celluloid::IO::TCPSocket yet
38
+ client = ::TCPSocket.new(example_addr, example_port)
39
+ peer = server.accept
40
+
41
+ begin
42
+ yield peer, client
43
+ ensure
44
+ client.close
45
+ peer.close
46
+ end
47
+ end
48
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: celluloid-io
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,33 +9,33 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-24 00:00:00.000000000 Z
12
+ date: 2012-02-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: celluloid
16
- requirement: &70254645083180 !ruby/object:Gem::Requirement
16
+ requirement: &70287081256280 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 0.8.0
21
+ version: 0.9.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70254645083180
24
+ version_requirements: *70287081256280
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: nio4r
27
- requirement: &70254645082660 !ruby/object:Gem::Requirement
27
+ requirement: &70287081254940 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
31
31
  - !ruby/object:Gem::Version
32
- version: 0.2.2
32
+ version: 0.3.1
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70254645082660
35
+ version_requirements: *70287081254940
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rake
38
- requirement: &70254645082180 !ruby/object:Gem::Requirement
38
+ requirement: &70287081253700 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,18 +43,18 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70254645082180
46
+ version_requirements: *70287081253700
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rspec
49
- requirement: &70254645081480 !ruby/object:Gem::Requirement
49
+ requirement: &70287081251980 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
- - - ~>
52
+ - - ! '>='
53
53
  - !ruby/object:Gem::Version
54
- version: 2.7.0
54
+ version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70254645081480
57
+ version_requirements: *70287081251980
58
58
  description: Evented IO for Celluloid actors
59
59
  email:
60
60
  - tony.arcieri@gmail.com
@@ -71,12 +71,21 @@ files:
71
71
  - README.md
72
72
  - Rakefile
73
73
  - celluloid-io.gemspec
74
+ - examples/echo_server.rb
74
75
  - lib/celluloid/io.rb
76
+ - lib/celluloid/io/common_methods.rb
75
77
  - lib/celluloid/io/mailbox.rb
76
78
  - lib/celluloid/io/reactor.rb
79
+ - lib/celluloid/io/tcp_server.rb
80
+ - lib/celluloid/io/tcp_socket.rb
81
+ - lib/celluloid/io/udp_socket.rb
77
82
  - lib/celluloid/io/version.rb
83
+ - logo.png
78
84
  - spec/celluloid/io/actor_spec.rb
79
85
  - spec/celluloid/io/mailbox_spec.rb
86
+ - spec/celluloid/io/tcp_server_spec.rb
87
+ - spec/celluloid/io/tcp_socket_spec.rb
88
+ - spec/celluloid/io/udp_socket_spec.rb
80
89
  - spec/spec_helper.rb
81
90
  homepage: http://github.com/tarcieri/celluloid-io
82
91
  licenses: []
@@ -106,5 +115,8 @@ summary: Celluloid::IO allows you to monitor multiple IO objects within a Cellul
106
115
  test_files:
107
116
  - spec/celluloid/io/actor_spec.rb
108
117
  - spec/celluloid/io/mailbox_spec.rb
118
+ - spec/celluloid/io/tcp_server_spec.rb
119
+ - spec/celluloid/io/tcp_socket_spec.rb
120
+ - spec/celluloid/io/udp_socket_spec.rb
109
121
  - spec/spec_helper.rb
110
122
  has_rdoc: