celluloid-io 0.13.0.pre → 0.13.0.pre2
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/.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
|

|
2
2
|
=============
|
3
|
+
[](http://rubygems.org/gems/celluloid-io)
|
3
4
|
[](http://travis-ci.org/celluloid/celluloid-io)
|
4
5
|
[](https://gemnasium.com/celluloid/celluloid-io)
|
5
|
-
[](https://codeclimate.com/github/celluloid/celluloid-io)
|
7
|
+
[](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
|