celluloid-io 0.8.0 → 0.9.0

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
@@ -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: