nio4r 0.3.3 → 0.4.0
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/.travis.yml +0 -1
- data/CHANGES.md +4 -0
- data/Gemfile +2 -0
- data/ext/libev/ev.c +9 -1
- data/ext/nio4r/org/nio4r/Nio4r.java +6 -0
- data/ext/nio4r/selector.c +11 -0
- data/lib/nio/selector.rb +26 -30
- data/lib/nio/version.rb +1 -1
- data/spec/nio/selectables/pipe_spec.rb +33 -0
- data/spec/nio/selectables/ssl_socket_spec.rb +162 -0
- data/spec/nio/selectables/tcp_socket_spec.rb +88 -0
- data/spec/nio/selectables/udp_socket_spec.rb +31 -0
- data/spec/nio/selector_spec.rb +8 -0
- data/spec/spec_helper.rb +2 -1
- data/spec/support/selectable_examples.rb +56 -0
- metadata +18 -10
- data/spec/nio/selectables_spec.rb +0 -182
data/.travis.yml
CHANGED
data/CHANGES.md
CHANGED
data/Gemfile
CHANGED
data/ext/libev/ev.c
CHANGED
@@ -3101,7 +3101,15 @@ other and thus cannot be composed.
|
|
3101
3101
|
And thus we are left with no choice but to patch the internals of libev in
|
3102
3102
|
order to release a mutex at just the precise moment.
|
3103
3103
|
|
3104
|
-
|
3104
|
+
This is a great example of a situation where granular locking and unlocking
|
3105
|
+
of the GVL is practically required. The goal is to get as close to the
|
3106
|
+
system call as possible, and to keep the GVL unlocked for the shortest
|
3107
|
+
amount of time possible.
|
3108
|
+
|
3109
|
+
Perhaps Ruby could benefit from such an API, e.g:
|
3110
|
+
|
3111
|
+
rb_thread_unsafe_dangerous_crazy_blocking_region_begin(...);
|
3112
|
+
rb_thread_unsafe_dangerous_crazy_blocking_region_end(...);
|
3105
3113
|
|
3106
3114
|
#######################################################################
|
3107
3115
|
*/
|
@@ -130,6 +130,12 @@ public class Nio4r implements Library {
|
|
130
130
|
Ruby runtime = context.getRuntime();
|
131
131
|
return this.selector.isOpen() ? runtime.getFalse() : runtime.getTrue();
|
132
132
|
}
|
133
|
+
|
134
|
+
@JRubyMethod(name = "empty?")
|
135
|
+
public IRubyObject isEmpty(ThreadContext context) {
|
136
|
+
Ruby runtime = context.getRuntime();
|
137
|
+
return this.selector.keys().isEmpty() ? runtime.getTrue() : runtime.getFalse();
|
138
|
+
}
|
133
139
|
|
134
140
|
@JRubyMethod
|
135
141
|
public IRubyObject register(ThreadContext context, IRubyObject io, IRubyObject interests) {
|
data/ext/nio4r/selector.c
CHANGED
@@ -28,6 +28,7 @@ static VALUE NIO_Selector_select(int argc, VALUE *argv, VALUE self);
|
|
28
28
|
static VALUE NIO_Selector_wakeup(VALUE self);
|
29
29
|
static VALUE NIO_Selector_close(VALUE self);
|
30
30
|
static VALUE NIO_Selector_closed(VALUE self);
|
31
|
+
static VALUE NIO_Selector_is_empty(VALUE self);
|
31
32
|
|
32
33
|
/* Internal functions */
|
33
34
|
static VALUE NIO_Selector_synchronize(VALUE self, VALUE (*func)(VALUE *args), VALUE *args);
|
@@ -60,6 +61,7 @@ void Init_NIO_Selector()
|
|
60
61
|
rb_define_method(cNIO_Selector, "wakeup", NIO_Selector_wakeup, 0);
|
61
62
|
rb_define_method(cNIO_Selector, "close", NIO_Selector_close, 0);
|
62
63
|
rb_define_method(cNIO_Selector, "closed?", NIO_Selector_closed, 0);
|
64
|
+
rb_define_method(cNIO_Selector, "empty?", NIO_Selector_is_empty, 0);
|
63
65
|
|
64
66
|
cNIO_Monitor = rb_define_class_under(mNIO, "Monitor", rb_cObject);
|
65
67
|
}
|
@@ -393,6 +395,15 @@ static VALUE NIO_Selector_closed(VALUE self)
|
|
393
395
|
return selector->closed ? Qtrue : Qfalse;
|
394
396
|
}
|
395
397
|
|
398
|
+
/* True if there are monitors on the loop */
|
399
|
+
static VALUE NIO_Selector_is_empty(VALUE self)
|
400
|
+
{
|
401
|
+
VALUE selectables = rb_ivar_get(self, rb_intern("selectables"));
|
402
|
+
|
403
|
+
return rb_funcall(selectables, rb_intern("empty?"), 0) == Qtrue ? Qtrue : Qfalse;
|
404
|
+
}
|
405
|
+
|
406
|
+
|
396
407
|
/* Called whenever a timeout fires on the event loop */
|
397
408
|
static void NIO_Selector_timeout_callback(struct ev_loop *ev_loop, struct ev_timer *timer, int revents)
|
398
409
|
{
|
data/lib/nio/selector.rb
CHANGED
@@ -49,16 +49,13 @@ module NIO
|
|
49
49
|
@selectables.each do |io, monitor|
|
50
50
|
readers << io if monitor.interests == :r || monitor.interests == :rw
|
51
51
|
writers << io if monitor.interests == :w || monitor.interests == :rw
|
52
|
+
monitor.readiness = nil
|
52
53
|
end
|
53
54
|
|
54
55
|
ready_readers, ready_writers = Kernel.select readers, writers, [], timeout
|
55
56
|
return unless ready_readers # timeout or wakeup
|
56
|
-
|
57
|
-
|
58
|
-
result = 0
|
59
|
-
else
|
60
|
-
result = []
|
61
|
-
end
|
57
|
+
|
58
|
+
selected_monitors = Set.new
|
62
59
|
|
63
60
|
ready_readers.each do |io|
|
64
61
|
if io == @wakeup
|
@@ -76,34 +73,29 @@ module NIO
|
|
76
73
|
else
|
77
74
|
monitor = @selectables[io]
|
78
75
|
monitor.readiness = :r
|
79
|
-
|
80
|
-
if block_given?
|
81
|
-
yield monitor
|
82
|
-
result += 1
|
83
|
-
else
|
84
|
-
result << monitor
|
85
|
-
end
|
76
|
+
selected_monitors << monitor
|
86
77
|
end
|
87
78
|
end
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
if block_given?
|
98
|
-
yield monitor
|
99
|
-
result += 1
|
100
|
-
else
|
101
|
-
result << monitor
|
102
|
-
end
|
79
|
+
|
80
|
+
ready_writers.each do |io|
|
81
|
+
monitor = @selectables[io]
|
82
|
+
monitor.readiness = case monitor.readiness
|
83
|
+
when :r
|
84
|
+
:rw
|
85
|
+
else
|
86
|
+
:w
|
103
87
|
end
|
88
|
+
selected_monitors << monitor
|
89
|
+
end
|
90
|
+
|
91
|
+
if block_given?
|
92
|
+
selected_monitors.each do |m|
|
93
|
+
yield m
|
94
|
+
end
|
95
|
+
selected_monitors.size
|
96
|
+
else
|
97
|
+
selected_monitors
|
104
98
|
end
|
105
|
-
|
106
|
-
result
|
107
99
|
end
|
108
100
|
end
|
109
101
|
|
@@ -132,5 +124,9 @@ module NIO
|
|
132
124
|
|
133
125
|
# Is this selector closed?
|
134
126
|
def closed?; @closed end
|
127
|
+
|
128
|
+
def empty?
|
129
|
+
@selectables.empty?
|
130
|
+
end
|
135
131
|
end
|
136
132
|
end
|
data/lib/nio/version.rb
CHANGED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "IO.pipe" do
|
4
|
+
let(:pair) { IO.pipe }
|
5
|
+
|
6
|
+
let :unreadable_subject do
|
7
|
+
pair.first
|
8
|
+
end
|
9
|
+
let :readable_subject do
|
10
|
+
pipe, peer = pair
|
11
|
+
peer << "data"
|
12
|
+
pipe
|
13
|
+
end
|
14
|
+
|
15
|
+
let :writable_subject do
|
16
|
+
pair.last
|
17
|
+
end
|
18
|
+
let :unwritable_subject do
|
19
|
+
reader, pipe = IO.pipe
|
20
|
+
|
21
|
+
begin
|
22
|
+
pipe.write_nonblock "JUNK IN THE TUBES"
|
23
|
+
_, writers = select [], [pipe], [], 0
|
24
|
+
rescue Errno::EPIPE
|
25
|
+
break
|
26
|
+
end while writers and writers.include? pipe
|
27
|
+
|
28
|
+
pipe
|
29
|
+
end
|
30
|
+
|
31
|
+
it_behaves_like "an NIO selectable"
|
32
|
+
it_behaves_like "an NIO selectable stream"
|
33
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
describe OpenSSL::SSL::SSLSocket, :if => RUBY_VERSION >= "1.9.0" do
|
5
|
+
let(:tcp_port) { 34567 }
|
6
|
+
|
7
|
+
let(:ssl_key) { OpenSSL::PKey::RSA.new(1024) }
|
8
|
+
|
9
|
+
let(:ssl_cert) do
|
10
|
+
name = OpenSSL::X509::Name.new([%w[CN localhost]])
|
11
|
+
OpenSSL::X509::Certificate.new.tap do |cert|
|
12
|
+
cert.version = 2
|
13
|
+
cert.serial = 1
|
14
|
+
cert.issuer = name
|
15
|
+
cert.subject = name
|
16
|
+
cert.not_before = Time.now
|
17
|
+
cert.not_after = Time.now + (365 * 24 *60 *60)
|
18
|
+
cert.public_key = ssl_key.public_key
|
19
|
+
|
20
|
+
cert.sign(ssl_key, OpenSSL::Digest::SHA1.new)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:ssl_server_context) do
|
25
|
+
OpenSSL::SSL::SSLContext.new.tap do |ctx|
|
26
|
+
ctx.cert = ssl_cert
|
27
|
+
ctx.key = ssl_key
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
let :readable_subject do
|
32
|
+
server = TCPServer.new("localhost", tcp_port)
|
33
|
+
client = TCPSocket.open("localhost", tcp_port)
|
34
|
+
peer = server.accept
|
35
|
+
|
36
|
+
ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
|
37
|
+
ssl_peer.sync_close = true
|
38
|
+
|
39
|
+
ssl_client = OpenSSL::SSL::SSLSocket.new(client)
|
40
|
+
ssl_client.sync_close = true
|
41
|
+
|
42
|
+
# SSLSocket#connect and #accept are blocking calls.
|
43
|
+
thread = Thread.new { ssl_client.connect }
|
44
|
+
|
45
|
+
ssl_peer.accept
|
46
|
+
ssl_peer << "data"
|
47
|
+
|
48
|
+
thread.join
|
49
|
+
pending "Failed to produce a readable SSL socket" unless select([ssl_client], [], [], 0)
|
50
|
+
|
51
|
+
ssl_client
|
52
|
+
end
|
53
|
+
|
54
|
+
let :unreadable_subject do
|
55
|
+
server = TCPServer.new("localhost", tcp_port + 1)
|
56
|
+
client = TCPSocket.new("localhost", tcp_port + 1)
|
57
|
+
peer = server.accept
|
58
|
+
|
59
|
+
ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
|
60
|
+
ssl_peer.sync_close = true
|
61
|
+
|
62
|
+
ssl_client = OpenSSL::SSL::SSLSocket.new(client)
|
63
|
+
ssl_client.sync_close = true
|
64
|
+
|
65
|
+
# SSLSocket#connect and #accept are blocking calls.
|
66
|
+
thread = Thread.new { ssl_client.connect }
|
67
|
+
ssl_peer.accept
|
68
|
+
thread.join
|
69
|
+
|
70
|
+
pending "Failed to produce an unreadable socket" if select([ssl_client], [], [], 0)
|
71
|
+
ssl_client
|
72
|
+
end
|
73
|
+
|
74
|
+
let :writable_subject do
|
75
|
+
server = TCPServer.new("localhost", tcp_port + 2)
|
76
|
+
client = TCPSocket.new("localhost", tcp_port + 2)
|
77
|
+
peer = server.accept
|
78
|
+
|
79
|
+
ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
|
80
|
+
ssl_peer.sync_close = true
|
81
|
+
|
82
|
+
ssl_client = OpenSSL::SSL::SSLSocket.new(client)
|
83
|
+
ssl_client.sync_close = true
|
84
|
+
|
85
|
+
# SSLSocket#connect and #accept are blocking calls.
|
86
|
+
thread = Thread.new { ssl_client.connect }
|
87
|
+
|
88
|
+
ssl_peer.accept
|
89
|
+
thread.join
|
90
|
+
|
91
|
+
ssl_client
|
92
|
+
end
|
93
|
+
|
94
|
+
let :unwritable_subject do
|
95
|
+
server = TCPServer.new("localhost", tcp_port + 3)
|
96
|
+
client = TCPSocket.open("localhost", tcp_port + 3)
|
97
|
+
peer = server.accept
|
98
|
+
|
99
|
+
ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
|
100
|
+
ssl_peer.sync_close = true
|
101
|
+
|
102
|
+
ssl_client = OpenSSL::SSL::SSLSocket.new(client)
|
103
|
+
ssl_client.sync_close = true
|
104
|
+
|
105
|
+
# SSLSocket#connect and #accept are blocking calls.
|
106
|
+
thread = Thread.new { ssl_client.connect }
|
107
|
+
|
108
|
+
ssl_peer.accept
|
109
|
+
thread.join
|
110
|
+
|
111
|
+
begin
|
112
|
+
_, writers = select [], [ssl_client], [], 0
|
113
|
+
count = ssl_client.write_nonblock "X" * 1024
|
114
|
+
count.should_not == 0
|
115
|
+
rescue IO::WaitReadable, IO::WaitWritable
|
116
|
+
pending "SSL will report writable but not accept writes"
|
117
|
+
raise if(writers.include? ssl_client)
|
118
|
+
end while writers and writers.include? ssl_client
|
119
|
+
|
120
|
+
# I think the kernel might manage to drain its buffer a bit even after
|
121
|
+
# the socket first goes unwritable. Attempt to sleep past this and then
|
122
|
+
# attempt to write again
|
123
|
+
sleep 0.1
|
124
|
+
|
125
|
+
# Once more for good measure!
|
126
|
+
begin
|
127
|
+
# ssl_client.write_nonblock "X" * 1024
|
128
|
+
loop { ssl_client.write_nonblock "X" * 1024 }
|
129
|
+
rescue OpenSSL::SSL::SSLError
|
130
|
+
end
|
131
|
+
|
132
|
+
# Sanity check to make sure we actually produced an unwritable socket
|
133
|
+
# if select([], [ssl_client], [], 0)
|
134
|
+
# pending "Failed to produce an unwritable socket"
|
135
|
+
# end
|
136
|
+
|
137
|
+
ssl_client
|
138
|
+
end
|
139
|
+
|
140
|
+
let :pair do
|
141
|
+
pending "figure out why newly created sockets are selecting readable immediately"
|
142
|
+
|
143
|
+
server = TCPServer.new("localhost", tcp_port + 4)
|
144
|
+
client = TCPSocket.open("localhost", tcp_port + 4)
|
145
|
+
peer = server.accept
|
146
|
+
|
147
|
+
ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
|
148
|
+
ssl_peer.sync_close = true
|
149
|
+
|
150
|
+
ssl_client = OpenSSL::SSL::SSLSocket.new(client)
|
151
|
+
ssl_client.sync_close = true
|
152
|
+
|
153
|
+
# SSLSocket#connect and #accept are blocking calls.
|
154
|
+
thread = Thread.new { ssl_client.connect }
|
155
|
+
ssl_peer.accept
|
156
|
+
|
157
|
+
[thread.value, ssl_peer]
|
158
|
+
end
|
159
|
+
|
160
|
+
it_behaves_like "an NIO selectable"
|
161
|
+
it_behaves_like "an NIO selectable stream"
|
162
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe TCPSocket do
|
4
|
+
port_offset = 0
|
5
|
+
let(:tcp_port) { 12345 + (port_offset += 1) }
|
6
|
+
|
7
|
+
let :readable_subject do
|
8
|
+
server = TCPServer.new("localhost", tcp_port)
|
9
|
+
sock = TCPSocket.open("localhost", tcp_port)
|
10
|
+
peer = server.accept
|
11
|
+
peer << "data"
|
12
|
+
sock
|
13
|
+
end
|
14
|
+
|
15
|
+
let :unreadable_subject do
|
16
|
+
TCPServer.new("localhost", tcp_port)
|
17
|
+
sock = TCPSocket.new("localhost", tcp_port)
|
18
|
+
|
19
|
+
# Sanity check to make sure we actually produced an unreadable socket
|
20
|
+
if select([sock], [], [], 0)
|
21
|
+
pending "Failed to produce an unreadable socket"
|
22
|
+
end
|
23
|
+
|
24
|
+
sock
|
25
|
+
end
|
26
|
+
|
27
|
+
let :writable_subject do
|
28
|
+
TCPServer.new("localhost", tcp_port)
|
29
|
+
TCPSocket.new("localhost", tcp_port)
|
30
|
+
end
|
31
|
+
|
32
|
+
let :unwritable_subject do
|
33
|
+
server = TCPServer.new("localhost", tcp_port)
|
34
|
+
sock = TCPSocket.open("localhost", tcp_port)
|
35
|
+
peer = server.accept
|
36
|
+
|
37
|
+
begin
|
38
|
+
sock.write_nonblock "X" * 1024
|
39
|
+
_, writers = select [], [sock], [], 0
|
40
|
+
end while writers and writers.include? sock
|
41
|
+
|
42
|
+
# I think the kernel might manage to drain its buffer a bit even after
|
43
|
+
# the socket first goes unwritable. Attempt to sleep past this and then
|
44
|
+
# attempt to write again
|
45
|
+
sleep 0.1
|
46
|
+
|
47
|
+
# Once more for good measure!
|
48
|
+
begin
|
49
|
+
sock.write_nonblock "X" * 1024
|
50
|
+
rescue Errno::EWOULDBLOCK
|
51
|
+
end
|
52
|
+
|
53
|
+
# Sanity check to make sure we actually produced an unwritable socket
|
54
|
+
if select([], [sock], [], 0)
|
55
|
+
pending "Failed to produce an unwritable socket"
|
56
|
+
end
|
57
|
+
|
58
|
+
sock
|
59
|
+
end
|
60
|
+
|
61
|
+
let :pair do
|
62
|
+
server = TCPServer.new("localhost", tcp_port)
|
63
|
+
client = TCPSocket.open("localhost", tcp_port)
|
64
|
+
[client, server.accept]
|
65
|
+
end
|
66
|
+
|
67
|
+
it_behaves_like "an NIO selectable"
|
68
|
+
it_behaves_like "an NIO selectable stream"
|
69
|
+
it_behaves_like "an NIO bidirectional stream"
|
70
|
+
|
71
|
+
context :connect do
|
72
|
+
it "selects writable when connected" do
|
73
|
+
selector = NIO::Selector.new
|
74
|
+
server = TCPServer.new('127.0.0.1', tcp_port)
|
75
|
+
|
76
|
+
client = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
77
|
+
monitor = selector.register(client, :w)
|
78
|
+
|
79
|
+
expect do
|
80
|
+
client.connect_nonblock Socket.sockaddr_in(tcp_port, '127.0.0.1')
|
81
|
+
end.to raise_exception Errno::EINPROGRESS
|
82
|
+
|
83
|
+
selector.select(0).should include monitor
|
84
|
+
result = client.getsockopt(::Socket::SOL_SOCKET, ::Socket::SO_ERROR)
|
85
|
+
result.unpack('i').first.should be_zero
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe UDPSocket do
|
4
|
+
let(:udp_port) { 23456 }
|
5
|
+
|
6
|
+
let :readable_subject do
|
7
|
+
sock = UDPSocket.new
|
8
|
+
sock.bind('localhost', udp_port)
|
9
|
+
|
10
|
+
peer = UDPSocket.new
|
11
|
+
peer.send("hi there", 0, 'localhost', udp_port)
|
12
|
+
|
13
|
+
sock
|
14
|
+
end
|
15
|
+
|
16
|
+
let :unreadable_subject do
|
17
|
+
sock = UDPSocket.new
|
18
|
+
sock.bind('localhost', udp_port + 1)
|
19
|
+
sock
|
20
|
+
end
|
21
|
+
|
22
|
+
let :writable_subject do
|
23
|
+
pending "come up with a writable UDPSocket example"
|
24
|
+
end
|
25
|
+
|
26
|
+
let :unwritable_subject do
|
27
|
+
pending "come up with a UDPSocket that's blocked on writing"
|
28
|
+
end
|
29
|
+
|
30
|
+
it_behaves_like "an NIO selectable"
|
31
|
+
end
|
data/spec/nio/selector_spec.rb
CHANGED
@@ -34,6 +34,14 @@ describe NIO::Selector do
|
|
34
34
|
subject.should_not be_registered(reader)
|
35
35
|
monitor.should be_closed
|
36
36
|
end
|
37
|
+
|
38
|
+
it "reports if it is empty" do
|
39
|
+
subject.should be_empty
|
40
|
+
|
41
|
+
monitor = subject.register(reader, :r)
|
42
|
+
|
43
|
+
subject.should_not be_empty
|
44
|
+
end
|
37
45
|
|
38
46
|
# This spec might seem a bit silly, but this actually something the
|
39
47
|
# Java NIO API specifically precludes that we need to work around
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,56 @@
|
|
1
|
+
shared_context "an NIO selectable" do
|
2
|
+
let(:selector) { NIO::Selector.new }
|
3
|
+
|
4
|
+
it "selects readable objects" do
|
5
|
+
monitor = selector.register(readable_subject, :r)
|
6
|
+
ready = selector.select(0)
|
7
|
+
ready.should be_an Enumerable
|
8
|
+
ready.should include monitor
|
9
|
+
end
|
10
|
+
|
11
|
+
it "does not select unreadable objects" do
|
12
|
+
monitor = selector.register(unreadable_subject, :r)
|
13
|
+
selector.select(0).should be_nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it "selects writable objects" do
|
17
|
+
monitor = selector.register(writable_subject, :w)
|
18
|
+
ready = selector.select(0)
|
19
|
+
ready.should be_an Enumerable
|
20
|
+
ready.should include monitor
|
21
|
+
end
|
22
|
+
|
23
|
+
it "does not select unwritable objects" do
|
24
|
+
monitor = selector.register(unwritable_subject, :w)
|
25
|
+
selector.select(0).should be_nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
shared_context "an NIO selectable stream" do
|
30
|
+
let(:selector) { NIO::Selector.new }
|
31
|
+
let(:stream) { pair.first }
|
32
|
+
let(:peer) { pair.last }
|
33
|
+
|
34
|
+
it "selects readable when the other end closes" do
|
35
|
+
monitor = selector.register(stream, :r)
|
36
|
+
selector.select(0).should be_nil
|
37
|
+
|
38
|
+
peer.close
|
39
|
+
#Wait and give the TCP session time to close
|
40
|
+
selector.select(0.1).should include monitor
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
shared_context "an NIO bidirectional stream" do
|
45
|
+
let(:selector) { NIO::Selector.new }
|
46
|
+
let(:stream) { pair.first }
|
47
|
+
let(:peer) { pair.last }
|
48
|
+
|
49
|
+
it "selects readable and writable" do
|
50
|
+
monitor = selector.register(readable_subject, :rw)
|
51
|
+
selector.select(0) do |m|
|
52
|
+
m.readiness.should == :rw
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nio4r
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-06-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake-compiler
|
16
|
-
requirement: &
|
16
|
+
requirement: &70312140700780 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70312140700780
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rake
|
27
|
-
requirement: &
|
27
|
+
requirement: &70312140700220 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70312140700220
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rspec
|
38
|
-
requirement: &
|
38
|
+
requirement: &70312140699800 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70312140699800
|
47
47
|
description: New IO for Ruby
|
48
48
|
email:
|
49
49
|
- tony.arcieri@gmail.com
|
@@ -90,9 +90,13 @@ files:
|
|
90
90
|
- nio4r.gemspec
|
91
91
|
- spec/nio/acceptables_spec.rb
|
92
92
|
- spec/nio/monitor_spec.rb
|
93
|
-
- spec/nio/
|
93
|
+
- spec/nio/selectables/pipe_spec.rb
|
94
|
+
- spec/nio/selectables/ssl_socket_spec.rb
|
95
|
+
- spec/nio/selectables/tcp_socket_spec.rb
|
96
|
+
- spec/nio/selectables/udp_socket_spec.rb
|
94
97
|
- spec/nio/selector_spec.rb
|
95
98
|
- spec/spec_helper.rb
|
99
|
+
- spec/support/selectable_examples.rb
|
96
100
|
- tasks/extension.rake
|
97
101
|
- tasks/rspec.rake
|
98
102
|
homepage: https://github.com/tarcieri/nio4r
|
@@ -122,6 +126,10 @@ summary: NIO provides a high performance selector API for monitoring IO objects
|
|
122
126
|
test_files:
|
123
127
|
- spec/nio/acceptables_spec.rb
|
124
128
|
- spec/nio/monitor_spec.rb
|
125
|
-
- spec/nio/
|
129
|
+
- spec/nio/selectables/pipe_spec.rb
|
130
|
+
- spec/nio/selectables/ssl_socket_spec.rb
|
131
|
+
- spec/nio/selectables/tcp_socket_spec.rb
|
132
|
+
- spec/nio/selectables/udp_socket_spec.rb
|
126
133
|
- spec/nio/selector_spec.rb
|
127
134
|
- spec/spec_helper.rb
|
135
|
+
- spec/support/selectable_examples.rb
|
@@ -1,182 +0,0 @@
|
|
1
|
-
describe "NIO selectables" do
|
2
|
-
let(:selector) { NIO::Selector.new }
|
3
|
-
|
4
|
-
shared_context "an NIO selectable" do
|
5
|
-
it "selects readable objects" do
|
6
|
-
monitor = selector.register(readable_subject, :r)
|
7
|
-
selector.select(0).should include monitor
|
8
|
-
end
|
9
|
-
|
10
|
-
it "does not select unreadable objects" do
|
11
|
-
monitor = selector.register(unreadable_subject, :r)
|
12
|
-
selector.select(0).should be_nil
|
13
|
-
end
|
14
|
-
|
15
|
-
it "selects writable objects" do
|
16
|
-
monitor = selector.register(writable_subject, :w)
|
17
|
-
selector.select(0).should include monitor
|
18
|
-
end
|
19
|
-
|
20
|
-
it "does not select unwritable objects" do
|
21
|
-
monitor = selector.register(unwritable_subject, :w)
|
22
|
-
selector.select(0).should be_nil
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
shared_context "an NIO selectable stream" do
|
27
|
-
let(:stream) { pair.first }
|
28
|
-
let(:peer) { pair.last }
|
29
|
-
|
30
|
-
it "selects readable when the other end closes" do
|
31
|
-
monitor = selector.register(stream, :r)
|
32
|
-
selector.select(0).should be_nil
|
33
|
-
|
34
|
-
peer.close
|
35
|
-
selector.select(0).should include monitor
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
describe "IO.pipe" do
|
40
|
-
let(:pair) { IO.pipe }
|
41
|
-
|
42
|
-
let :unreadable_subject do
|
43
|
-
pair.first
|
44
|
-
end
|
45
|
-
let :readable_subject do
|
46
|
-
pipe, peer = pair
|
47
|
-
peer << "data"
|
48
|
-
pipe
|
49
|
-
end
|
50
|
-
|
51
|
-
let :writable_subject do
|
52
|
-
pair.last
|
53
|
-
end
|
54
|
-
let :unwritable_subject do
|
55
|
-
reader, pipe = IO.pipe
|
56
|
-
|
57
|
-
begin
|
58
|
-
pipe.write_nonblock "JUNK IN THE TUBES"
|
59
|
-
_, writers = select [], [pipe], [], 0
|
60
|
-
rescue Errno::EPIPE
|
61
|
-
break
|
62
|
-
end while writers and writers.include? pipe
|
63
|
-
|
64
|
-
pipe
|
65
|
-
end
|
66
|
-
|
67
|
-
it_behaves_like "an NIO selectable"
|
68
|
-
it_behaves_like "an NIO selectable stream"
|
69
|
-
end
|
70
|
-
|
71
|
-
describe TCPSocket do
|
72
|
-
let(:tcp_port) { 12345 }
|
73
|
-
|
74
|
-
let :readable_subject do
|
75
|
-
server = TCPServer.new("localhost", tcp_port)
|
76
|
-
sock = TCPSocket.open("localhost", tcp_port)
|
77
|
-
peer = server.accept
|
78
|
-
peer << "data"
|
79
|
-
sock
|
80
|
-
end
|
81
|
-
|
82
|
-
let :unreadable_subject do
|
83
|
-
TCPServer.new("localhost", tcp_port + 1)
|
84
|
-
sock = TCPSocket.new("localhost", tcp_port + 1)
|
85
|
-
|
86
|
-
# Sanity check to make sure we actually produced an unreadable socket
|
87
|
-
if select([sock], [], [], 0)
|
88
|
-
pending "Failed to produce an unreadable socket"
|
89
|
-
end
|
90
|
-
|
91
|
-
sock
|
92
|
-
end
|
93
|
-
|
94
|
-
let :writable_subject do
|
95
|
-
TCPServer.new("localhost", tcp_port + 2)
|
96
|
-
TCPSocket.new("localhost", tcp_port + 2)
|
97
|
-
end
|
98
|
-
|
99
|
-
let :unwritable_subject do
|
100
|
-
server = TCPServer.new("localhost", tcp_port + 3)
|
101
|
-
sock = TCPSocket.open("localhost", tcp_port + 3)
|
102
|
-
peer = server.accept
|
103
|
-
|
104
|
-
begin
|
105
|
-
sock.write_nonblock "X" * 1024
|
106
|
-
_, writers = select [], [sock], [], 0
|
107
|
-
end while writers and writers.include? sock
|
108
|
-
|
109
|
-
# I think the kernel might manage to drain its buffer a bit even after
|
110
|
-
# the socket first goes unwritable. Attempt to sleep past this and then
|
111
|
-
# attempt to write again
|
112
|
-
sleep 0.1
|
113
|
-
|
114
|
-
# Once more for good measure!
|
115
|
-
begin
|
116
|
-
sock.write_nonblock "X" * 1024
|
117
|
-
rescue Errno::EWOULDBLOCK
|
118
|
-
end
|
119
|
-
|
120
|
-
# Sanity check to make sure we actually produced an unwritable socket
|
121
|
-
if select([], [sock], [], 0)
|
122
|
-
pending "Failed to produce an unwritable socket"
|
123
|
-
end
|
124
|
-
|
125
|
-
sock
|
126
|
-
end
|
127
|
-
|
128
|
-
let :pair do
|
129
|
-
server = TCPServer.new("localhost", tcp_port + 4)
|
130
|
-
client = TCPSocket.open("localhost", tcp_port + 4)
|
131
|
-
[client, server.accept]
|
132
|
-
end
|
133
|
-
|
134
|
-
it_behaves_like "an NIO selectable"
|
135
|
-
it_behaves_like "an NIO selectable stream"
|
136
|
-
|
137
|
-
it "selects writable when connected" do
|
138
|
-
server = TCPServer.new('127.0.0.1', tcp_port + 5)
|
139
|
-
|
140
|
-
client = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
141
|
-
monitor = selector.register(client, :w)
|
142
|
-
|
143
|
-
expect do
|
144
|
-
client.connect_nonblock Socket.sockaddr_in(tcp_port + 5, '127.0.0.1')
|
145
|
-
end.to raise_exception Errno::EINPROGRESS
|
146
|
-
|
147
|
-
selector.select(0).should include monitor
|
148
|
-
result = client.getsockopt(::Socket::SOL_SOCKET, ::Socket::SO_ERROR)
|
149
|
-
result.unpack('i').first.should be_zero
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
describe UDPSocket do
|
154
|
-
let(:udp_port) { 23456 }
|
155
|
-
|
156
|
-
let :readable_subject do
|
157
|
-
sock = UDPSocket.new
|
158
|
-
sock.bind('localhost', udp_port)
|
159
|
-
|
160
|
-
peer = UDPSocket.new
|
161
|
-
peer.send("hi there", 0, 'localhost', udp_port)
|
162
|
-
|
163
|
-
sock
|
164
|
-
end
|
165
|
-
|
166
|
-
let :unreadable_subject do
|
167
|
-
sock = UDPSocket.new
|
168
|
-
sock.bind('localhost', udp_port + 1)
|
169
|
-
sock
|
170
|
-
end
|
171
|
-
|
172
|
-
let :writable_subject do
|
173
|
-
pending "come up with a writable UDPSocket example"
|
174
|
-
end
|
175
|
-
|
176
|
-
let :unwritable_subject do
|
177
|
-
pending "come up with a UDPSocket that's blocked on writing"
|
178
|
-
end
|
179
|
-
|
180
|
-
it_behaves_like "an NIO selectable"
|
181
|
-
end
|
182
|
-
end
|