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.
Files changed (57) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/workflow.yml +43 -0
  3. data/.gitignore +1 -0
  4. data/.rspec +0 -1
  5. data/.rubocop.yml +70 -31
  6. data/CHANGES.md +190 -42
  7. data/Gemfile +8 -4
  8. data/Guardfile +10 -0
  9. data/README.md +102 -147
  10. data/Rakefile +3 -4
  11. data/examples/echo_server.rb +3 -2
  12. data/ext/libev/Changes +44 -13
  13. data/ext/libev/README +2 -1
  14. data/ext/libev/ev.c +314 -225
  15. data/ext/libev/ev.h +90 -88
  16. data/ext/libev/ev_epoll.c +30 -16
  17. data/ext/libev/ev_kqueue.c +19 -9
  18. data/ext/libev/ev_linuxaio.c +642 -0
  19. data/ext/libev/ev_poll.c +19 -11
  20. data/ext/libev/ev_port.c +13 -6
  21. data/ext/libev/ev_select.c +4 -2
  22. data/ext/libev/ev_vars.h +14 -3
  23. data/ext/libev/ev_wrap.h +16 -0
  24. data/ext/nio4r/bytebuffer.c +429 -0
  25. data/ext/nio4r/extconf.rb +17 -30
  26. data/ext/nio4r/monitor.c +113 -49
  27. data/ext/nio4r/nio4r.h +11 -13
  28. data/ext/nio4r/org/nio4r/ByteBuffer.java +293 -0
  29. data/ext/nio4r/org/nio4r/Monitor.java +175 -0
  30. data/ext/nio4r/org/nio4r/Nio4r.java +22 -391
  31. data/ext/nio4r/org/nio4r/Selector.java +299 -0
  32. data/ext/nio4r/selector.c +155 -68
  33. data/lib/nio.rb +4 -4
  34. data/lib/nio/bytebuffer.rb +229 -0
  35. data/lib/nio/monitor.rb +73 -11
  36. data/lib/nio/selector.rb +64 -21
  37. data/lib/nio/version.rb +1 -1
  38. data/nio4r.gemspec +34 -20
  39. data/{tasks → rakelib}/extension.rake +4 -0
  40. data/{tasks → rakelib}/rspec.rake +2 -0
  41. data/{tasks → rakelib}/rubocop.rake +2 -0
  42. data/spec/nio/acceptables_spec.rb +5 -5
  43. data/spec/nio/bytebuffer_spec.rb +354 -0
  44. data/spec/nio/monitor_spec.rb +128 -79
  45. data/spec/nio/selectables/pipe_spec.rb +12 -3
  46. data/spec/nio/selectables/ssl_socket_spec.rb +61 -29
  47. data/spec/nio/selectables/tcp_socket_spec.rb +47 -34
  48. data/spec/nio/selectables/udp_socket_spec.rb +24 -7
  49. data/spec/nio/selector_spec.rb +65 -16
  50. data/spec/spec_helper.rb +12 -3
  51. data/spec/support/selectable_examples.rb +45 -18
  52. metadata +33 -23
  53. data/.rubocop_todo.yml +0 -35
  54. data/.travis.yml +0 -27
  55. data/LICENSE.txt +0 -20
  56. data/ext/libev/README.embed +0 -3
  57. data/ext/libev/test_libev_win32.c +0 -123
@@ -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(:example_peers) do
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) { example_peers.first }
24
- let(:writer) { example_peers.last }
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 { selector.register(reader, :r) }
29
- let(:peer) { selector.register(writer, :rw) }
30
- after { selector.close }
15
+ subject(:monitor) { selector.register(writer, :rw) }
16
+ subject(:peer) { selector.register(reader, :r) }
31
17
 
32
- before { example_peers } # open server and client automatically
33
- after { reader.close }
34
- after { writer.close }
18
+ before { reader }
19
+ before { writer }
20
+ after { reader.close }
21
+ after { writer.close }
22
+ after { selector.close }
35
23
 
36
- it "knows its interests" do
37
- expect(subject.interests).to eq(:r)
38
- expect(peer.interests).to eq(:rw)
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
- it "changes the interest set" do
42
- expect(peer.interests).not_to eq(:w)
43
- peer.interests = :w
44
- expect(peer.interests).to eq(:w)
45
- end
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
- it "knows its IO object" do
48
- expect(subject.io).to eq(reader)
49
- end
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
- it "knows its selector" do
52
- expect(subject.selector).to eq(selector)
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
- it "stores arbitrary values" do
56
- subject.value = 42
57
- expect(subject.value).to eq(42)
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
- it "knows what operations IO objects are ready for" do
61
- # For whatever odd reason this breaks unless we eagerly evaluate subject
62
- reader_monitor = subject
63
- writer_monitor = peer
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
- selected = selector.select(0)
66
- expect(selected).to include(writer_monitor)
80
+ it "can clear the last interest" do
81
+ monitor.interests = :w
82
+ expect(monitor.interests).to eq(:w)
67
83
 
68
- expect(writer_monitor.readiness).to eq(:w)
69
- expect(writer_monitor).not_to be_readable
70
- expect(writer_monitor).to be_writable
84
+ expect(monitor.remove_interest(:w)).to be_nil
85
+ expect(monitor.interests).to be_nil
86
+ end
71
87
 
72
- writer << "testing 1 2 3"
88
+ it "acts idempotently" do
89
+ monitor.interests = :w
90
+ expect(monitor.interests).to eq(:w)
73
91
 
74
- selected = selector.select(0)
75
- expect(selected).to include(reader_monitor)
92
+ expect(monitor.remove_interest(:r)).to eq(:w)
93
+ expect(monitor.interests).to eq(:w)
94
+ end
76
95
 
77
- expect(reader_monitor.readiness).to eq(:r)
78
- expect(reader_monitor).to be_readable
79
- expect(reader_monitor).not_to be_writable
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
- it "changes current interests with #interests=" do
83
- client = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
84
- monitor = selector.register(client, :r)
85
- expect(monitor.interests).to eq(:r)
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
- it "closes" do
91
- expect(subject).not_to be_closed
92
- expect(selector.registered?(reader)).to be_truthy
107
+ describe "#selector" do
108
+ it "knows its selector" do
109
+ expect(monitor.selector).to eq(selector)
110
+ end
111
+ end
93
112
 
94
- subject.close
95
- expect(subject).to be_closed
96
- expect(selector.registered?(reader)).to be_falsey
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
- it "closes even if the selector has been shutdown" do
100
- expect(subject).not_to be_closed
101
- selector.close # forces shutdown
102
- expect(subject).not_to be_closed
103
- subject.close
104
- expect(subject).to be_closed
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
- it "changes the interest set after monitor closed" do
108
- # check for changing the interests on the go after closed expected to fail
109
- expect(subject.interests).not_to eq(:rw)
110
- subject.close # forced shutdown
111
- expect { subject.interests = :rw }.to raise_error(TypeError)
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
- reader, pipe = IO.pipe
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
- _, writers = select [], [pipe], [], 0
33
+ cntr += 1
34
+ t = select [], [pipe], [], 0
29
35
  rescue Errno::EPIPE
30
36
  break
31
- end while writers && writers.include?(pipe)
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, if: RUBY_VERSION >= "1.9.0" do
5
- let(:tcp_port) { 34_567 }
5
+ RSpec.describe OpenSSL::SSL::SSLSocket do
6
+
7
+ require "openssl"
8
+
9
+ before(:all) do
10
+ @tls = []
11
+ end
6
12
 
7
- let(:ssl_key) { OpenSSL::PKey::RSA.new(1024) }
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(CN localhost)])
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 + (365 * 24 * 60 * 60)
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::SHA1.new)
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("localhost", tcp_port)
33
- client = TCPSocket.open("localhost", tcp_port)
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("localhost", tcp_port + 1)
56
- client = TCPSocket.new("localhost", tcp_port + 1)
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("localhost", tcp_port + 2)
76
- client = TCPSocket.new("localhost", tcp_port + 2)
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("localhost", tcp_port + 3)
96
- client = TCPSocket.open("localhost", tcp_port + 3)
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
- raise if writers.include? ssl_client
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
- # ssl_client.write_nonblock "X" * 1024
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
- # if select([], [ssl_client], [], 0)
134
- # pending "Failed to produce an unwritable socket"
135
- # end
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
- 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)
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
- it_behaves_like "an NIO selectable"
161
- it_behaves_like "an NIO selectable stream"
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