ione 1.0.0 → 1.1.0.pre0
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/lib/ione/io/acceptor.rb +96 -0
- data/lib/ione/io/base_connection.rb +162 -0
- data/lib/ione/io/connection.rb +9 -150
- data/lib/ione/io/io_reactor.rb +15 -0
- data/lib/ione/io/server_connection.rb +17 -0
- data/lib/ione/io.rb +6 -0
- data/lib/ione/version.rb +1 -1
- data/spec/integration/io_spec.rb +41 -4
- data/spec/ione/io/acceptor_spec.rb +223 -0
- data/spec/ione/io/connection_common.rb +255 -0
- data/spec/ione/io/connection_spec.rb +23 -178
- data/spec/ione/io/io_reactor_spec.rb +48 -3
- data/spec/ione/io/server_connection_spec.rb +37 -0
- metadata +13 -4
@@ -0,0 +1,223 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
module Ione
|
7
|
+
module Io
|
8
|
+
describe Acceptor do
|
9
|
+
let :acceptor do
|
10
|
+
described_class.new('example.com', 4321, backlog, unblocker, reactor, socket_impl)
|
11
|
+
end
|
12
|
+
|
13
|
+
let :backlog do
|
14
|
+
3
|
15
|
+
end
|
16
|
+
|
17
|
+
let :unblocker do
|
18
|
+
double(:unblocker)
|
19
|
+
end
|
20
|
+
|
21
|
+
let :reactor do
|
22
|
+
double(:reactor)
|
23
|
+
end
|
24
|
+
|
25
|
+
let :socket_impl do
|
26
|
+
double(:socket_impl)
|
27
|
+
end
|
28
|
+
|
29
|
+
let :socket do
|
30
|
+
double(:socket)
|
31
|
+
end
|
32
|
+
|
33
|
+
shared_context 'accepting_connections' do
|
34
|
+
let :client_socket do
|
35
|
+
double(:client_socket)
|
36
|
+
end
|
37
|
+
|
38
|
+
let :accepted_handlers do
|
39
|
+
[]
|
40
|
+
end
|
41
|
+
|
42
|
+
before do
|
43
|
+
socket_impl.stub(:unpack_sockaddr_in).with('SOCKADDRX').and_return([3333, 'example.com'])
|
44
|
+
socket.stub(:bind)
|
45
|
+
socket.stub(:accept_nonblock).and_return([client_socket, 'SOCKADDRX'])
|
46
|
+
reactor.stub(:accept) { |h| accepted_handlers << h }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
before do
|
51
|
+
socket_impl.stub(:getaddrinfo)
|
52
|
+
.with('example.com', 4321, nil, Socket::SOCK_STREAM)
|
53
|
+
.and_return([[nil, 'PORT', nil, 'IP1', 'FAMILY1', 'TYPE1'], [nil, 'PORT', nil, 'IP2', 'FAMILY2', 'TYPE2']])
|
54
|
+
socket_impl.stub(:sockaddr_in)
|
55
|
+
.with('PORT', 'IP1')
|
56
|
+
.and_return('SOCKADDR1')
|
57
|
+
socket_impl.stub(:sockaddr_in)
|
58
|
+
.with('PORT', 'IP2')
|
59
|
+
.and_return('SOCKADDR2')
|
60
|
+
socket_impl.stub(:new)
|
61
|
+
.with('FAMILY1', 'TYPE1', 0)
|
62
|
+
.and_return(socket)
|
63
|
+
socket_impl.stub(:new)
|
64
|
+
.with('FAMILY2', 'TYPE2', 0)
|
65
|
+
.and_return(socket)
|
66
|
+
socket.stub(:bind)
|
67
|
+
socket.stub(:listen)
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '#bind' do
|
71
|
+
if RUBY_ENGINE == 'jruby'
|
72
|
+
it 'creates a new socket and binds it to all interfaces' do
|
73
|
+
acceptor.bind
|
74
|
+
socket.should have_received(:bind).with('SOCKADDR1', backlog)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'tries the next address when the first one raises EADDRNOTAVAIL' do
|
78
|
+
socket.stub(:bind).with('SOCKADDR1', anything).and_raise(Errno::EADDRNOTAVAIL)
|
79
|
+
acceptor.bind
|
80
|
+
socket.should have_received(:bind).with('SOCKADDR2', backlog)
|
81
|
+
end
|
82
|
+
else
|
83
|
+
it 'creates a new socket and binds it to all interfaces' do
|
84
|
+
acceptor.bind
|
85
|
+
socket.should have_received(:bind).with('SOCKADDR1')
|
86
|
+
socket.should have_received(:listen).with(backlog)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'tries the next address when the first one raises EADDRNOTAVAIL' do
|
90
|
+
socket.stub(:bind).with('SOCKADDR1').and_raise(Errno::EADDRNOTAVAIL)
|
91
|
+
acceptor.bind
|
92
|
+
socket.should have_received(:bind).with('SOCKADDR2')
|
93
|
+
socket.should have_received(:listen).with(backlog)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'returns a failed future when none of the addresses worked' do
|
98
|
+
socket.stub(:bind).and_raise(Errno::EADDRNOTAVAIL)
|
99
|
+
f = acceptor.bind
|
100
|
+
expect { f.value }.to raise_error(Errno::EADDRNOTAVAIL)
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'returns a future that resolves to itself when the socket has been bound' do
|
104
|
+
f = acceptor.bind
|
105
|
+
f.should be_resolved
|
106
|
+
f.value.should equal(acceptor)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe '#close' do
|
111
|
+
before do
|
112
|
+
socket.stub(:close)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'closes the socket' do
|
116
|
+
acceptor.bind
|
117
|
+
acceptor.close
|
118
|
+
socket.should have_received(:close)
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'does nothing when called before #bind' do
|
122
|
+
acceptor.close
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'does nothing when called again' do
|
126
|
+
acceptor.bind
|
127
|
+
acceptor.close
|
128
|
+
acceptor.close
|
129
|
+
acceptor.close
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'ignores IOError' do
|
133
|
+
socket.stub(:close).and_raise(IOError)
|
134
|
+
acceptor.bind
|
135
|
+
expect { acceptor.close }.to_not raise_error
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'ignores Errno::*' do
|
139
|
+
socket.stub(:close).and_raise(Errno::EINVAL)
|
140
|
+
acceptor.bind
|
141
|
+
expect { acceptor.close }.to_not raise_error
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'is closed afterwards' do
|
145
|
+
acceptor.bind
|
146
|
+
acceptor.close
|
147
|
+
acceptor.should be_closed
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'is is not connected afterwards' do
|
151
|
+
acceptor.bind
|
152
|
+
acceptor.close
|
153
|
+
acceptor.should_not be_connected
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe '#read' do
|
158
|
+
include_context 'accepting_connections'
|
159
|
+
|
160
|
+
it 'accepts a new connection' do
|
161
|
+
acceptor.bind
|
162
|
+
acceptor.read
|
163
|
+
socket.should have_received(:accept_nonblock)
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'creates a new connection handler and registers it with the reactor' do
|
167
|
+
acceptor.bind
|
168
|
+
acceptor.read
|
169
|
+
accepted_handlers.should have(1).item
|
170
|
+
accepted_handlers.first.host.should == 'example.com'
|
171
|
+
accepted_handlers.first.port.should == 3333
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'passes the unblocker along to the connection handler' do
|
175
|
+
unblocker.stub(:unblock!)
|
176
|
+
acceptor.bind
|
177
|
+
acceptor.read
|
178
|
+
accepted_handlers.first.write('foo')
|
179
|
+
unblocker.should have_received(:unblock!)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe '#on_accept' do
|
184
|
+
include_context 'accepting_connections'
|
185
|
+
|
186
|
+
it 'calls accept listeners with new connections' do
|
187
|
+
received_connection1 = nil
|
188
|
+
received_connection2 = nil
|
189
|
+
acceptor.on_accept { |c| received_connection1 = c }
|
190
|
+
acceptor.on_accept { |c| received_connection2 = c }
|
191
|
+
acceptor.bind
|
192
|
+
acceptor.read
|
193
|
+
received_connection1.host.should == 'example.com'
|
194
|
+
received_connection2.host.should == 'example.com'
|
195
|
+
received_connection2.port.should == 3333
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'ignores exceptions raised by the connection callback' do
|
199
|
+
called = false
|
200
|
+
acceptor.on_accept { |c| raise 'bork!' }
|
201
|
+
acceptor.on_accept { |c| called = true }
|
202
|
+
acceptor.bind
|
203
|
+
acceptor.read
|
204
|
+
called.should be_true
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe '#to_io' do
|
209
|
+
it 'returns the socket' do
|
210
|
+
acceptor.bind
|
211
|
+
acceptor.to_io.should equal(socket)
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'returns nil when the socket has been closed' do
|
215
|
+
socket.stub(:close)
|
216
|
+
acceptor.bind
|
217
|
+
acceptor.close
|
218
|
+
acceptor.to_io.should be_nil
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
@@ -0,0 +1,255 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
shared_examples_for 'a connection' do
|
4
|
+
describe '#close' do
|
5
|
+
it 'closes the socket' do
|
6
|
+
socket.should_receive(:close)
|
7
|
+
handler.close
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'returns true' do
|
11
|
+
handler.close.should be_true
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'does nothing when called again' do
|
15
|
+
handler.close
|
16
|
+
handler.close
|
17
|
+
handler.close
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'swallows SystemCallErrors' do
|
21
|
+
socket.stub(:close).and_raise(SystemCallError.new('Bork!', 9999))
|
22
|
+
handler.close
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'swallows IOErrors' do
|
26
|
+
socket.stub(:close).and_raise(IOError.new('Bork!'))
|
27
|
+
handler.close
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'calls the closed listener' do
|
31
|
+
called = false
|
32
|
+
handler.on_closed { called = true }
|
33
|
+
handler.close
|
34
|
+
called.should be_true, 'expected the close listener to have been called'
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'does nothing when closed a second time' do
|
38
|
+
socket.should_receive(:close).once
|
39
|
+
calls = 0
|
40
|
+
handler.on_closed { calls += 1 }
|
41
|
+
handler.close
|
42
|
+
handler.close
|
43
|
+
calls.should == 1
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'returns false if it did nothing' do
|
47
|
+
handler.close
|
48
|
+
handler.close.should be_false
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'is not writable when closed' do
|
52
|
+
handler.write('foo')
|
53
|
+
handler.close
|
54
|
+
handler.should_not be_writable
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'is closed afterwards' do
|
58
|
+
handler.close
|
59
|
+
handler.should be_closed
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'is is not connected afterwards' do
|
63
|
+
handler.close
|
64
|
+
handler.should_not be_connected
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '#drain' do
|
69
|
+
before do
|
70
|
+
socket.stub(:write_nonblock) { |s| s.bytesize }
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'waits for the buffer to drain and then closes the socket' do
|
74
|
+
handler.write('hello world')
|
75
|
+
handler.drain
|
76
|
+
handler.should_not be_closed
|
77
|
+
handler.flush
|
78
|
+
handler.should be_closed
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'closes the socket immediately when the buffer is empty' do
|
82
|
+
handler.drain
|
83
|
+
handler.should be_closed
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'returns a future that completes when the socket has closed' do
|
87
|
+
handler.write('hello world')
|
88
|
+
f = handler.drain
|
89
|
+
f.should_not be_completed
|
90
|
+
handler.flush
|
91
|
+
f.should be_completed
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#write/#flush' do
|
96
|
+
before do
|
97
|
+
socket.stub(:write_nonblock)
|
98
|
+
unblocker.stub(:unblock!)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'appends to its buffer when #write is called' do
|
102
|
+
handler.write('hello world')
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'unblocks the reactor' do
|
106
|
+
unblocker.should_receive(:unblock!)
|
107
|
+
handler.write('hello world')
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'is writable when there are bytes to write' do
|
111
|
+
handler.should_not be_writable
|
112
|
+
handler.write('hello world')
|
113
|
+
handler.should be_writable
|
114
|
+
socket.should_receive(:write_nonblock).with('hello world').and_return(11)
|
115
|
+
handler.flush
|
116
|
+
handler.should_not be_writable
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'writes to the socket from its buffer when #flush is called' do
|
120
|
+
handler.write('hello world')
|
121
|
+
socket.should_receive(:write_nonblock).with('hello world').and_return(11)
|
122
|
+
handler.flush
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'takes note of how much the #write_nonblock call consumed and writes the rest of the buffer on the next call to #flush' do
|
126
|
+
handler.write('hello world')
|
127
|
+
socket.should_receive(:write_nonblock).with('hello world').and_return(6)
|
128
|
+
handler.flush
|
129
|
+
socket.should_receive(:write_nonblock).with('world').and_return(5)
|
130
|
+
handler.flush
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'does not call #write_nonblock if the buffer is empty' do
|
134
|
+
handler.flush
|
135
|
+
handler.write('hello world')
|
136
|
+
socket.should_receive(:write_nonblock).with('hello world').and_return(11)
|
137
|
+
handler.flush
|
138
|
+
socket.should_not_receive(:write_nonblock)
|
139
|
+
handler.flush
|
140
|
+
end
|
141
|
+
|
142
|
+
context 'with a block' do
|
143
|
+
it 'yields a byte buffer to the block' do
|
144
|
+
socket.should_receive(:write_nonblock).with('hello world').and_return(11)
|
145
|
+
handler.write do |buffer|
|
146
|
+
buffer << 'hello world'
|
147
|
+
end
|
148
|
+
handler.flush
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'when #write_nonblock raises an error' do
|
153
|
+
before do
|
154
|
+
socket.stub(:close)
|
155
|
+
socket.stub(:write_nonblock).and_raise('Bork!')
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'closes the socket' do
|
159
|
+
socket.should_receive(:close)
|
160
|
+
handler.write('hello world')
|
161
|
+
handler.flush
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'passes the error to the close handler' do
|
165
|
+
error = nil
|
166
|
+
handler.on_closed { |e| error = e }
|
167
|
+
handler.write('hello world')
|
168
|
+
handler.flush
|
169
|
+
error.should be_a(Exception)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'when closed' do
|
174
|
+
it 'discards the bytes' do
|
175
|
+
handler.close
|
176
|
+
handler.write('hello world')
|
177
|
+
handler.flush
|
178
|
+
socket.should_not have_received(:write_nonblock)
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'does not yield the buffer' do
|
182
|
+
called = false
|
183
|
+
handler.close
|
184
|
+
handler.write { called = true }
|
185
|
+
handler.flush
|
186
|
+
called.should be_false
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'does not unblock the reactor' do
|
190
|
+
handler.close
|
191
|
+
handler.write('hello world')
|
192
|
+
handler.flush
|
193
|
+
unblocker.should_not have_received(:unblock!)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
context 'when draining' do
|
198
|
+
it 'discards the bytes' do
|
199
|
+
handler.drain
|
200
|
+
handler.write('hello world')
|
201
|
+
handler.flush
|
202
|
+
socket.should_not have_received(:write_nonblock)
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'does not yield the buffer' do
|
206
|
+
called = false
|
207
|
+
handler.drain
|
208
|
+
handler.write { called = true }
|
209
|
+
handler.flush
|
210
|
+
called.should be_false
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'does not unblock the reactor' do
|
214
|
+
handler.drain
|
215
|
+
handler.write('hello world')
|
216
|
+
handler.flush
|
217
|
+
unblocker.should_not have_received(:unblock!)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
describe '#read/#on_data' do
|
223
|
+
it 'reads a chunk from the socket' do
|
224
|
+
socket.should_receive(:read_nonblock).with(instance_of(Fixnum)).and_return('foo bar')
|
225
|
+
handler.read
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'calls the data listener with the new data' do
|
229
|
+
socket.should_receive(:read_nonblock).with(instance_of(Fixnum)).and_return('foo bar')
|
230
|
+
data = nil
|
231
|
+
handler.on_data { |d| data = d }
|
232
|
+
handler.read
|
233
|
+
data.should == 'foo bar'
|
234
|
+
end
|
235
|
+
|
236
|
+
context 'when #read_nonblock raises an error' do
|
237
|
+
before do
|
238
|
+
socket.stub(:close)
|
239
|
+
socket.stub(:read_nonblock).and_raise('Bork!')
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'closes the socket' do
|
243
|
+
socket.should_receive(:close)
|
244
|
+
handler.read
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'passes the error to the close handler' do
|
248
|
+
error = nil
|
249
|
+
handler.on_closed { |e| error = e }
|
250
|
+
handler.read
|
251
|
+
error.should be_a(Exception)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|