celluloid-io 0.17.2 → 0.17.3
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.
- 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|
|