nio4r 1.2.1 → 2.5.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 +5 -5
- data/.github/workflows/workflow.yml +43 -0
- data/.gitignore +1 -0
- data/.rspec +0 -1
- data/.rubocop.yml +70 -31
- data/CHANGES.md +190 -42
- data/Gemfile +8 -4
- data/Guardfile +10 -0
- data/README.md +102 -147
- data/Rakefile +3 -4
- data/examples/echo_server.rb +3 -2
- data/ext/libev/Changes +44 -13
- data/ext/libev/README +2 -1
- data/ext/libev/ev.c +314 -225
- data/ext/libev/ev.h +90 -88
- data/ext/libev/ev_epoll.c +30 -16
- data/ext/libev/ev_kqueue.c +19 -9
- data/ext/libev/ev_linuxaio.c +642 -0
- data/ext/libev/ev_poll.c +19 -11
- data/ext/libev/ev_port.c +13 -6
- data/ext/libev/ev_select.c +4 -2
- data/ext/libev/ev_vars.h +14 -3
- data/ext/libev/ev_wrap.h +16 -0
- data/ext/nio4r/bytebuffer.c +429 -0
- data/ext/nio4r/extconf.rb +17 -30
- data/ext/nio4r/monitor.c +113 -49
- data/ext/nio4r/nio4r.h +11 -13
- data/ext/nio4r/org/nio4r/ByteBuffer.java +293 -0
- data/ext/nio4r/org/nio4r/Monitor.java +175 -0
- data/ext/nio4r/org/nio4r/Nio4r.java +22 -391
- data/ext/nio4r/org/nio4r/Selector.java +299 -0
- data/ext/nio4r/selector.c +155 -68
- data/lib/nio.rb +4 -4
- data/lib/nio/bytebuffer.rb +229 -0
- data/lib/nio/monitor.rb +73 -11
- data/lib/nio/selector.rb +64 -21
- data/lib/nio/version.rb +1 -1
- data/nio4r.gemspec +34 -20
- data/{tasks → rakelib}/extension.rake +4 -0
- data/{tasks → rakelib}/rspec.rake +2 -0
- data/{tasks → rakelib}/rubocop.rake +2 -0
- data/spec/nio/acceptables_spec.rb +5 -5
- data/spec/nio/bytebuffer_spec.rb +354 -0
- data/spec/nio/monitor_spec.rb +128 -79
- data/spec/nio/selectables/pipe_spec.rb +12 -3
- data/spec/nio/selectables/ssl_socket_spec.rb +61 -29
- data/spec/nio/selectables/tcp_socket_spec.rb +47 -34
- data/spec/nio/selectables/udp_socket_spec.rb +24 -7
- data/spec/nio/selector_spec.rb +65 -16
- data/spec/spec_helper.rb +12 -3
- data/spec/support/selectable_examples.rb +45 -18
- metadata +33 -23
- data/.rubocop_todo.yml +0 -35
- data/.travis.yml +0 -27
- data/LICENSE.txt +0 -20
- data/ext/libev/README.embed +0 -3
- data/ext/libev/test_libev_win32.c +0 -123
data/spec/nio/monitor_spec.rb
CHANGED
@@ -1,113 +1,162 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "spec_helper"
|
2
4
|
require "socket"
|
3
5
|
|
4
6
|
RSpec.describe NIO::Monitor do
|
5
|
-
let(:
|
6
|
-
address = "127.0.0.1"
|
7
|
-
base_port = 12_345
|
8
|
-
tries = 10
|
9
|
-
|
10
|
-
server = tries.times do |n|
|
11
|
-
begin
|
12
|
-
break TCPServer.new(address, base_port + n)
|
13
|
-
rescue Errno::EADDRINUSE
|
14
|
-
retry
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
fail Errno::EADDRINUSE, "couldn't find an open port" unless server
|
19
|
-
client = TCPSocket.new(address, server.addr[1])
|
20
|
-
[server, client]
|
21
|
-
end
|
7
|
+
let(:addr) { "127.0.0.1" }
|
22
8
|
|
23
|
-
let(:reader) {
|
24
|
-
let(:
|
9
|
+
let(:reader) { TCPServer.new(addr, 0) }
|
10
|
+
let(:port) { reader.local_address.ip_port }
|
11
|
+
let(:writer) { TCPSocket.new(addr, port) }
|
25
12
|
|
26
13
|
let(:selector) { NIO::Selector.new }
|
27
14
|
|
28
|
-
subject
|
29
|
-
|
30
|
-
after { selector.close }
|
15
|
+
subject(:monitor) { selector.register(writer, :rw) }
|
16
|
+
subject(:peer) { selector.register(reader, :r) }
|
31
17
|
|
32
|
-
before
|
33
|
-
|
34
|
-
after
|
18
|
+
before { reader }
|
19
|
+
before { writer }
|
20
|
+
after { reader.close }
|
21
|
+
after { writer.close }
|
22
|
+
after { selector.close }
|
35
23
|
|
36
|
-
|
37
|
-
|
38
|
-
|
24
|
+
describe "#interests" do
|
25
|
+
it "knows its interests" do
|
26
|
+
expect(peer.interests).to eq(:r)
|
27
|
+
expect(monitor.interests).to eq(:rw)
|
28
|
+
end
|
39
29
|
end
|
40
30
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
31
|
+
describe "#interests=" do
|
32
|
+
it "can set interests to nil" do
|
33
|
+
expect(monitor.interests).not_to eq(nil)
|
34
|
+
monitor.interests = nil
|
35
|
+
expect(monitor.interests).to eq(nil)
|
36
|
+
end
|
46
37
|
|
47
|
-
|
48
|
-
|
49
|
-
|
38
|
+
it "changes the interest set" do
|
39
|
+
expect(monitor.interests).not_to eq(:w)
|
40
|
+
monitor.interests = :w
|
41
|
+
expect(monitor.interests).to eq(:w)
|
42
|
+
end
|
50
43
|
|
51
|
-
|
52
|
-
|
44
|
+
it "raises EOFError if interests are changed after the monitor is closed" do
|
45
|
+
monitor.close
|
46
|
+
expect { monitor.interests = :rw }.to raise_error(EOFError)
|
47
|
+
end
|
53
48
|
end
|
54
49
|
|
55
|
-
|
56
|
-
|
57
|
-
|
50
|
+
describe "#add_interest" do
|
51
|
+
it "sets a new interest if it isn't currently registered" do
|
52
|
+
monitor.interests = :r
|
53
|
+
expect(monitor.interests).to eq(:r)
|
54
|
+
|
55
|
+
expect(monitor.add_interest(:w)).to eq(:rw)
|
56
|
+
expect(monitor.interests).to eq(:rw)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "acts idempotently" do
|
60
|
+
monitor.interests = :r
|
61
|
+
expect(monitor.interests).to eq(:r)
|
62
|
+
|
63
|
+
expect(monitor.add_interest(:r)).to eq(:r)
|
64
|
+
expect(monitor.interests).to eq(:r)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "raises ArgumentError if given a bogus option" do
|
68
|
+
expect { monitor.add_interest(:derp) }.to raise_error(ArgumentError)
|
69
|
+
end
|
58
70
|
end
|
59
71
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
72
|
+
describe "#remove_interest" do
|
73
|
+
it "removes an interest from the set" do
|
74
|
+
expect(monitor.interests).to eq(:rw)
|
75
|
+
|
76
|
+
expect(monitor.remove_interest(:r)).to eq(:w)
|
77
|
+
expect(monitor.interests).to eq(:w)
|
78
|
+
end
|
64
79
|
|
65
|
-
|
66
|
-
|
80
|
+
it "can clear the last interest" do
|
81
|
+
monitor.interests = :w
|
82
|
+
expect(monitor.interests).to eq(:w)
|
67
83
|
|
68
|
-
|
69
|
-
|
70
|
-
|
84
|
+
expect(monitor.remove_interest(:w)).to be_nil
|
85
|
+
expect(monitor.interests).to be_nil
|
86
|
+
end
|
71
87
|
|
72
|
-
|
88
|
+
it "acts idempotently" do
|
89
|
+
monitor.interests = :w
|
90
|
+
expect(monitor.interests).to eq(:w)
|
73
91
|
|
74
|
-
|
75
|
-
|
92
|
+
expect(monitor.remove_interest(:r)).to eq(:w)
|
93
|
+
expect(monitor.interests).to eq(:w)
|
94
|
+
end
|
76
95
|
|
77
|
-
|
78
|
-
|
79
|
-
|
96
|
+
it "raises ArgumentError if given a bogus option" do
|
97
|
+
expect { monitor.add_interest(:derp) }.to raise_error(ArgumentError)
|
98
|
+
end
|
80
99
|
end
|
81
100
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
monitor.interests = :w
|
87
|
-
expect(monitor.interests).to eq(:w)
|
101
|
+
describe "#io" do
|
102
|
+
it "knows its IO object" do
|
103
|
+
expect(monitor.io).to eq(writer)
|
104
|
+
end
|
88
105
|
end
|
89
106
|
|
90
|
-
|
91
|
-
|
92
|
-
|
107
|
+
describe "#selector" do
|
108
|
+
it "knows its selector" do
|
109
|
+
expect(monitor.selector).to eq(selector)
|
110
|
+
end
|
111
|
+
end
|
93
112
|
|
94
|
-
|
95
|
-
|
96
|
-
|
113
|
+
describe "#value=" do
|
114
|
+
it "stores arbitrary values" do
|
115
|
+
monitor.value = 42
|
116
|
+
expect(monitor.value).to eq(42)
|
117
|
+
end
|
97
118
|
end
|
98
119
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
120
|
+
describe "#readiness" do
|
121
|
+
it "knows what operations IO objects are ready for" do
|
122
|
+
# For whatever odd reason this breaks unless we eagerly evaluate monitor
|
123
|
+
reader_peer = peer
|
124
|
+
writer_peer = monitor
|
125
|
+
|
126
|
+
selected = selector.select(0)
|
127
|
+
expect(selected).to include(writer_peer)
|
128
|
+
|
129
|
+
expect(writer_peer.readiness).to eq(:w)
|
130
|
+
expect(writer_peer).not_to be_readable
|
131
|
+
expect(writer_peer).to be_writable
|
132
|
+
|
133
|
+
writer << "testing 1 2 3"
|
134
|
+
|
135
|
+
selected = selector.select(0)
|
136
|
+
expect(selected).to include(reader_peer)
|
137
|
+
|
138
|
+
expect(reader_peer.readiness).to eq(:r)
|
139
|
+
expect(reader_peer).to be_readable
|
140
|
+
expect(reader_peer).not_to be_writable
|
141
|
+
end
|
105
142
|
end
|
106
143
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
144
|
+
describe "#close" do
|
145
|
+
it "closes" do
|
146
|
+
expect(monitor).not_to be_closed
|
147
|
+
expect(selector.registered?(writer)).to be_truthy
|
148
|
+
|
149
|
+
monitor.close
|
150
|
+
expect(monitor).to be_closed
|
151
|
+
expect(selector.registered?(writer)).to be_falsey
|
152
|
+
end
|
153
|
+
|
154
|
+
it "closes even if the selector has been shutdown" do
|
155
|
+
expect(monitor).not_to be_closed
|
156
|
+
selector.close # forces shutdown
|
157
|
+
expect(monitor).not_to be_closed
|
158
|
+
monitor.close
|
159
|
+
expect(monitor).to be_closed
|
160
|
+
end
|
112
161
|
end
|
113
162
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "spec_helper"
|
2
4
|
|
3
5
|
RSpec.describe "IO.pipe" do
|
@@ -6,6 +8,7 @@ RSpec.describe "IO.pipe" do
|
|
6
8
|
let :unreadable_subject do
|
7
9
|
pair.first
|
8
10
|
end
|
11
|
+
|
9
12
|
let :readable_subject do
|
10
13
|
pipe, peer = pair
|
11
14
|
peer << "data"
|
@@ -15,20 +18,26 @@ RSpec.describe "IO.pipe" do
|
|
15
18
|
let :writable_subject do
|
16
19
|
pair.last
|
17
20
|
end
|
21
|
+
|
18
22
|
let :unwritable_subject do
|
19
|
-
|
23
|
+
_reader, pipe = pair
|
20
24
|
|
21
25
|
# HACK: On OS X 10.8, this str must be larger than PIPE_BUF. Otherwise,
|
22
26
|
# the write is atomic and select() will return writable but write()
|
23
27
|
# will throw EAGAIN if there is too little space to write the string
|
24
28
|
# TODO: Use FFI to lookup the platform-specific size of PIPE_BUF
|
25
29
|
str = "JUNK IN THE TUBES" * 10_000
|
30
|
+
cntr = 0
|
26
31
|
begin
|
27
32
|
pipe.write_nonblock str
|
28
|
-
|
33
|
+
cntr += 1
|
34
|
+
t = select [], [pipe], [], 0
|
29
35
|
rescue Errno::EPIPE
|
30
36
|
break
|
31
|
-
|
37
|
+
rescue IO::EWOULDBLOCKWaitWritable
|
38
|
+
skip "windows - can't test due to 'select' not showing correct status"
|
39
|
+
break
|
40
|
+
end while t && t[1].include?(pipe) && cntr < 20
|
32
41
|
|
33
42
|
pipe
|
34
43
|
end
|
@@ -1,23 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "spec_helper"
|
2
|
-
require "openssl"
|
3
4
|
|
4
|
-
RSpec.describe OpenSSL::SSL::SSLSocket
|
5
|
-
|
5
|
+
RSpec.describe OpenSSL::SSL::SSLSocket do
|
6
|
+
|
7
|
+
require "openssl"
|
8
|
+
|
9
|
+
before(:all) do
|
10
|
+
@tls = []
|
11
|
+
end
|
6
12
|
|
7
|
-
let(:
|
13
|
+
let(:addr) { "127.0.0.1" }
|
14
|
+
|
15
|
+
let(:ssl_key) { OpenSSL::PKey::RSA.new(2048) }
|
8
16
|
|
9
17
|
let(:ssl_cert) do
|
10
|
-
name = OpenSSL::X509::Name.new([%w
|
18
|
+
name = OpenSSL::X509::Name.new([%w[CN 127.0.0.1]])
|
11
19
|
OpenSSL::X509::Certificate.new.tap do |cert|
|
12
20
|
cert.version = 2
|
13
21
|
cert.serial = 1
|
14
22
|
cert.issuer = name
|
15
23
|
cert.subject = name
|
16
24
|
cert.not_before = Time.now
|
17
|
-
cert.not_after = Time.now + (
|
25
|
+
cert.not_after = Time.now + (7 * 24 * 60 * 60)
|
18
26
|
cert.public_key = ssl_key.public_key
|
19
27
|
|
20
|
-
cert.sign(ssl_key, OpenSSL::Digest::
|
28
|
+
cert.sign(ssl_key, OpenSSL::Digest::SHA256.new)
|
21
29
|
end
|
22
30
|
end
|
23
31
|
|
@@ -25,12 +33,19 @@ RSpec.describe OpenSSL::SSL::SSLSocket, if: RUBY_VERSION >= "1.9.0" do
|
|
25
33
|
OpenSSL::SSL::SSLContext.new.tap do |ctx|
|
26
34
|
ctx.cert = ssl_cert
|
27
35
|
ctx.key = ssl_key
|
36
|
+
unless @tls.empty?
|
37
|
+
if ctx.respond_to? :set_minmax_proto_version, true
|
38
|
+
ctx.max_version = @tls[0]
|
39
|
+
else
|
40
|
+
ctx.ssl_version = @tls[1]
|
41
|
+
end
|
42
|
+
end
|
28
43
|
end
|
29
44
|
end
|
30
45
|
|
31
46
|
let :readable_subject do
|
32
|
-
server = TCPServer.new(
|
33
|
-
client = TCPSocket.open(
|
47
|
+
server = TCPServer.new(addr, 0)
|
48
|
+
client = TCPSocket.open(addr, server.local_address.ip_port)
|
34
49
|
peer = server.accept
|
35
50
|
|
36
51
|
ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
|
@@ -44,16 +59,17 @@ RSpec.describe OpenSSL::SSL::SSLSocket, if: RUBY_VERSION >= "1.9.0" do
|
|
44
59
|
|
45
60
|
ssl_peer.accept
|
46
61
|
ssl_peer << "data"
|
62
|
+
ssl_peer.flush
|
47
63
|
|
48
64
|
thread.join
|
49
|
-
pending "Failed to produce a readable SSL socket" unless select([ssl_client], [], [], 0)
|
50
65
|
|
66
|
+
pending "Failed to produce a readable socket" unless select([ssl_client], [], [], 10)
|
51
67
|
ssl_client
|
52
68
|
end
|
53
69
|
|
54
70
|
let :unreadable_subject do
|
55
|
-
server = TCPServer.new(
|
56
|
-
client = TCPSocket.new(
|
71
|
+
server = TCPServer.new(addr, 0)
|
72
|
+
client = TCPSocket.new(addr, server.local_address.ip_port)
|
57
73
|
peer = server.accept
|
58
74
|
|
59
75
|
ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
|
@@ -67,13 +83,17 @@ RSpec.describe OpenSSL::SSL::SSLSocket, if: RUBY_VERSION >= "1.9.0" do
|
|
67
83
|
ssl_peer.accept
|
68
84
|
thread.join
|
69
85
|
|
86
|
+
if ssl_client.ssl_version == "TLSv1.3"
|
87
|
+
expect(ssl_client.read_nonblock(1, exception: false)).to eq(:wait_readable)
|
88
|
+
end
|
89
|
+
|
70
90
|
pending "Failed to produce an unreadable socket" if select([ssl_client], [], [], 0)
|
71
91
|
ssl_client
|
72
92
|
end
|
73
93
|
|
74
94
|
let :writable_subject do
|
75
|
-
server = TCPServer.new(
|
76
|
-
client = TCPSocket.new(
|
95
|
+
server = TCPServer.new(addr, 0)
|
96
|
+
client = TCPSocket.new(addr, server.local_address.ip_port)
|
77
97
|
peer = server.accept
|
78
98
|
|
79
99
|
ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
|
@@ -92,8 +112,8 @@ RSpec.describe OpenSSL::SSL::SSLSocket, if: RUBY_VERSION >= "1.9.0" do
|
|
92
112
|
end
|
93
113
|
|
94
114
|
let :unwritable_subject do
|
95
|
-
server = TCPServer.new(
|
96
|
-
client = TCPSocket.
|
115
|
+
server = TCPServer.new(addr, 0)
|
116
|
+
client = TCPSocket.new(addr, server.local_address.ip_port)
|
97
117
|
peer = server.accept
|
98
118
|
|
99
119
|
ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
|
@@ -108,14 +128,15 @@ RSpec.describe OpenSSL::SSL::SSLSocket, if: RUBY_VERSION >= "1.9.0" do
|
|
108
128
|
ssl_peer.accept
|
109
129
|
thread.join
|
110
130
|
|
131
|
+
cntr = 0
|
111
132
|
begin
|
112
|
-
_, writers = select [], [ssl_client], [], 0
|
113
133
|
count = ssl_client.write_nonblock "X" * 1024
|
114
134
|
expect(count).not_to eq(0)
|
135
|
+
cntr += 1
|
136
|
+
t = select [], [ssl_client], [], 0
|
115
137
|
rescue IO::WaitReadable, IO::WaitWritable
|
116
138
|
pending "SSL will report writable but not accept writes"
|
117
|
-
|
118
|
-
end while writers && writers.include?(ssl_client)
|
139
|
+
end while t && t[1].include?(ssl_client) && cntr < 30
|
119
140
|
|
120
141
|
# I think the kernel might manage to drain its buffer a bit even after
|
121
142
|
# the socket first goes unwritable. Attempt to sleep past this and then
|
@@ -124,24 +145,22 @@ RSpec.describe OpenSSL::SSL::SSLSocket, if: RUBY_VERSION >= "1.9.0" do
|
|
124
145
|
|
125
146
|
# Once more for good measure!
|
126
147
|
begin
|
127
|
-
#
|
148
|
+
# ssl_client.write_nonblock "X" * 1024
|
128
149
|
loop { ssl_client.write_nonblock "X" * 1024 }
|
129
150
|
rescue OpenSSL::SSL::SSLError
|
130
151
|
end
|
131
152
|
|
132
153
|
# Sanity check to make sure we actually produced an unwritable socket
|
133
|
-
|
134
|
-
|
135
|
-
|
154
|
+
if select([], [ssl_client], [], 0)
|
155
|
+
pending "Failed to produce an unwritable socket"
|
156
|
+
end
|
136
157
|
|
137
158
|
ssl_client
|
138
159
|
end
|
139
160
|
|
140
161
|
let :pair do
|
141
|
-
|
142
|
-
|
143
|
-
server = TCPServer.new("localhost", tcp_port + 4)
|
144
|
-
client = TCPSocket.open("localhost", tcp_port + 4)
|
162
|
+
server = TCPServer.new(addr, 0)
|
163
|
+
client = TCPSocket.new(addr, server.local_address.ip_port)
|
145
164
|
peer = server.accept
|
146
165
|
|
147
166
|
ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
|
@@ -157,6 +176,19 @@ RSpec.describe OpenSSL::SSL::SSLSocket, if: RUBY_VERSION >= "1.9.0" do
|
|
157
176
|
[thread.value, ssl_peer]
|
158
177
|
end
|
159
178
|
|
160
|
-
|
161
|
-
|
179
|
+
describe "using TLS 1.2" do
|
180
|
+
before(:all) do
|
181
|
+
@tls = %i[TLS1_2 TLSv1_2]
|
182
|
+
end
|
183
|
+
it_behaves_like "an NIO selectable"
|
184
|
+
it_behaves_like "an NIO selectable stream"
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "using TLS 1.3", if: OpenSSL::SSL.const_defined?(:TLS1_3_VERSION) do
|
188
|
+
before(:all) do
|
189
|
+
@tls = %i[TLS1_3 TLSv1_3]
|
190
|
+
end
|
191
|
+
it_behaves_like "an NIO selectable"
|
192
|
+
it_behaves_like "an NIO selectable stream", true
|
193
|
+
end
|
162
194
|
end
|