celluloid-io 0.13.0.pre → 0.13.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- data/.coveralls.yml +1 -0
- data/.travis.yml +3 -0
- data/CHANGES.md +6 -2
- data/Gemfile +4 -5
- data/README.md +4 -4
- data/benchmarks/actor.rb +8 -5
- data/examples/echo_server.rb +4 -2
- data/examples/echo_unix_server.rb +5 -3
- data/lib/celluloid/io/ssl_server.rb +1 -1
- data/lib/celluloid/io/ssl_socket.rb +2 -2
- data/lib/celluloid/io/stream.rb +422 -0
- data/lib/celluloid/io/tcp_socket.rb +31 -7
- data/lib/celluloid/io/unix_socket.rb +15 -10
- data/lib/celluloid/io/version.rb +1 -1
- data/lib/celluloid/io.rb +1 -1
- data/spec/celluloid/io/ssl_socket_spec.rb +29 -9
- data/spec/celluloid/io/tcp_socket_spec.rb +5 -5
- data/spec/celluloid/io/unix_socket_spec.rb +4 -4
- data/spec/spec_helper.rb +9 -0
- metadata +5 -4
- data/lib/celluloid/io/common_methods.rb +0 -146
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
service-name: travis-pro
|
data/.travis.yml
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
script: rake ci
|
2
2
|
rvm:
|
3
3
|
- 1.9.3
|
4
|
+
- 2.0.0
|
4
5
|
- ruby-head
|
5
6
|
- jruby-19mode
|
6
7
|
- rbx-19mode
|
@@ -8,8 +9,10 @@ rvm:
|
|
8
9
|
|
9
10
|
matrix:
|
10
11
|
allow_failures:
|
12
|
+
- rvm: 2.0.0 # not ready for primetime :(
|
11
13
|
- rvm: ruby-head
|
12
14
|
- rvm: jruby-head
|
15
|
+
- rvm: rbx-19mode
|
13
16
|
|
14
17
|
notifications:
|
15
18
|
irc: "irc.freenode.org#celluloid"
|
data/CHANGES.md
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
-
0.13.0.
|
2
|
-
|
1
|
+
0.13.0.pre2
|
2
|
+
-----------
|
3
|
+
* Support for many, many more IO methods, particularly line-oriented
|
4
|
+
methods like #gets, #readline, and #readlines
|
3
5
|
* Initial SSL support via Celluloid::IO::SSLSocket and
|
4
6
|
Celluloid::IO::SSLServer
|
7
|
+
* Concurrent writes between tasks of the same actor are now coordinated
|
8
|
+
using Celluloid::Conditions instead of signals
|
5
9
|
* Celluloid 0.13 compatibility fixes
|
6
10
|
|
7
11
|
0.12.0
|
data/Gemfile
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
source
|
2
|
-
|
3
|
-
#gem 'celluloid', :git => 'git://github.com/celluloid/celluloid'
|
4
|
-
|
5
|
-
# Specify your gem's dependencies in celluloid-io.gemspec
|
1
|
+
source 'https://rubygems.org'
|
6
2
|
gemspec
|
3
|
+
|
4
|
+
gem 'coveralls', require: false
|
5
|
+
gem 'celluloid', github: 'celluloid/celluloid'
|
data/README.md
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
![Celluloid](https://github.com/celluloid/celluloid-io/raw/master/logo.png)
|
2
2
|
=============
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/celluloid-io.png)](http://rubygems.org/gems/celluloid-io)
|
3
4
|
[![Build Status](https://secure.travis-ci.org/celluloid/celluloid-io.png?branch=master)](http://travis-ci.org/celluloid/celluloid-io)
|
4
5
|
[![Dependency Status](https://gemnasium.com/celluloid/celluloid-io.png)](https://gemnasium.com/celluloid/celluloid-io)
|
5
|
-
[![Code Climate](https://codeclimate.com/
|
6
|
+
[![Code Climate](https://codeclimate.com/github/celluloid/celluloid-io.png)](https://codeclimate.com/github/celluloid/celluloid-io)
|
7
|
+
[![Coverage Status](https://coveralls.io/repos/celluloid/celluloid-io/badge.png?branch=master)](https://coveralls.io/r/celluloid/celluloid-io)
|
6
8
|
|
7
9
|
You don't have to choose between threaded and evented IO! Celluloid::IO
|
8
10
|
provides an event-driven IO system for building fast, scalable network
|
@@ -162,15 +164,13 @@ classes instead of the core Ruby TCPSocket and TCPServer classes.
|
|
162
164
|
Status
|
163
165
|
------
|
164
166
|
|
165
|
-
The rudiments of TCPServer and
|
167
|
+
The rudiments of TCPServer TCPSocket, and UNIXSocket are in place and ready to use. It is now
|
166
168
|
fully nonblocking, including DNS resolution, which effectively makes Celluloid::IO
|
167
169
|
feature complete as a nonblocking I/O system.
|
168
170
|
|
169
171
|
Basic UDPSocket support is in place. On JRuby, recvfrom makes a blocking call
|
170
172
|
as the underlying recvfrom_nonblock call is not supported by JRuby.
|
171
173
|
|
172
|
-
No UNIXSocket support yet, sorry (patches welcome!)
|
173
|
-
|
174
174
|
Contributing to Celluloid::IO
|
175
175
|
-----------------------------
|
176
176
|
|
data/benchmarks/actor.rb
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'bundler/setup'
|
5
|
-
require 'celluloid'
|
5
|
+
require 'celluloid/io'
|
6
6
|
require 'benchmark/ips'
|
7
7
|
|
8
8
|
class ExampleActor
|
9
9
|
include Celluloid::IO
|
10
10
|
|
11
11
|
def initialize
|
12
|
-
@condition = Condition.new
|
12
|
+
@condition = Celluloid::Condition.new
|
13
13
|
end
|
14
14
|
|
15
15
|
def example_method; end
|
@@ -24,13 +24,13 @@ class ExampleActor
|
|
24
24
|
end
|
25
25
|
|
26
26
|
example_actor = ExampleActor.new
|
27
|
-
mailbox = Celluloid::Mailbox.new
|
27
|
+
mailbox = Celluloid::IO::Mailbox.new
|
28
28
|
|
29
29
|
latch_in, latch_out = Queue.new, Queue.new
|
30
30
|
latch = Thread.new do
|
31
31
|
while true
|
32
32
|
n = latch_in.pop
|
33
|
-
for i in 0
|
33
|
+
for i in 0...n; mailbox.receive; end
|
34
34
|
latch_out << :done
|
35
35
|
end
|
36
36
|
end
|
@@ -49,9 +49,12 @@ Benchmark.ips do |ips|
|
|
49
49
|
waiter.value
|
50
50
|
end
|
51
51
|
|
52
|
+
# Deadlocking o_O
|
53
|
+
=begin
|
52
54
|
ips.report("messages") do |n|
|
53
55
|
latch_in << n
|
54
|
-
for i in 0
|
56
|
+
for i in 0...n; mailbox << :message; end
|
55
57
|
latch_out.pop
|
56
58
|
end
|
59
|
+
=end
|
57
60
|
end
|
data/examples/echo_server.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Run this as: bundle exec examples/echo_server.rb
|
2
4
|
|
3
5
|
require 'bundler/setup'
|
4
6
|
require 'celluloid/io'
|
@@ -12,7 +14,7 @@ class EchoServer
|
|
12
14
|
# Since we included Celluloid::IO, we're actually making a
|
13
15
|
# Celluloid::IO::TCPServer here
|
14
16
|
@server = TCPServer.new(host, port)
|
15
|
-
run
|
17
|
+
async.run
|
16
18
|
end
|
17
19
|
|
18
20
|
def finalize
|
@@ -20,7 +22,7 @@ class EchoServer
|
|
20
22
|
end
|
21
23
|
|
22
24
|
def run
|
23
|
-
loop { handle_connection
|
25
|
+
loop { async.handle_connection @server.accept }
|
24
26
|
end
|
25
27
|
|
26
28
|
def handle_connection(socket)
|
@@ -10,10 +10,11 @@ class EchoUNIXServer
|
|
10
10
|
puts "*** start server #{socket_path}"
|
11
11
|
@socket_path = socket_path
|
12
12
|
@server = UNIXServer.open(socket_path)
|
13
|
+
async.run
|
13
14
|
end
|
14
15
|
|
15
16
|
def run
|
16
|
-
loop { handle_connection
|
17
|
+
loop { async.handle_connection @server.accept }
|
17
18
|
end
|
18
19
|
|
19
20
|
def handle_connection(socket)
|
@@ -39,5 +40,6 @@ class EchoUNIXServer
|
|
39
40
|
|
40
41
|
end
|
41
42
|
|
42
|
-
|
43
|
-
|
43
|
+
supervisor = EchoUNIXServer.supervise("/tmp/sock_test")
|
44
|
+
trap("INT") { supervisor.terminate; exit }
|
45
|
+
sleep
|
@@ -3,14 +3,14 @@ require 'openssl'
|
|
3
3
|
module Celluloid
|
4
4
|
module IO
|
5
5
|
# SSLSocket with Celluloid::IO support
|
6
|
-
class SSLSocket
|
7
|
-
include CommonMethods
|
6
|
+
class SSLSocket < Stream
|
8
7
|
extend Forwardable
|
9
8
|
|
10
9
|
def_delegators :@socket, :read_nonblock, :write_nonblock, :close, :closed?,
|
11
10
|
:cert, :cipher, :client_ca, :peer_cert, :peer_cert_chain, :verify_result
|
12
11
|
|
13
12
|
def initialize(io, ctx = OpenSSL::SSL::SSLContext.new)
|
13
|
+
super()
|
14
14
|
@context = ctx
|
15
15
|
@socket = OpenSSL::SSL::SSLSocket.new(::IO.try_convert(io), @context)
|
16
16
|
end
|
@@ -0,0 +1,422 @@
|
|
1
|
+
# Partially adapted from Ruby's OpenSSL::Buffering
|
2
|
+
# Originally from the 'OpenSSL for Ruby 2' project
|
3
|
+
# Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org>
|
4
|
+
# All rights reserved.
|
5
|
+
#
|
6
|
+
# This program is licenced under the same licence as Ruby.
|
7
|
+
|
8
|
+
module Celluloid
|
9
|
+
module IO
|
10
|
+
# Base class of all streams in Celluloid::IO
|
11
|
+
class Stream
|
12
|
+
include Enumerable
|
13
|
+
|
14
|
+
# The "sync mode" of the stream
|
15
|
+
#
|
16
|
+
# See IO#sync for full details.
|
17
|
+
attr_accessor :sync
|
18
|
+
|
19
|
+
# Default size to read from or write to the stream for buffer operations
|
20
|
+
BLOCK_SIZE = 1024*16
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@eof = false
|
24
|
+
@sync = true # FIXME: hax
|
25
|
+
@read_buffer = ''.force_encoding(Encoding::ASCII_8BIT)
|
26
|
+
@write_buffer = ''.force_encoding(Encoding::ASCII_8BIT)
|
27
|
+
|
28
|
+
@read_latch = Latch.new
|
29
|
+
@write_latch = Latch.new
|
30
|
+
end
|
31
|
+
|
32
|
+
# Are we inside of a Celluloid::IO actor?
|
33
|
+
def evented?
|
34
|
+
actor = Thread.current[:celluloid_actor]
|
35
|
+
actor && actor.mailbox.is_a?(Celluloid::IO::Mailbox)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Wait until the current object is readable
|
39
|
+
def wait_readable
|
40
|
+
if evented?
|
41
|
+
Celluloid.current_actor.wait_readable(self.to_io)
|
42
|
+
else
|
43
|
+
Kernel.select([self.to_io])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Wait until the current object is writable
|
48
|
+
def wait_writable
|
49
|
+
if evented?
|
50
|
+
Celluloid.current_actor.wait_writable(self.to_io)
|
51
|
+
else
|
52
|
+
Kernel.select([], [self.to_io])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# System read via the nonblocking subsystem
|
57
|
+
def sysread(length = nil, buffer = nil)
|
58
|
+
buffer ||= ''.force_encoding(Encoding::ASCII_8BIT)
|
59
|
+
|
60
|
+
@read_latch.synchronize do
|
61
|
+
begin
|
62
|
+
read_nonblock(length, buffer)
|
63
|
+
rescue ::IO::WaitReadable
|
64
|
+
wait_readable
|
65
|
+
retry
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
buffer
|
70
|
+
end
|
71
|
+
|
72
|
+
# System write via the nonblocking subsystem
|
73
|
+
def syswrite(string)
|
74
|
+
length = string.length
|
75
|
+
total_written = 0
|
76
|
+
|
77
|
+
remaining = string
|
78
|
+
|
79
|
+
@write_latch.synchronize do
|
80
|
+
while total_written < length
|
81
|
+
begin
|
82
|
+
written = write_nonblock(remaining)
|
83
|
+
rescue ::IO::WaitWritable
|
84
|
+
wait_writable
|
85
|
+
retry
|
86
|
+
rescue EOFError
|
87
|
+
return total_written
|
88
|
+
end
|
89
|
+
|
90
|
+
total_written += written
|
91
|
+
|
92
|
+
# FIXME: mutating the original buffer here. Seems bad.
|
93
|
+
remaining.slice!(0, written) if written < remaining.length
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
total_written
|
98
|
+
end
|
99
|
+
|
100
|
+
# Reads +size+ bytes from the stream. If +buf+ is provided it must
|
101
|
+
# reference a string which will receive the data.
|
102
|
+
#
|
103
|
+
# See IO#read for full details.
|
104
|
+
def read(size=nil, buf=nil)
|
105
|
+
if size == 0
|
106
|
+
if buf
|
107
|
+
buf.clear
|
108
|
+
return buf
|
109
|
+
else
|
110
|
+
return ""
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
until @eof
|
115
|
+
break if size && size <= @read_buffer.size
|
116
|
+
fill_rbuff
|
117
|
+
break unless size
|
118
|
+
end
|
119
|
+
|
120
|
+
ret = consume_rbuff(size) || ""
|
121
|
+
|
122
|
+
if buf
|
123
|
+
buf.replace(ret)
|
124
|
+
ret = buf
|
125
|
+
end
|
126
|
+
|
127
|
+
(size && ret.empty?) ? nil : ret
|
128
|
+
end
|
129
|
+
|
130
|
+
# Reads at most +maxlen+ bytes from the stream. If +buf+ is provided it
|
131
|
+
# must reference a string which will receive the data.
|
132
|
+
#
|
133
|
+
# See IO#readpartial for full details.
|
134
|
+
def readpartial(maxlen, buf=nil)
|
135
|
+
if maxlen == 0
|
136
|
+
if buf
|
137
|
+
buf.clear
|
138
|
+
return buf
|
139
|
+
else
|
140
|
+
return ""
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
if @read_buffer.empty?
|
145
|
+
begin
|
146
|
+
return sysread(maxlen, buf)
|
147
|
+
rescue Errno::EAGAIN
|
148
|
+
retry
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
ret = consume_rbuff(maxlen)
|
153
|
+
|
154
|
+
if buf
|
155
|
+
buf.replace(ret)
|
156
|
+
ret = buf
|
157
|
+
end
|
158
|
+
|
159
|
+
raise EOFError if ret.empty?
|
160
|
+
ret
|
161
|
+
end
|
162
|
+
|
163
|
+
# Reads the next "line+ from the stream. Lines are separated by +eol+. If
|
164
|
+
# +limit+ is provided the result will not be longer than the given number of
|
165
|
+
# bytes.
|
166
|
+
#
|
167
|
+
# +eol+ may be a String or Regexp.
|
168
|
+
#
|
169
|
+
# Unlike IO#gets the line read will not be assigned to +$_+.
|
170
|
+
#
|
171
|
+
# Unlike IO#gets the separator must be provided if a limit is provided.
|
172
|
+
def gets(eol=$/, limit=nil)
|
173
|
+
idx = @read_buffer.index(eol)
|
174
|
+
|
175
|
+
until @eof
|
176
|
+
break if idx
|
177
|
+
fill_rbuff
|
178
|
+
idx = @read_buffer.index(eol)
|
179
|
+
end
|
180
|
+
|
181
|
+
if eol.is_a?(Regexp)
|
182
|
+
size = idx ? idx+$&.size : nil
|
183
|
+
else
|
184
|
+
size = idx ? idx+eol.size : nil
|
185
|
+
end
|
186
|
+
|
187
|
+
if limit and limit >= 0
|
188
|
+
size = [size, limit].min
|
189
|
+
end
|
190
|
+
|
191
|
+
consume_rbuff(size)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Executes the block for every line in the stream where lines are separated
|
195
|
+
# by +eol+.
|
196
|
+
#
|
197
|
+
# See also #gets
|
198
|
+
def each(eol=$/)
|
199
|
+
while line = self.gets(eol)
|
200
|
+
yield line
|
201
|
+
end
|
202
|
+
end
|
203
|
+
alias each_line each
|
204
|
+
|
205
|
+
# Reads lines from the stream which are separated by +eol+.
|
206
|
+
#
|
207
|
+
# See also #gets
|
208
|
+
def readlines(eol=$/)
|
209
|
+
ary = []
|
210
|
+
ary << line while line = self.gets(eol)
|
211
|
+
ary
|
212
|
+
end
|
213
|
+
|
214
|
+
# Reads a line from the stream which is separated by +eol+.
|
215
|
+
#
|
216
|
+
# Raises EOFError if at end of file.
|
217
|
+
def readline(eol=$/)
|
218
|
+
raise EOFError if eof?
|
219
|
+
gets(eol)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Reads one character from the stream. Returns nil if called at end of
|
223
|
+
# file.
|
224
|
+
def getc
|
225
|
+
read(1)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Calls the given block once for each byte in the stream.
|
229
|
+
def each_byte # :yields: byte
|
230
|
+
yield(c.ord) while c = getc
|
231
|
+
end
|
232
|
+
|
233
|
+
# Reads a one-character string from the stream. Raises an EOFError at end
|
234
|
+
# of file.
|
235
|
+
def readchar
|
236
|
+
raise EOFError if eof?
|
237
|
+
getc
|
238
|
+
end
|
239
|
+
|
240
|
+
# Pushes character +c+ back onto the stream such that a subsequent buffered
|
241
|
+
# character read will return it.
|
242
|
+
#
|
243
|
+
# Unlike IO#getc multiple bytes may be pushed back onto the stream.
|
244
|
+
#
|
245
|
+
# Has no effect on unbuffered reads (such as #sysread).
|
246
|
+
def ungetc(c)
|
247
|
+
@read_buffer[0,0] = c.chr
|
248
|
+
end
|
249
|
+
|
250
|
+
# Returns true if the stream is at file which means there is no more data to
|
251
|
+
# be read.
|
252
|
+
def eof?
|
253
|
+
fill_rbuff if !@eof && @read_buffer.empty?
|
254
|
+
@eof && @read_buffer.empty?
|
255
|
+
end
|
256
|
+
alias eof eof?
|
257
|
+
|
258
|
+
# Writes +s+ to the stream. If the argument is not a string it will be
|
259
|
+
# converted using String#to_s. Returns the number of bytes written.
|
260
|
+
def write(s)
|
261
|
+
do_write(s)
|
262
|
+
s.bytesize
|
263
|
+
end
|
264
|
+
|
265
|
+
# Writes +s+ to the stream. +s+ will be converted to a String using
|
266
|
+
# String#to_s.
|
267
|
+
def << (s)
|
268
|
+
do_write(s)
|
269
|
+
self
|
270
|
+
end
|
271
|
+
|
272
|
+
# Writes +args+ to the stream along with a record separator.
|
273
|
+
#
|
274
|
+
# See IO#puts for full details.
|
275
|
+
def puts(*args)
|
276
|
+
s = ""
|
277
|
+
if args.empty?
|
278
|
+
s << "\n"
|
279
|
+
end
|
280
|
+
|
281
|
+
args.each do |arg|
|
282
|
+
s << arg.to_s
|
283
|
+
if $/ && /\n\z/ !~ s
|
284
|
+
s << "\n"
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
do_write(s)
|
289
|
+
nil
|
290
|
+
end
|
291
|
+
|
292
|
+
# Writes +args+ to the stream.
|
293
|
+
#
|
294
|
+
# See IO#print for full details.
|
295
|
+
def print(*args)
|
296
|
+
s = ""
|
297
|
+
args.each { |arg| s << arg.to_s }
|
298
|
+
do_write(s)
|
299
|
+
nil
|
300
|
+
end
|
301
|
+
|
302
|
+
# Formats and writes to the stream converting parameters under control of
|
303
|
+
# the format string.
|
304
|
+
#
|
305
|
+
# See Kernel#sprintf for format string details.
|
306
|
+
def printf(s, *args)
|
307
|
+
do_write(s % args)
|
308
|
+
nil
|
309
|
+
end
|
310
|
+
|
311
|
+
# Flushes buffered data to the stream.
|
312
|
+
def flush
|
313
|
+
osync = @sync
|
314
|
+
@sync = true
|
315
|
+
do_write ""
|
316
|
+
return self
|
317
|
+
ensure
|
318
|
+
@sync = osync
|
319
|
+
end
|
320
|
+
|
321
|
+
# Closes the stream and flushes any unwritten data.
|
322
|
+
def close
|
323
|
+
flush rescue nil
|
324
|
+
sysclose
|
325
|
+
end
|
326
|
+
|
327
|
+
#######
|
328
|
+
private
|
329
|
+
#######
|
330
|
+
|
331
|
+
# Fills the buffer from the underlying stream
|
332
|
+
def fill_rbuff
|
333
|
+
begin
|
334
|
+
@read_buffer << sysread(BLOCK_SIZE)
|
335
|
+
rescue Errno::EAGAIN
|
336
|
+
retry
|
337
|
+
rescue EOFError
|
338
|
+
@eof = true
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# Consumes +size+ bytes from the buffer
|
343
|
+
def consume_rbuff(size=nil)
|
344
|
+
if @read_buffer.empty?
|
345
|
+
nil
|
346
|
+
else
|
347
|
+
size = @read_buffer.size unless size
|
348
|
+
ret = @read_buffer[0, size]
|
349
|
+
@read_buffer[0, size] = ""
|
350
|
+
ret
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# Writes +s+ to the buffer. When the buffer is full or #sync is true the
|
355
|
+
# buffer is flushed to the underlying stream.
|
356
|
+
def do_write(s)
|
357
|
+
@write_buffer << s
|
358
|
+
@write_buffer.force_encoding(Encoding::BINARY)
|
359
|
+
@sync ||= false
|
360
|
+
|
361
|
+
if @sync or @write_buffer.size > BLOCK_SIZE or idx = @write_buffer.rindex($/)
|
362
|
+
remain = idx ? idx + $/.size : @write_buffer.length
|
363
|
+
nwritten = 0
|
364
|
+
|
365
|
+
while remain > 0
|
366
|
+
str = @write_buffer[nwritten,remain]
|
367
|
+
begin
|
368
|
+
nwrote = syswrite(str)
|
369
|
+
rescue Errno::EAGAIN
|
370
|
+
retry
|
371
|
+
end
|
372
|
+
remain -= nwrote
|
373
|
+
nwritten += nwrote
|
374
|
+
end
|
375
|
+
|
376
|
+
@write_buffer[0,nwritten] = ""
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
# Perform an operation exclusively, uncontested by other tasks
|
381
|
+
class Latch
|
382
|
+
def initialize
|
383
|
+
@owner = nil
|
384
|
+
@waiters = 0
|
385
|
+
@condition = Celluloid::Condition.new
|
386
|
+
end
|
387
|
+
|
388
|
+
# Synchronize an operation across all tasks in the current actor
|
389
|
+
def synchronize
|
390
|
+
actor = Thread.current[:celluloid_actor]
|
391
|
+
return yield unless actor
|
392
|
+
|
393
|
+
# Silently acquire ownership at the actor level. This method should be
|
394
|
+
# replaced with an ownership system similar to conditions if this code
|
395
|
+
# is ever extracted into Celluloid itself
|
396
|
+
if @condition.owner != actor.proxy
|
397
|
+
@condition.owner = actor.proxy
|
398
|
+
@waiters = 0
|
399
|
+
@owner = nil
|
400
|
+
end
|
401
|
+
|
402
|
+
if @owner || @waiters > 0
|
403
|
+
@waiters += 1
|
404
|
+
@condition.wait
|
405
|
+
@waiters -= 1
|
406
|
+
end
|
407
|
+
|
408
|
+
@owner = Task.current
|
409
|
+
|
410
|
+
begin
|
411
|
+
ret = yield
|
412
|
+
ensure
|
413
|
+
@owner = nil
|
414
|
+
@condition.signal if @waiters > 0
|
415
|
+
end
|
416
|
+
|
417
|
+
ret
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
@@ -4,25 +4,49 @@ require 'resolv'
|
|
4
4
|
module Celluloid
|
5
5
|
module IO
|
6
6
|
# TCPSocket with combined blocking and evented support
|
7
|
-
class TCPSocket
|
8
|
-
include CommonMethods
|
7
|
+
class TCPSocket < Stream
|
9
8
|
extend Forwardable
|
10
9
|
|
11
10
|
def_delegators :@socket, :read_nonblock, :write_nonblock, :close, :closed?
|
12
11
|
def_delegators :@socket, :addr, :peeraddr, :setsockopt
|
13
12
|
|
13
|
+
# Open a TCP socket, yielding it to the given block and closing it
|
14
|
+
# automatically when done (if a block is given)
|
15
|
+
def self.open(*args, &block)
|
16
|
+
sock = new(*args)
|
17
|
+
|
18
|
+
if block_given?
|
19
|
+
begin
|
20
|
+
yield sock
|
21
|
+
ensure
|
22
|
+
sock.close
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
sock
|
27
|
+
end
|
28
|
+
|
14
29
|
# Convert a Ruby TCPSocket into a Celluloid::IO::TCPSocket
|
30
|
+
# DEPRECATED: to be removed in a future release
|
15
31
|
def self.from_ruby_socket(ruby_socket)
|
16
|
-
|
17
|
-
socket = allocate
|
18
|
-
socket.instance_variable_set(:@socket, ruby_socket)
|
19
|
-
socket
|
32
|
+
new(ruby_socket)
|
20
33
|
end
|
21
34
|
|
22
35
|
# Opens a TCP connection to remote_host on remote_port. If local_host
|
23
36
|
# and local_port are specified, then those parameters are used on the
|
24
37
|
# local end to establish the connection.
|
25
|
-
def initialize(remote_host, remote_port, local_host = nil, local_port = nil)
|
38
|
+
def initialize(remote_host, remote_port = nil, local_host = nil, local_port = nil)
|
39
|
+
super()
|
40
|
+
|
41
|
+
# Allow users to pass in a Ruby TCPSocket directly
|
42
|
+
if remote_host.is_a? ::TCPSocket
|
43
|
+
@addr = nil
|
44
|
+
@socket = remote_host
|
45
|
+
return
|
46
|
+
elsif remote_port.nil?
|
47
|
+
raise ArgumentError, "wrong number of arguments (1 for 2)"
|
48
|
+
end
|
49
|
+
|
26
50
|
# Is it an IPv4 address?
|
27
51
|
begin
|
28
52
|
@addr = Resolv::IPv4.create(remote_host)
|
@@ -3,27 +3,32 @@ require 'socket'
|
|
3
3
|
module Celluloid
|
4
4
|
module IO
|
5
5
|
# UNIXSocket with combined blocking and evented support
|
6
|
-
class UNIXSocket
|
7
|
-
include CommonMethods
|
6
|
+
class UNIXSocket < Stream
|
8
7
|
extend Forwardable
|
9
8
|
|
10
9
|
def_delegators :@socket, :read_nonblock, :write_nonblock, :close, :closed?, :readline, :puts, :addr
|
11
10
|
|
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
11
|
# Open a UNIX connection.
|
21
12
|
def self.open(socket_path, &block)
|
22
13
|
self.new(socket_path, &block)
|
23
14
|
end
|
24
15
|
|
16
|
+
# Convert a Ruby UNIXSocket into a Celluloid::IO::UNIXSocket
|
17
|
+
# DEPRECATED: to be removed in a future release
|
18
|
+
def self.from_ruby_socket(ruby_socket)
|
19
|
+
new(ruby_socket)
|
20
|
+
end
|
21
|
+
|
25
22
|
# Open a UNIX connection.
|
26
23
|
def initialize(socket_path, &block)
|
24
|
+
super()
|
25
|
+
|
26
|
+
# Allow users to pass in a Ruby UNIXSocket directly
|
27
|
+
if socket_path.is_a? ::UNIXSocket
|
28
|
+
@socket = socket_path
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
27
32
|
# FIXME: not doing non-blocking connect
|
28
33
|
@socket = if block
|
29
34
|
::UNIXSocket.open(socket_path, &block)
|
data/lib/celluloid/io/version.rb
CHANGED
data/lib/celluloid/io.rb
CHANGED
@@ -2,10 +2,10 @@ require 'forwardable'
|
|
2
2
|
require 'celluloid/io/version'
|
3
3
|
|
4
4
|
require 'celluloid'
|
5
|
-
require 'celluloid/io/common_methods'
|
6
5
|
require 'celluloid/io/dns_resolver'
|
7
6
|
require 'celluloid/io/mailbox'
|
8
7
|
require 'celluloid/io/reactor'
|
8
|
+
require 'celluloid/io/stream'
|
9
9
|
|
10
10
|
require 'celluloid/io/tcp_server'
|
11
11
|
require 'celluloid/io/tcp_socket'
|
@@ -14,7 +14,23 @@ describe Celluloid::IO::SSLSocket do
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
let(:client)
|
17
|
+
let(:client) do
|
18
|
+
remaining_attempts = 3
|
19
|
+
|
20
|
+
begin
|
21
|
+
TCPSocket.new example_addr, example_ssl_port
|
22
|
+
rescue Errno::ECONNREFUSED
|
23
|
+
# HAX: sometimes this fails to connect? o_O
|
24
|
+
# This is quite likely due to the Thread.pass style spinlocks for startup
|
25
|
+
raise if remaining_attempts < 1
|
26
|
+
remaining_attempts -= 1
|
27
|
+
|
28
|
+
# Seems gimpy, but sleep and retry
|
29
|
+
sleep 0.1
|
30
|
+
retry
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
18
34
|
let(:ssl_client) { Celluloid::IO::SSLSocket.new client, client_context }
|
19
35
|
|
20
36
|
let(:server_cert) { OpenSSL::X509::Certificate.new fixture_dir.join("server.crt").read }
|
@@ -31,6 +47,7 @@ describe Celluloid::IO::SSLSocket do
|
|
31
47
|
let(:server_thread) do
|
32
48
|
Thread.new { ssl_server.accept }.tap do |thread|
|
33
49
|
Thread.pass while thread.status && thread.status != "sleep"
|
50
|
+
thread.join unless thread.status
|
34
51
|
end
|
35
52
|
end
|
36
53
|
|
@@ -38,6 +55,7 @@ describe Celluloid::IO::SSLSocket do
|
|
38
55
|
let(:raw_server_thread) do
|
39
56
|
Thread.new { celluloid_server.accept }.tap do |thread|
|
40
57
|
Thread.pass while thread.status && thread.status != "sleep"
|
58
|
+
thread.join unless thread.status
|
41
59
|
end
|
42
60
|
end
|
43
61
|
|
@@ -160,13 +178,14 @@ describe Celluloid::IO::SSLSocket do
|
|
160
178
|
end
|
161
179
|
|
162
180
|
def with_ssl_sockets
|
163
|
-
|
181
|
+
server_thread
|
164
182
|
ssl_client.connect
|
165
183
|
|
166
184
|
begin
|
167
|
-
ssl_peer =
|
185
|
+
ssl_peer = server_thread.value
|
168
186
|
yield ssl_client, ssl_peer
|
169
187
|
ensure
|
188
|
+
server_thread.join
|
170
189
|
ssl_server.close
|
171
190
|
ssl_client.close
|
172
191
|
ssl_peer.close
|
@@ -174,16 +193,17 @@ describe Celluloid::IO::SSLSocket do
|
|
174
193
|
end
|
175
194
|
|
176
195
|
def with_raw_sockets
|
177
|
-
|
178
|
-
|
196
|
+
raw_server_thread
|
197
|
+
client
|
179
198
|
|
180
199
|
begin
|
181
|
-
|
182
|
-
yield
|
200
|
+
peer = raw_server_thread.value
|
201
|
+
yield client, peer
|
183
202
|
ensure
|
203
|
+
raw_server_thread.join
|
184
204
|
celluloid_server.close
|
185
|
-
|
186
|
-
|
205
|
+
client.close
|
206
|
+
peer.close
|
187
207
|
end
|
188
208
|
end
|
189
209
|
end
|
@@ -45,10 +45,10 @@ describe Celluloid::IO::TCPSocket do
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
-
it "reads data in
|
48
|
+
it "reads data in binary encoding" do
|
49
49
|
with_connected_sockets do |subject, peer|
|
50
50
|
peer << payload
|
51
|
-
within_io_actor { subject.read(payload.size).encoding }.should eq Encoding::
|
51
|
+
within_io_actor { subject.read(payload.size).encoding }.should eq Encoding::BINARY
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
@@ -59,10 +59,10 @@ describe Celluloid::IO::TCPSocket do
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
-
it "reads partial data in
|
62
|
+
it "reads partial data in binary encoding" do
|
63
63
|
with_connected_sockets do |subject, peer|
|
64
64
|
peer << payload * 2
|
65
|
-
within_io_actor { subject.readpartial(payload.size).encoding }.should eq Encoding::
|
65
|
+
within_io_actor { subject.readpartial(payload.size).encoding }.should eq Encoding::BINARY
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
@@ -90,7 +90,7 @@ describe Celluloid::IO::TCPSocket do
|
|
90
90
|
end
|
91
91
|
|
92
92
|
it "raises IOError when active sockets are closed across threads" do
|
93
|
-
|
93
|
+
pending "not implemented"
|
94
94
|
|
95
95
|
with_connected_sockets do |subject, peer|
|
96
96
|
actor = ExampleActor.new
|
@@ -50,10 +50,10 @@ describe Celluloid::IO::UNIXSocket do
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
-
it "reads data in
|
53
|
+
it "reads data in binary encoding" do
|
54
54
|
with_connected_unix_sockets do |subject, peer|
|
55
55
|
peer << payload
|
56
|
-
within_io_actor { subject.read(payload.size).encoding }.should eq Encoding::
|
56
|
+
within_io_actor { subject.read(payload.size).encoding }.should eq Encoding::BINARY
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
@@ -64,10 +64,10 @@ describe Celluloid::IO::UNIXSocket do
|
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
|
-
it "reads partial data in
|
67
|
+
it "reads partial data in binary encoding" do
|
68
68
|
with_connected_unix_sockets do |subject, peer|
|
69
69
|
peer << payload * 2
|
70
|
-
within_io_actor { subject.readpartial(payload.size).encoding }.should eq Encoding::
|
70
|
+
within_io_actor { subject.readpartial(payload.size).encoding }.should eq Encoding::BINARY
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
data/spec/spec_helper.rb
CHANGED
@@ -2,10 +2,19 @@ require 'rubygems'
|
|
2
2
|
require 'bundler/setup'
|
3
3
|
require 'celluloid/io'
|
4
4
|
require 'celluloid/rspec'
|
5
|
+
require 'coveralls'
|
6
|
+
Coveralls.wear!
|
5
7
|
|
6
8
|
logfile = File.open(File.expand_path("../../log/test.log", __FILE__), 'a')
|
7
9
|
Celluloid.logger = Logger.new(logfile)
|
8
10
|
|
11
|
+
# FIXME: Hax until test termination can be cleaned up
|
12
|
+
module Celluloid
|
13
|
+
class << self
|
14
|
+
def shutdown; end # hax: noop!
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
9
18
|
class ExampleActor
|
10
19
|
include Celluloid::IO
|
11
20
|
|
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.13.0.
|
4
|
+
version: 0.13.0.pre2
|
5
5
|
prerelease: 7
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-03-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: celluloid
|
@@ -130,6 +130,7 @@ executables: []
|
|
130
130
|
extensions: []
|
131
131
|
extra_rdoc_files: []
|
132
132
|
files:
|
133
|
+
- .coveralls.yml
|
133
134
|
- .gitignore
|
134
135
|
- .rspec
|
135
136
|
- .travis.yml
|
@@ -146,12 +147,12 @@ files:
|
|
146
147
|
- examples/echo_unix_client.rb
|
147
148
|
- examples/echo_unix_server.rb
|
148
149
|
- lib/celluloid/io.rb
|
149
|
-
- lib/celluloid/io/common_methods.rb
|
150
150
|
- lib/celluloid/io/dns_resolver.rb
|
151
151
|
- lib/celluloid/io/mailbox.rb
|
152
152
|
- lib/celluloid/io/reactor.rb
|
153
153
|
- lib/celluloid/io/ssl_server.rb
|
154
154
|
- lib/celluloid/io/ssl_socket.rb
|
155
|
+
- lib/celluloid/io/stream.rb
|
155
156
|
- lib/celluloid/io/tcp_server.rb
|
156
157
|
- lib/celluloid/io/tcp_socket.rb
|
157
158
|
- lib/celluloid/io/udp_socket.rb
|
@@ -191,7 +192,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
191
192
|
version: '0'
|
192
193
|
segments:
|
193
194
|
- 0
|
194
|
-
hash: -
|
195
|
+
hash: -1245022308244925467
|
195
196
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
196
197
|
none: false
|
197
198
|
requirements:
|
@@ -1,146 +0,0 @@
|
|
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[:celluloid_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
|
-
if evented?
|
23
|
-
Celluloid.current_actor.wait_writable(self.to_io)
|
24
|
-
else
|
25
|
-
Kernel.select([], [self.to_io])
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# Request exclusive control for a particular operation
|
30
|
-
# Type should be one of :r (read) or :w (write)
|
31
|
-
def acquire_ownership(type)
|
32
|
-
return unless Thread.current[:celluloid_actor]
|
33
|
-
|
34
|
-
case type
|
35
|
-
when :r
|
36
|
-
ivar = :@read_owner
|
37
|
-
when :w
|
38
|
-
ivar = :@write_owner
|
39
|
-
else raise ArgumentError, "invalid ownership type: #{type}"
|
40
|
-
end
|
41
|
-
|
42
|
-
# Celluloid needs a better API here o_O
|
43
|
-
Thread.current[:celluloid_actor].wait(self) while instance_variable_defined?(ivar) && instance_variable_get(ivar)
|
44
|
-
instance_variable_set(ivar, Task.current)
|
45
|
-
end
|
46
|
-
|
47
|
-
# Release ownership for a particular operation
|
48
|
-
# Type should be one of :r (read) or :w (write)
|
49
|
-
def release_ownership(type)
|
50
|
-
return unless Thread.current[:celluloid_actor]
|
51
|
-
|
52
|
-
case type
|
53
|
-
when :r
|
54
|
-
ivar = :@read_owner
|
55
|
-
when :w
|
56
|
-
ivar = :@write_owner
|
57
|
-
else raise ArgumentError, "invalid ownership type: #{type}"
|
58
|
-
end
|
59
|
-
|
60
|
-
raise "not owner" unless instance_variable_defined?(ivar) && instance_variable_get(ivar) == Task.current
|
61
|
-
instance_variable_set(ivar, nil)
|
62
|
-
Thread.current[:celluloid_actor].signal(self)
|
63
|
-
end
|
64
|
-
|
65
|
-
def read(length = nil, buffer = nil)
|
66
|
-
buffer ||= ''.force_encoding(Encoding::ASCII_8BIT)
|
67
|
-
remaining = length
|
68
|
-
|
69
|
-
acquire_ownership :r
|
70
|
-
begin
|
71
|
-
if length
|
72
|
-
until remaining.zero?
|
73
|
-
begin
|
74
|
-
str = readpartial(remaining)
|
75
|
-
rescue EOFError
|
76
|
-
return if length == remaining
|
77
|
-
return buffer
|
78
|
-
end
|
79
|
-
|
80
|
-
buffer << str
|
81
|
-
remaining -= str.length
|
82
|
-
end
|
83
|
-
else
|
84
|
-
while true
|
85
|
-
begin
|
86
|
-
buffer << read_nonblock(Socket::SO_RCVBUF)
|
87
|
-
rescue Errno::EAGAIN, EOFError
|
88
|
-
return buffer
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
ensure
|
93
|
-
release_ownership :r
|
94
|
-
end
|
95
|
-
|
96
|
-
buffer
|
97
|
-
end
|
98
|
-
|
99
|
-
def readpartial(length, buffer = nil)
|
100
|
-
buffer ||= ''.force_encoding(Encoding::ASCII_8BIT)
|
101
|
-
|
102
|
-
begin
|
103
|
-
read_nonblock(length, buffer)
|
104
|
-
rescue ::IO::WaitReadable
|
105
|
-
wait_readable
|
106
|
-
retry
|
107
|
-
end
|
108
|
-
|
109
|
-
buffer
|
110
|
-
end
|
111
|
-
|
112
|
-
def write(string)
|
113
|
-
length = string.length
|
114
|
-
total_written = 0
|
115
|
-
|
116
|
-
remaining = string
|
117
|
-
acquire_ownership :w
|
118
|
-
|
119
|
-
begin
|
120
|
-
while total_written < length
|
121
|
-
begin
|
122
|
-
written = write_nonblock(remaining)
|
123
|
-
rescue ::IO::WaitWritable
|
124
|
-
wait_writable
|
125
|
-
retry
|
126
|
-
rescue EOFError
|
127
|
-
return total_written
|
128
|
-
end
|
129
|
-
|
130
|
-
total_written += written
|
131
|
-
remaining.slice!(0, written) if written < remaining.length
|
132
|
-
end
|
133
|
-
ensure
|
134
|
-
release_ownership :w
|
135
|
-
end
|
136
|
-
|
137
|
-
total_written
|
138
|
-
end
|
139
|
-
|
140
|
-
def <<(string)
|
141
|
-
write string
|
142
|
-
self
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|