celluloid-io 0.17.2 → 0.17.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/CHANGES.md +35 -0
- data/lib/celluloid/io.rb +1 -0
- data/lib/celluloid/io/reactor.rb +1 -1
- data/lib/celluloid/io/socket.rb +80 -0
- data/lib/celluloid/io/ssl_server.rb +1 -4
- data/lib/celluloid/io/ssl_socket.rb +6 -13
- data/lib/celluloid/io/stream.rb +4 -3
- data/lib/celluloid/io/tcp_server.rb +30 -15
- data/lib/celluloid/io/tcp_socket.rb +66 -44
- data/lib/celluloid/io/udp_socket.rb +24 -9
- data/lib/celluloid/io/unix_server.rb +25 -17
- data/lib/celluloid/io/unix_socket.rb +6 -14
- data/lib/celluloid/io/version.rb +1 -1
- data/spec/celluloid/io/actor_spec.rb +54 -1
- data/spec/celluloid/io/socket_spec.rb +173 -0
- data/spec/celluloid/io/tcp_server_spec.rb +27 -0
- data/spec/celluloid/io/tcp_socket_spec.rb +14 -0
- data/spec/celluloid/io/unix_socket_spec.rb +31 -2
- metadata +80 -44
@@ -1,12 +1,27 @@
|
|
1
1
|
module Celluloid
|
2
2
|
module IO
|
3
3
|
# UDPSockets with combined blocking and evented support
|
4
|
-
class UDPSocket
|
4
|
+
class UDPSocket < Socket
|
5
5
|
extend Forwardable
|
6
|
-
def_delegators
|
6
|
+
def_delegators :to_io, :bind, :connect, :send, :recvfrom_nonblock
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
# @overload initialize(address_family)
|
9
|
+
# Opens a new udp socket using address_family.
|
10
|
+
# @param address_family [Numeric]
|
11
|
+
#
|
12
|
+
# @overload initialize(socket)
|
13
|
+
# Wraps an already existing udp socket.
|
14
|
+
# @param socket [::UDPSocket]
|
15
|
+
def initialize(*args)
|
16
|
+
if args.first.kind_of? ::BasicSocket
|
17
|
+
# socket
|
18
|
+
socket = args.first
|
19
|
+
fail ArgumentError, "wrong number of arguments (#{args.size} for 1)" if args.size != 1
|
20
|
+
fail ArgumentError, "wrong kind of socket (#{socket.class} for UDPSocket)" unless socket.kind_of? ::UDPSocket
|
21
|
+
super(socket)
|
22
|
+
else
|
23
|
+
super(::UDPSocket.new(*args))
|
24
|
+
end
|
10
25
|
end
|
11
26
|
|
12
27
|
# Wait until the socket is readable
|
@@ -16,13 +31,14 @@ module Celluloid
|
|
16
31
|
# MSG_ options. The first element of the results, mesg, is the data
|
17
32
|
# received. The second element, sender_addrinfo, contains
|
18
33
|
# protocol-specific address information of the sender.
|
19
|
-
def recvfrom(maxlen, flags =
|
34
|
+
def recvfrom(maxlen, flags = 0)
|
20
35
|
begin
|
21
|
-
|
22
|
-
|
36
|
+
socket = to_io
|
37
|
+
if socket.respond_to? :recvfrom_nonblock
|
38
|
+
socket.recvfrom_nonblock(maxlen, flags)
|
23
39
|
else
|
24
40
|
# FIXME: hax for JRuby
|
25
|
-
|
41
|
+
socket.recvfrom(maxlen, flags)
|
26
42
|
end
|
27
43
|
rescue ::IO::WaitReadable
|
28
44
|
wait_readable
|
@@ -30,7 +46,6 @@ module Celluloid
|
|
30
46
|
end
|
31
47
|
end
|
32
48
|
|
33
|
-
def to_io; @socket; end
|
34
49
|
end
|
35
50
|
end
|
36
51
|
end
|
@@ -3,40 +3,48 @@ require 'socket'
|
|
3
3
|
module Celluloid
|
4
4
|
module IO
|
5
5
|
# UNIXServer with combined blocking and evented support
|
6
|
-
class UNIXServer
|
6
|
+
class UNIXServer < Socket
|
7
7
|
extend Forwardable
|
8
|
-
def_delegators
|
8
|
+
def_delegators :to_io, :listen, :sysaccept
|
9
9
|
|
10
10
|
def self.open(socket_path)
|
11
11
|
self.new(socket_path)
|
12
12
|
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
14
|
+
# @overload initialize(socket_path)
|
15
|
+
# @param socket_path [String]
|
16
|
+
#
|
17
|
+
# @overload initialize(socket)
|
18
|
+
# @param socket [::UNIXServer]
|
19
|
+
def initialize(socket)
|
20
|
+
if socket.kind_of? ::BasicSocket
|
21
|
+
# socket
|
22
|
+
fail ArgumentError, "wrong kind of socket (#{socket.class} for UNIXServer)" unless socket.kind_of? ::UNIXServer
|
23
|
+
super(socket)
|
24
|
+
else
|
25
|
+
begin
|
26
|
+
super(::UNIXServer.new(socket))
|
27
|
+
rescue => ex
|
28
|
+
# Translate the EADDRINUSE jRuby exception.
|
29
|
+
raise unless RUBY_PLATFORM == 'java'
|
30
|
+
if ex.class.name == "IOError" && # Won't agree to .is_a?(IOError)
|
31
|
+
ex.message.include?("in use")
|
32
|
+
raise Errno::EADDRINUSE.new(ex.message)
|
33
|
+
end
|
34
|
+
raise
|
23
35
|
end
|
24
|
-
raise
|
25
36
|
end
|
26
37
|
end
|
27
38
|
|
28
39
|
def accept
|
29
|
-
Celluloid::IO.wait_readable(
|
40
|
+
Celluloid::IO.wait_readable(to_io)
|
30
41
|
accept_nonblock
|
31
42
|
end
|
32
43
|
|
33
44
|
def accept_nonblock
|
34
|
-
Celluloid::IO::UNIXSocket.new(
|
45
|
+
Celluloid::IO::UNIXSocket.new(to_io.accept_nonblock)
|
35
46
|
end
|
36
47
|
|
37
|
-
def to_io
|
38
|
-
@server
|
39
|
-
end
|
40
48
|
end
|
41
49
|
end
|
42
50
|
end
|
@@ -4,10 +4,6 @@ module Celluloid
|
|
4
4
|
module IO
|
5
5
|
# UNIXSocket with combined blocking and evented support
|
6
6
|
class UNIXSocket < Stream
|
7
|
-
extend Forwardable
|
8
|
-
|
9
|
-
def_delegators :@socket, :read_nonblock, :write_nonblock, :close, :closed?, :readline, :puts, :addr
|
10
|
-
|
11
7
|
# Open a UNIX connection.
|
12
8
|
def self.open(socket_path, &block)
|
13
9
|
new(socket_path, &block)
|
@@ -15,31 +11,27 @@ module Celluloid
|
|
15
11
|
|
16
12
|
# Convert a Ruby UNIXSocket into a Celluloid::IO::UNIXSocket
|
17
13
|
# DEPRECATED: to be removed in a future release
|
14
|
+
# @deprecated use .new instead
|
18
15
|
def self.from_ruby_socket(ruby_socket)
|
19
16
|
new(ruby_socket)
|
20
17
|
end
|
21
18
|
|
22
19
|
# Open a UNIX connection.
|
23
20
|
def initialize(socket_path, &block)
|
24
|
-
super()
|
25
|
-
|
26
21
|
# Allow users to pass in a Ruby UNIXSocket directly
|
27
22
|
if socket_path.is_a? ::UNIXSocket
|
28
|
-
|
23
|
+
super(socket_path)
|
29
24
|
return
|
30
25
|
end
|
31
26
|
|
32
27
|
# FIXME: not doing non-blocking connect
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
28
|
+
if block
|
29
|
+
super ::UNIXSocket.open(socket_path, &block)
|
30
|
+
else
|
31
|
+
super ::UNIXSocket.new(socket_path)
|
37
32
|
end
|
38
33
|
end
|
39
34
|
|
40
|
-
def to_io
|
41
|
-
@socket
|
42
|
-
end
|
43
35
|
end
|
44
36
|
end
|
45
37
|
end
|
data/lib/celluloid/io/version.rb
CHANGED
@@ -1,5 +1,58 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
RSpec.describe Celluloid::IO, library: :IO do
|
4
|
-
it_behaves_like "a Celluloid Actor", Celluloid::IO
|
4
|
+
it_behaves_like "a Celluloid Actor", Celluloid::IO do
|
5
|
+
let(:example_port) { assign_port }
|
6
|
+
|
7
|
+
context :timeouts do
|
8
|
+
let :sleeping_actor_class do
|
9
|
+
Class.new do
|
10
|
+
include Celluloid::IO
|
11
|
+
def initialize(addr, port)
|
12
|
+
@server = Celluloid::IO::TCPServer.new(addr, port)
|
13
|
+
async { @server.accept ; sleep 10 }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
let :foo_actor_class do
|
18
|
+
Class.new do
|
19
|
+
include Celluloid::IO
|
20
|
+
def initialize(addr, port)
|
21
|
+
@sock = Celluloid::IO::TCPSocket.new(addr, port)
|
22
|
+
end
|
23
|
+
|
24
|
+
# returns true if the operation timedout
|
25
|
+
def timedout_read(duration)
|
26
|
+
begin
|
27
|
+
timeout(duration) do
|
28
|
+
@sock.wait_readable
|
29
|
+
end
|
30
|
+
rescue Celluloid::TaskTimeout
|
31
|
+
return true
|
32
|
+
end
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
# returns true if it cannot write (socket is already registered)
|
37
|
+
def failed_write
|
38
|
+
begin
|
39
|
+
@sock.wait_readable
|
40
|
+
rescue ArgumentError # IO Selector Exception
|
41
|
+
return true
|
42
|
+
end
|
43
|
+
false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it "frees up the socket when a timeout error occurs" do
|
49
|
+
a1 = sleeping_actor_class.new(example_addr, example_port)
|
50
|
+
a2 = foo_actor_class.new(example_addr, example_port)
|
51
|
+
|
52
|
+
expect(a2.timedout_read(1)).to eq true # this ensures that the socket timeouted trying to read
|
53
|
+
skip "not implemented"
|
54
|
+
expect(a2.failed_write).to eq false # this ensures that the socket isn't usable anymore
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
5
58
|
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Celluloid::IO::Socket, library: :IO do
|
4
|
+
let(:logger) { Specs::FakeLogger.current }
|
5
|
+
let(:example_port) { assign_port }
|
6
|
+
|
7
|
+
context '.try_convert' do
|
8
|
+
|
9
|
+
subject{ described_class.try_convert(socket) }
|
10
|
+
|
11
|
+
after(:each) do
|
12
|
+
if subject.respond_to? :close
|
13
|
+
subject.close
|
14
|
+
else
|
15
|
+
socket.close if socket.respond_to? :close
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'with a Celluloid Socket' do
|
20
|
+
let(:socket){ Celluloid::IO::UDPSocket.new }
|
21
|
+
|
22
|
+
it 'returns given socket' do
|
23
|
+
expect(subject).to be socket
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'with a ::TCPServer' do
|
28
|
+
let(:socket){ ::TCPServer.new(example_port) }
|
29
|
+
|
30
|
+
it 'creates a Celluloid::IO::TCPServer' do
|
31
|
+
expect(subject).to be_a Celluloid::IO::TCPServer
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'with a ::TCPSocket' do
|
36
|
+
let!(:server){
|
37
|
+
::TCPServer.new example_addr, example_port
|
38
|
+
}
|
39
|
+
after(:each){
|
40
|
+
server.close
|
41
|
+
}
|
42
|
+
|
43
|
+
let(:socket){
|
44
|
+
::TCPSocket.new example_addr, example_port
|
45
|
+
}
|
46
|
+
|
47
|
+
it 'creates a Celluloid::IO::TCPSocket' do
|
48
|
+
expect(subject).to be_a Celluloid::IO::TCPSocket
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'with a ::UDPSocket' do
|
53
|
+
let(:socket){ ::UDPSocket.new }
|
54
|
+
|
55
|
+
it 'creates a Celluloid::IO::UDPServer' do
|
56
|
+
expect(subject).to be_a Celluloid::IO::UDPSocket
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'with a ::UNIXServer' do
|
61
|
+
let(:socket){ ::UNIXServer.new(example_unix_sock) }
|
62
|
+
|
63
|
+
it 'creates a Celluloid::IO::UNIXServer' do
|
64
|
+
expect(subject).to be_a Celluloid::IO::UNIXServer
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'with a ::UNIXSocket' do
|
69
|
+
let!(:server){
|
70
|
+
::UNIXServer.new(example_unix_sock)
|
71
|
+
}
|
72
|
+
after(:each){
|
73
|
+
server.close
|
74
|
+
}
|
75
|
+
|
76
|
+
let(:socket){
|
77
|
+
::UNIXSocket.new example_unix_sock
|
78
|
+
}
|
79
|
+
|
80
|
+
it 'creates a Celluloid::IO::UNIXSocket' do
|
81
|
+
expect(subject).to be_a Celluloid::IO::UNIXSocket
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'with an OpenSSL::SSL::SSLServer' do
|
86
|
+
let(:socket){
|
87
|
+
OpenSSL::SSL::SSLServer.new(::TCPServer.new(example_addr, example_port), OpenSSL::SSL::SSLContext.new)
|
88
|
+
}
|
89
|
+
|
90
|
+
it 'creates a Celluloid::IO::SSLServer' do
|
91
|
+
expect(subject).to be_a Celluloid::IO::SSLServer
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'with an OpenSSL::SSL::SSLSocket' do
|
96
|
+
let!(:server){
|
97
|
+
OpenSSL::SSL::SSLServer.new(::TCPServer.new(example_addr, example_port), OpenSSL::SSL::SSLContext.new)
|
98
|
+
}
|
99
|
+
after(:each){
|
100
|
+
server.close
|
101
|
+
}
|
102
|
+
|
103
|
+
let(:socket){
|
104
|
+
OpenSSL::SSL::SSLSocket.new(::TCPSocket.new(example_addr, example_port))
|
105
|
+
}
|
106
|
+
|
107
|
+
it 'creates a Celluloid::IO::SSLSocket' do
|
108
|
+
expect(subject).to be_a Celluloid::IO::SSLSocket
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'with an object responding to #to_io' do
|
113
|
+
let(:real){
|
114
|
+
::UDPSocket.new
|
115
|
+
}
|
116
|
+
|
117
|
+
let(:socket){
|
118
|
+
proxy = double(:socket)
|
119
|
+
allow(proxy).to receive(:to_io){ real }
|
120
|
+
allow(proxy).to receive(:close){ real.close }
|
121
|
+
proxy
|
122
|
+
}
|
123
|
+
|
124
|
+
it 'creates a celluloid socket' do
|
125
|
+
expect(subject).to be_a described_class
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'uses the returned IO' do
|
129
|
+
expect(subject.to_io).to be socket.to_io
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context 'with a simple object' do
|
134
|
+
let(:socket){ Object.new }
|
135
|
+
|
136
|
+
it 'returns nil' do
|
137
|
+
expect(subject).to be_nil
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
context 'compatibility with ::Socket' do
|
143
|
+
|
144
|
+
context '.new' do
|
145
|
+
it "creates basic sockets" do
|
146
|
+
socket = Celluloid::IO::Socket.new(Celluloid::IO::Socket::AF_INET, Celluloid::IO::Socket::SOCK_STREAM, 0)
|
147
|
+
expect(socket).to be_a ::Socket
|
148
|
+
socket.close
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context '.pair' do
|
153
|
+
it "creates basic sockets" do
|
154
|
+
a,b = Celluloid::IO::Socket.pair( Celluloid::IO::Socket::AF_UNIX, Celluloid::IO::Socket::SOCK_DGRAM, 0)
|
155
|
+
expect(a).to be_a ::Socket
|
156
|
+
expect(b).to be_a ::Socket
|
157
|
+
a.close
|
158
|
+
b.close
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context '.for_fd' do
|
163
|
+
it "creates basic sockets" do
|
164
|
+
socket = Celluloid::IO::Socket.new(Celluloid::IO::Socket::AF_INET, Celluloid::IO::Socket::SOCK_STREAM, 0)
|
165
|
+
copy = Celluloid::IO::Socket.for_fd(socket.fileno)
|
166
|
+
expect(copy).to be_a ::Socket
|
167
|
+
copy.close
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
@@ -28,6 +28,33 @@ RSpec.describe Celluloid::IO::TCPServer, library: :IO do
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
it "sends information to the client later" do
|
32
|
+
class LaterActor
|
33
|
+
include Celluloid::IO
|
34
|
+
|
35
|
+
def send_later(socket)
|
36
|
+
peer = socket.accept
|
37
|
+
after(0.4) { peer.write "1" }
|
38
|
+
after(0.4) { peer.write "2" }
|
39
|
+
peer
|
40
|
+
end
|
41
|
+
end
|
42
|
+
with_tcp_server(example_port) do |subject|
|
43
|
+
thread = Thread.new { TCPSocket.new(example_addr, example_port) }
|
44
|
+
actor = LaterActor.new
|
45
|
+
begin
|
46
|
+
peer = actor.send_later(subject)
|
47
|
+
client = thread.value
|
48
|
+
client.write payload
|
49
|
+
expect(peer.read(payload.size)).to eq payload # confirm the client read
|
50
|
+
Timeout::timeout(1) { expect(client.read(1)).to eq "1" }
|
51
|
+
Timeout::timeout(2) { expect(client.read(1)).to eq "2" }
|
52
|
+
ensure
|
53
|
+
actor.terminate if actor.alive?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
31
58
|
context "outside Celluloid::IO" do
|
32
59
|
it "should be blocking" do
|
33
60
|
with_tcp_server(example_port) do |subject|
|