nio4r 2.0.0.pre → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rubocop.yml +31 -38
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +4 -21
  6. data/CHANGES.md +75 -42
  7. data/Gemfile +11 -3
  8. data/Guardfile +10 -0
  9. data/LICENSE.txt +1 -1
  10. data/README.md +32 -136
  11. data/Rakefile +2 -0
  12. data/examples/echo_server.rb +1 -0
  13. data/ext/libev/Changes +4 -13
  14. data/ext/libev/ev.c +100 -74
  15. data/ext/libev/ev.h +3 -3
  16. data/ext/libev/ev_epoll.c +6 -3
  17. data/ext/libev/ev_kqueue.c +8 -4
  18. data/ext/libev/ev_poll.c +6 -3
  19. data/ext/libev/ev_port.c +8 -4
  20. data/ext/libev/ev_select.c +4 -2
  21. data/ext/nio4r/bytebuffer.c +265 -257
  22. data/ext/nio4r/extconf.rb +2 -10
  23. data/ext/nio4r/monitor.c +93 -46
  24. data/ext/nio4r/nio4r.h +5 -15
  25. data/ext/nio4r/org/nio4r/ByteBuffer.java +193 -209
  26. data/ext/nio4r/org/nio4r/Monitor.java +164 -0
  27. data/ext/nio4r/org/nio4r/Nio4r.java +13 -391
  28. data/ext/nio4r/org/nio4r/Selector.java +278 -0
  29. data/ext/nio4r/selector.c +52 -52
  30. data/lib/nio.rb +3 -3
  31. data/lib/nio/bytebuffer.rb +179 -132
  32. data/lib/nio/monitor.rb +64 -4
  33. data/lib/nio/selector.rb +36 -13
  34. data/lib/nio/version.rb +1 -1
  35. data/nio4r.gemspec +25 -19
  36. data/spec/nio/acceptables_spec.rb +6 -4
  37. data/spec/nio/bytebuffer_spec.rb +323 -51
  38. data/spec/nio/monitor_spec.rb +122 -79
  39. data/spec/nio/selectables/pipe_spec.rb +5 -1
  40. data/spec/nio/selectables/ssl_socket_spec.rb +15 -12
  41. data/spec/nio/selectables/tcp_socket_spec.rb +42 -31
  42. data/spec/nio/selectables/udp_socket_spec.rb +2 -0
  43. data/spec/nio/selector_spec.rb +10 -4
  44. data/spec/spec_helper.rb +24 -3
  45. data/spec/support/selectable_examples.rb +7 -5
  46. data/tasks/extension.rake +2 -0
  47. data/tasks/rspec.rake +2 -0
  48. data/tasks/rubocop.rake +2 -0
  49. metadata +15 -11
  50. data/.rubocop_todo.yml +0 -35
@@ -1,113 +1,156 @@
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) { "localhost" }
8
+ let(:port) { next_available_tcp_port }
22
9
 
23
- let(:reader) { example_peers.first }
24
- let(:writer) { example_peers.last }
10
+ let(:reader) { TCPServer.new(addr, 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 "changes the interest set" do
33
+ expect(monitor.interests).not_to eq(:w)
34
+ monitor.interests = :w
35
+ expect(monitor.interests).to eq(:w)
36
+ end
46
37
 
47
- it "knows its IO object" do
48
- expect(subject.io).to eq(reader)
38
+ it "raises EOFError if interests are changed after the monitor is closed" do
39
+ monitor.close
40
+ expect { monitor.interests = :rw }.to raise_error(EOFError)
41
+ end
49
42
  end
50
43
 
51
- it "knows its selector" do
52
- expect(subject.selector).to eq(selector)
53
- end
44
+ describe "#add_interest" do
45
+ it "sets a new interest if it isn't currently registered" do
46
+ monitor.interests = :r
47
+ expect(monitor.interests).to eq(:r)
48
+
49
+ expect(monitor.add_interest(:w)).to eq(:rw)
50
+ expect(monitor.interests).to eq(:rw)
51
+ end
52
+
53
+ it "acts idempotently" do
54
+ monitor.interests = :r
55
+ expect(monitor.interests).to eq(:r)
56
+
57
+ expect(monitor.add_interest(:r)).to eq(:r)
58
+ expect(monitor.interests).to eq(:r)
59
+ end
54
60
 
55
- it "stores arbitrary values" do
56
- subject.value = 42
57
- expect(subject.value).to eq(42)
61
+ it "raises ArgumentError if given a bogus option" do
62
+ expect { monitor.add_interest(:derp) }.to raise_error(ArgumentError)
63
+ end
58
64
  end
59
65
 
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
66
+ describe "#remove_interest" do
67
+ it "removes an interest from the set" do
68
+ expect(monitor.interests).to eq(:rw)
69
+
70
+ expect(monitor.remove_interest(:r)).to eq(:w)
71
+ expect(monitor.interests).to eq(:w)
72
+ end
64
73
 
65
- selected = selector.select(0)
66
- expect(selected).to include(writer_monitor)
74
+ it "can clear the last interest" do
75
+ monitor.interests = :w
76
+ expect(monitor.interests).to eq(:w)
67
77
 
68
- expect(writer_monitor.readiness).to eq(:w)
69
- expect(writer_monitor).not_to be_readable
70
- expect(writer_monitor).to be_writable
78
+ expect(monitor.remove_interest(:w)).to be_nil
79
+ expect(monitor.interests).to be_nil
80
+ end
71
81
 
72
- writer << "testing 1 2 3"
82
+ it "acts idempotently" do
83
+ monitor.interests = :w
84
+ expect(monitor.interests).to eq(:w)
73
85
 
74
- selected = selector.select(0)
75
- expect(selected).to include(reader_monitor)
86
+ expect(monitor.remove_interest(:r)).to eq(:w)
87
+ expect(monitor.interests).to eq(:w)
88
+ end
76
89
 
77
- expect(reader_monitor.readiness).to eq(:r)
78
- expect(reader_monitor).to be_readable
79
- expect(reader_monitor).not_to be_writable
90
+ it "raises ArgumentError if given a bogus option" do
91
+ expect { monitor.add_interest(:derp) }.to raise_error(ArgumentError)
92
+ end
80
93
  end
81
94
 
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)
95
+ describe "#io" do
96
+ it "knows its IO object" do
97
+ expect(monitor.io).to eq(writer)
98
+ end
88
99
  end
89
100
 
90
- it "closes" do
91
- expect(subject).not_to be_closed
92
- expect(selector.registered?(reader)).to be_truthy
101
+ describe "#selector" do
102
+ it "knows its selector" do
103
+ expect(monitor.selector).to eq(selector)
104
+ end
105
+ end
93
106
 
94
- subject.close
95
- expect(subject).to be_closed
96
- expect(selector.registered?(reader)).to be_falsey
107
+ describe "#value=" do
108
+ it "stores arbitrary values" do
109
+ monitor.value = 42
110
+ expect(monitor.value).to eq(42)
111
+ end
97
112
  end
98
113
 
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
114
+ describe "#readiness" do
115
+ it "knows what operations IO objects are ready for" do
116
+ # For whatever odd reason this breaks unless we eagerly evaluate monitor
117
+ reader_peer = peer
118
+ writer_peer = monitor
119
+
120
+ selected = selector.select(0)
121
+ expect(selected).to include(writer_peer)
122
+
123
+ expect(writer_peer.readiness).to eq(:w)
124
+ expect(writer_peer).not_to be_readable
125
+ expect(writer_peer).to be_writable
126
+
127
+ writer << "testing 1 2 3"
128
+
129
+ selected = selector.select(0)
130
+ expect(selected).to include(reader_peer)
131
+
132
+ expect(reader_peer.readiness).to eq(:r)
133
+ expect(reader_peer).to be_readable
134
+ expect(reader_peer).not_to be_writable
135
+ end
105
136
  end
106
137
 
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)
138
+ describe "#close" do
139
+ it "closes" do
140
+ expect(monitor).not_to be_closed
141
+ expect(selector.registered?(writer)).to be_truthy
142
+
143
+ monitor.close
144
+ expect(monitor).to be_closed
145
+ expect(selector.registered?(writer)).to be_falsey
146
+ end
147
+
148
+ it "closes even if the selector has been shutdown" do
149
+ expect(monitor).not_to be_closed
150
+ selector.close # forces shutdown
151
+ expect(monitor).not_to be_closed
152
+ monitor.close
153
+ expect(monitor).to be_closed
154
+ end
112
155
  end
113
156
  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,8 +18,9 @@ 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()
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "spec_helper"
2
4
  require "openssl"
3
5
 
4
- RSpec.describe OpenSSL::SSL::SSLSocket, if: RUBY_VERSION >= "1.9.0" do
5
- let(:tcp_port) { 34_567 }
6
+ RSpec.describe OpenSSL::SSL::SSLSocket do
7
+ let(:addr) { "localhost" }
8
+ let(:port) { next_available_tcp_port }
6
9
 
7
10
  let(:ssl_key) { OpenSSL::PKey::RSA.new(1024) }
8
11
 
@@ -29,8 +32,8 @@ RSpec.describe OpenSSL::SSL::SSLSocket, if: RUBY_VERSION >= "1.9.0" do
29
32
  end
30
33
 
31
34
  let :readable_subject do
32
- server = TCPServer.new("localhost", tcp_port)
33
- client = TCPSocket.open("localhost", tcp_port)
35
+ server = TCPServer.new(addr, port)
36
+ client = TCPSocket.open(addr, port)
34
37
  peer = server.accept
35
38
 
36
39
  ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
@@ -52,8 +55,8 @@ RSpec.describe OpenSSL::SSL::SSLSocket, if: RUBY_VERSION >= "1.9.0" do
52
55
  end
53
56
 
54
57
  let :unreadable_subject do
55
- server = TCPServer.new("localhost", tcp_port + 1)
56
- client = TCPSocket.new("localhost", tcp_port + 1)
58
+ server = TCPServer.new(addr, port)
59
+ client = TCPSocket.new(addr, port)
57
60
  peer = server.accept
58
61
 
59
62
  ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
@@ -72,8 +75,8 @@ RSpec.describe OpenSSL::SSL::SSLSocket, if: RUBY_VERSION >= "1.9.0" do
72
75
  end
73
76
 
74
77
  let :writable_subject do
75
- server = TCPServer.new("localhost", tcp_port + 2)
76
- client = TCPSocket.new("localhost", tcp_port + 2)
78
+ server = TCPServer.new(addr, port)
79
+ client = TCPSocket.new(addr, port)
77
80
  peer = server.accept
78
81
 
79
82
  ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
@@ -92,8 +95,8 @@ RSpec.describe OpenSSL::SSL::SSLSocket, if: RUBY_VERSION >= "1.9.0" do
92
95
  end
93
96
 
94
97
  let :unwritable_subject do
95
- server = TCPServer.new("localhost", tcp_port + 3)
96
- client = TCPSocket.open("localhost", tcp_port + 3)
98
+ server = TCPServer.new(addr, port)
99
+ client = TCPSocket.new(addr, port)
97
100
  peer = server.accept
98
101
 
99
102
  ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
@@ -140,8 +143,8 @@ RSpec.describe OpenSSL::SSL::SSLSocket, if: RUBY_VERSION >= "1.9.0" do
140
143
  let :pair do
141
144
  pending "figure out why newly created sockets are selecting readable immediately"
142
145
 
143
- server = TCPServer.new("localhost", tcp_port + 4)
144
- client = TCPSocket.open("localhost", tcp_port + 4)
146
+ server = TCPServer.new(addr, port)
147
+ client = TCPSocket.new(addr, port)
145
148
  peer = server.accept
146
149
 
147
150
  ssl_peer = OpenSSL::SSL::SSLSocket.new(peer, ssl_server_context)
@@ -1,20 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "spec_helper"
2
4
 
3
5
  RSpec.describe TCPSocket do
4
- port_offset = 0
5
- let(:tcp_port) { 12_345 + (port_offset += 1) }
6
+ let(:addr) { "127.0.0.1" }
7
+ let(:port) { next_available_tcp_port }
6
8
 
7
9
  let :readable_subject do
8
- server = TCPServer.new("localhost", tcp_port)
9
- sock = TCPSocket.open("localhost", tcp_port)
10
+ server = TCPServer.new(addr, port)
11
+ sock = TCPSocket.new(addr, port)
10
12
  peer = server.accept
11
13
  peer << "data"
12
14
  sock
13
15
  end
14
16
 
15
17
  let :unreadable_subject do
16
- TCPServer.new("localhost", tcp_port)
17
- sock = TCPSocket.new("localhost", tcp_port)
18
+ TCPServer.new(addr, port)
19
+ sock = TCPSocket.new(addr, port)
18
20
 
19
21
  # Sanity check to make sure we actually produced an unreadable socket
20
22
  if select([sock], [], [], 0)
@@ -25,21 +27,25 @@ RSpec.describe TCPSocket do
25
27
  end
26
28
 
27
29
  let :writable_subject do
28
- TCPServer.new("localhost", tcp_port)
29
- TCPSocket.new("localhost", tcp_port)
30
+ TCPServer.new(addr, port)
31
+ TCPSocket.new(addr, port)
30
32
  end
31
33
 
32
34
  let :unwritable_subject do
33
- server = TCPServer.new("localhost", tcp_port)
34
- sock = TCPSocket.open("localhost", tcp_port)
35
- peer = server.accept
35
+ server = TCPServer.new(addr, port)
36
+ sock = TCPSocket.new(addr, port)
36
37
 
37
- begin
38
+ # TODO: close this socket
39
+ _peer = server.accept
40
+
41
+ loop do
38
42
  sock.write_nonblock "X" * 1024
39
- _, writers = select [], [sock], [], 0
40
- end while writers && writers.include?(sock)
43
+ _, writers = Kernel.select([], [sock], [], 0)
41
44
 
42
- # I think the kernel might manage to drain its buffer a bit even after
45
+ break unless writers && writers.include?(sock)
46
+ end
47
+
48
+ # HAX: I think the kernel might manage to drain its buffer a bit even after
43
49
  # the socket first goes unwritable. Attempt to sleep past this and then
44
50
  # attempt to write again
45
51
  sleep 0.1
@@ -59,8 +65,8 @@ RSpec.describe TCPSocket do
59
65
  end
60
66
 
61
67
  let :pair do
62
- server = TCPServer.new("localhost", tcp_port)
63
- client = TCPSocket.open("localhost", tcp_port)
68
+ server = TCPServer.new(addr, port)
69
+ client = TCPSocket.new(addr, port)
64
70
  [client, server.accept]
65
71
  end
66
72
 
@@ -69,20 +75,25 @@ RSpec.describe TCPSocket do
69
75
  it_behaves_like "an NIO bidirectional stream"
70
76
 
71
77
  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
- expect(selector.select(0.001)).to include monitor
84
- result = client.getsockopt(::Socket::SOL_SOCKET, ::Socket::SO_ERROR)
85
- expect(result.unpack("i").first).to be_zero
78
+ it "selects writable when connected", retry: 5 do # retry: Flaky on OS X
79
+ begin
80
+ server = TCPServer.new(addr, port)
81
+ selector = NIO::Selector.new
82
+
83
+ client = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
84
+ monitor = selector.register(client, :w)
85
+
86
+ expect do
87
+ client.connect_nonblock Socket.sockaddr_in(port, addr)
88
+ end.to raise_exception Errno::EINPROGRESS
89
+
90
+ expect(selector.select(0.001)).to include monitor
91
+ result = client.getsockopt(::Socket::SOL_SOCKET, ::Socket::SO_ERROR)
92
+ expect(result.unpack("i").first).to be_zero
93
+ ensure
94
+ server.close rescue nil
95
+ selector.close rescue nil
96
+ end
86
97
  end
87
98
  end
88
99
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "spec_helper"
2
4
 
3
5
  RSpec.describe UDPSocket do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "spec_helper"
2
4
  require "timeout"
3
5
 
@@ -11,6 +13,12 @@ RSpec.describe NIO::Selector do
11
13
  let(:reader) { pair.first }
12
14
  let(:writer) { pair.last }
13
15
 
16
+ context "backend" do
17
+ it "knows its backend" do
18
+ expect(subject.backend).to be_a Symbol
19
+ end
20
+ end
21
+
14
22
  context "register" do
15
23
  it "registers IO objects" do
16
24
  monitor = subject.register(reader, :r)
@@ -43,9 +51,7 @@ RSpec.describe NIO::Selector do
43
51
 
44
52
  it "reports if it is empty" do
45
53
  expect(subject).to be_empty
46
-
47
- monitor = subject.register(reader, :r)
48
-
54
+ subject.register(reader, :r)
49
55
  expect(subject).not_to be_empty
50
56
  end
51
57
 
@@ -96,7 +102,7 @@ RSpec.describe NIO::Selector do
96
102
 
97
103
  thread = Thread.new do
98
104
  started_at = Time.now
99
- expect(subject.select).to be_nil
105
+ expect(subject.select).to eq []
100
106
  Time.now - started_at
101
107
  end
102
108