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,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NIO
4
- VERSION = "1.2.1".freeze
4
+ VERSION = "2.5.3"
5
5
  end
@@ -1,28 +1,42 @@
1
- # -*- encoding: utf-8 -*-
2
- require File.expand_path("../lib/nio/version", __FILE__)
1
+ # frozen_string_literal: true
3
2
 
4
- Gem::Specification.new do |gem|
5
- gem.authors = ["Tony Arcieri"]
6
- gem.email = ["tony.arcieri@gmail.com"]
7
- gem.description = "New IO for Ruby"
8
- gem.summary = "NIO provides a high performance selector API for monitoring IO objects"
9
- gem.homepage = "https://github.com/celluloid/nio4r"
10
- gem.license = "MIT"
3
+ require File.expand_path("lib/nio/version", __dir__)
11
4
 
12
- gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
13
- gem.files = `git ls-files`.split("\n")
14
- gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
- gem.name = "nio4r"
16
- gem.require_paths = ["lib"]
17
- gem.version = NIO::VERSION
5
+ Gem::Specification.new do |spec|
6
+ spec.authors = ["Tony Arcieri"]
7
+ spec.email = ["bascule@gmail.com"]
8
+ spec.homepage = "https://github.com/socketry/nio4r"
9
+ spec.license = "MIT"
10
+ spec.summary = "New IO for Ruby"
11
+ spec.description = <<-DESCRIPTION.strip.gsub(/\s+/, " ")
12
+ Cross-platform asynchronous I/O primitives for scalable network clients
13
+ and servers. Inspired by the Java NIO API, but simplified for ease-of-use.
14
+ DESCRIPTION
15
+
16
+ spec.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
17
+ spec.files = `git ls-files`.split("\n")
18
+ spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ spec.name = "nio4r"
20
+ spec.require_paths = ["lib"]
21
+ spec.version = NIO::VERSION
22
+
23
+ spec.metadata = {
24
+ "bug_tracker_uri" => "https://github.com/socketry/nio4r/issues",
25
+ "changelog_uri" => "https://github.com/socketry/nio4r/blob/master/CHANGES.md",
26
+ "documentation_uri" => "https://www.rubydoc.info/gems/nio4r/#{spec.version}",
27
+ "source_code_uri" => "https://github.com/socketry/nio4r/tree/v#{spec.version}",
28
+ "wiki_uri" => "https://github.com/socketry/nio4r/wiki"
29
+ }
30
+
31
+ spec.required_ruby_version = ">= 2.4"
18
32
 
19
33
  if defined? JRUBY_VERSION
20
- gem.files << "lib/nio4r_ext.jar"
21
- gem.platform = "java"
34
+ spec.files << "lib/nio4r_ext.jar"
35
+ spec.platform = "java"
22
36
  else
23
- gem.extensions = ["ext/nio4r/extconf.rb"]
37
+ spec.extensions = ["ext/nio4r/extconf.rb"]
24
38
  end
25
39
 
26
- gem.add_development_dependency "rake"
27
- gem.add_development_dependency "bundler"
40
+ spec.add_development_dependency "bundler"
41
+ spec.add_development_dependency "rake"
28
42
  end
@@ -1,7 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  if defined? JRUBY_VERSION
2
4
  require "rake/javaextensiontask"
3
5
  Rake::JavaExtensionTask.new("nio4r_ext") do |ext|
4
6
  ext.ext_dir = "ext/nio4r"
7
+ ext.source_version = "1.8"
8
+ ext.target_version = "1.8"
5
9
  end
6
10
  else
7
11
  require "rake/extensiontask"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rspec/core/rake_task"
2
4
 
3
5
  RSpec::Core::RakeTask.new
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rubocop/rake_task"
2
4
 
3
5
  RuboCop::RakeTask.new
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "spec_helper"
2
4
 
3
5
  RSpec.describe "NIO acceptables" do
@@ -15,16 +17,14 @@ RSpec.describe "NIO acceptables" do
15
17
  end
16
18
 
17
19
  describe TCPServer do
18
- let(:tcp_port) { 23_456 }
19
-
20
20
  let :acceptable_subject do
21
- server = TCPServer.new("localhost", tcp_port)
22
- TCPSocket.open("localhost", tcp_port)
21
+ server = TCPServer.new("127.0.0.1", 0)
22
+ TCPSocket.open("127.0.0.1", server.local_address.ip_port)
23
23
  server
24
24
  end
25
25
 
26
26
  let :unacceptable_subject do
27
- TCPServer.new("localhost", tcp_port + 1)
27
+ TCPServer.new("127.0.0.1", 0)
28
28
  end
29
29
 
30
30
  it_behaves_like "an NIO acceptable"
@@ -0,0 +1,354 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe NIO::ByteBuffer do
6
+ let(:capacity) { 256 }
7
+ let(:example_string) { "Testing 1 2 3..." }
8
+ subject(:bytebuffer) { described_class.new(capacity) }
9
+
10
+ describe "#initialize" do
11
+ it "raises TypeError if given a bogus argument" do
12
+ expect { described_class.new(:symbols_are_bogus) }.to raise_error(TypeError)
13
+ end
14
+ end
15
+
16
+ describe "#clear" do
17
+ it "clears the buffer" do
18
+ bytebuffer << example_string
19
+ bytebuffer.clear
20
+
21
+ expect(bytebuffer.remaining).to eq capacity
22
+ end
23
+ end
24
+
25
+ describe "#position" do
26
+ it "defaults to zero" do
27
+ expect(bytebuffer.position).to be_zero
28
+ end
29
+ end
30
+
31
+ describe "#position=" do
32
+ let(:example_position) { 42 }
33
+
34
+ it "sets the buffer's position to a valid value" do
35
+ expect(bytebuffer.position).to be_zero
36
+ bytebuffer.position = example_position
37
+ expect(bytebuffer.position).to eq example_position
38
+ end
39
+
40
+ it "raises ArgumentError if the specified position is less than zero" do
41
+ expect { bytebuffer.position = -1 }.to raise_error(ArgumentError)
42
+ end
43
+
44
+ it "raises ArgumentError if the specified position exceeds the limit" do
45
+ expect { bytebuffer.position = capacity + 1 }.to raise_error(ArgumentError)
46
+ end
47
+ end
48
+
49
+ describe "#limit" do
50
+ it "defaults to the buffer's capacity" do
51
+ expect(bytebuffer.limit).to eq capacity
52
+ end
53
+ end
54
+
55
+ describe "#limit=" do
56
+ it "sets the buffer's limit to a valid value" do
57
+ bytebuffer.flip
58
+ expect(bytebuffer.limit).to be_zero
59
+
60
+ new_limit = capacity / 2
61
+ bytebuffer.limit = new_limit
62
+ expect(bytebuffer.limit).to eq new_limit
63
+ end
64
+
65
+ it "preserves position and mark if they're less than the new limit" do
66
+ bytebuffer << "four"
67
+ bytebuffer.mark
68
+ bytebuffer << "more"
69
+
70
+ bytebuffer.limit = capacity / 2
71
+ expect(bytebuffer.position).to eq 8
72
+ bytebuffer.reset
73
+ expect(bytebuffer.position).to eq 4
74
+ end
75
+
76
+ it "sets position to the new limit if the previous position is beyond the limit" do
77
+ bytebuffer << "four"
78
+ bytebuffer.limit = 2
79
+ expect(bytebuffer.position).to eq 2
80
+ end
81
+
82
+ it "clears the mark if the new limit is before the current mark" do
83
+ bytebuffer << "four"
84
+ bytebuffer.mark
85
+ bytebuffer.limit = 2
86
+ expect { bytebuffer.reset }.to raise_error(NIO::ByteBuffer::MarkUnsetError)
87
+ end
88
+
89
+ it "raises ArgumentError if specified limit is less than zero" do
90
+ expect { bytebuffer.limit = -1 }.to raise_error(ArgumentError)
91
+ end
92
+
93
+ it "raises ArgumentError if specified limit exceeds capacity" do
94
+ expect { bytebuffer.limit = capacity }.not_to raise_error
95
+ expect { bytebuffer.limit = capacity + 1 }.to raise_error(ArgumentError)
96
+ end
97
+ end
98
+
99
+ describe "#capacity" do
100
+ it "has the requested capacity" do
101
+ expect(bytebuffer.capacity).to eq capacity
102
+ end
103
+ end
104
+
105
+ describe "#remaining" do
106
+ it "calculates the number of bytes remaining" do
107
+ expect(bytebuffer.remaining).to eq capacity
108
+ bytebuffer << example_string
109
+ expect(bytebuffer.remaining).to eq(capacity - example_string.length)
110
+ end
111
+ end
112
+
113
+ describe "#full?" do
114
+ it "returns false when there is space remaining in the buffer" do
115
+ expect(bytebuffer).not_to be_full
116
+ end
117
+
118
+ it "returns true when the buffer is full" do
119
+ bytebuffer << "X" * capacity
120
+ expect(bytebuffer).to be_full
121
+ end
122
+ end
123
+
124
+ describe "#get" do
125
+ it "reads all remaining data if no length is given" do
126
+ bytebuffer << example_string
127
+ bytebuffer.flip
128
+
129
+ expect(bytebuffer.get).to eq example_string
130
+ end
131
+
132
+ it "reads zeroes from a newly initialized buffer" do
133
+ expect(bytebuffer.get(capacity)).to eq("\0" * capacity)
134
+ end
135
+
136
+ it "advances position as data is read" do
137
+ bytebuffer << "First"
138
+ bytebuffer << "Second"
139
+ bytebuffer << "Third"
140
+ bytebuffer.flip
141
+
142
+ expect(bytebuffer.position).to be_zero
143
+ expect(bytebuffer.get(10)).to eq "FirstSecon"
144
+ expect(bytebuffer.position).to eq 10
145
+ end
146
+
147
+ it "raises NIO::ByteBuffer::UnderflowError if there is not enough data in the buffer" do
148
+ bytebuffer << example_string
149
+ bytebuffer.flip
150
+
151
+ expect { bytebuffer.get(example_string.length + 1) }.to raise_error(NIO::ByteBuffer::UnderflowError)
152
+ expect(bytebuffer.get(example_string.length)).to eq example_string
153
+ end
154
+ end
155
+
156
+ describe "#[]" do
157
+ it "obtains bytes at a given index without altering position" do
158
+ bytebuffer << example_string
159
+ expect(bytebuffer[7]).to eq example_string.bytes[7]
160
+ expect(bytebuffer.position).to eq example_string.length
161
+ end
162
+
163
+ it "raises ArgumentError if the index is less than zero" do
164
+ expect { bytebuffer[-1] }.to raise_error(ArgumentError)
165
+ end
166
+
167
+ it "raises ArgumentError if the index exceeds the limit" do
168
+ bytebuffer << example_string
169
+ bytebuffer.flip
170
+ expect(bytebuffer[bytebuffer.limit - 1]).to eq example_string.bytes.last
171
+ expect { bytebuffer[bytebuffer.limit] }.to raise_error(ArgumentError)
172
+ end
173
+ end
174
+
175
+ describe "#<<" do
176
+ it "adds strings to the buffer" do
177
+ bytebuffer << example_string
178
+ expect(bytebuffer.position).to eq example_string.length
179
+ expect(bytebuffer.limit).to eq capacity
180
+ end
181
+
182
+ it "raises TypeError if given a non-String type" do
183
+ expect { bytebuffer << 42 }.to raise_error(TypeError)
184
+ expect { bytebuffer << nil }.to raise_error(TypeError)
185
+ end
186
+
187
+ it "raises NIO::ByteBuffer::OverflowError if the buffer is full" do
188
+ bytebuffer << "X" * (capacity - 1)
189
+ expect { bytebuffer << "X" }.not_to raise_error
190
+ expect { bytebuffer << "X" }.to raise_error(NIO::ByteBuffer::OverflowError)
191
+ end
192
+ end
193
+
194
+ describe "#flip" do
195
+ it "flips the bytebuffer" do
196
+ bytebuffer << example_string
197
+ expect(bytebuffer.position).to eql example_string.length
198
+
199
+ expect(bytebuffer.flip).to eq bytebuffer
200
+
201
+ expect(bytebuffer.position).to be_zero
202
+ expect(bytebuffer.limit).to eq example_string.length
203
+ expect(bytebuffer.get).to eq example_string
204
+ end
205
+
206
+ it "sets remaining to the previous position" do
207
+ bytebuffer << example_string
208
+ previous_position = bytebuffer.position
209
+ expect(bytebuffer.remaining).to eq(capacity - previous_position)
210
+ expect(bytebuffer.flip.remaining).to eq previous_position
211
+ end
212
+
213
+ it "sets limit to the previous position" do
214
+ bytebuffer << example_string
215
+ expect(bytebuffer.limit).to eql(capacity)
216
+
217
+ previous_position = bytebuffer.position
218
+ expect(bytebuffer.flip.limit).to eql previous_position
219
+ end
220
+ end
221
+
222
+ describe "#rewind" do
223
+ it "rewinds the buffer leaving the limit intact" do
224
+ bytebuffer << example_string
225
+ expect(bytebuffer.rewind).to eq bytebuffer
226
+
227
+ expect(bytebuffer.position).to be_zero
228
+ expect(bytebuffer.limit).to eq capacity
229
+ end
230
+ end
231
+
232
+ describe "#mark" do
233
+ it "returns self" do
234
+ expect(bytebuffer.mark).to eql bytebuffer
235
+ end
236
+ end
237
+
238
+ describe "#reset" do
239
+ it "returns to a previously marked position" do
240
+ bytebuffer << "First"
241
+ expected_position = bytebuffer.position
242
+
243
+ expect(bytebuffer.mark).to eq bytebuffer
244
+ bytebuffer << "Second"
245
+ expect(bytebuffer.position).not_to eq expected_position
246
+ expect(bytebuffer.reset.position).to eq expected_position
247
+ end
248
+
249
+ it "raises NIO::ByteBuffer::MarkUnsetError unless mark has been set" do
250
+ expect { bytebuffer.reset }.to raise_error(NIO::ByteBuffer::MarkUnsetError)
251
+ end
252
+ end
253
+
254
+ describe "#compact" do
255
+ let(:first_string) { "CompactMe" }
256
+ let(:second_string) { "Leftover" }
257
+
258
+ it "copies data from the current position to the beginning of the buffer" do
259
+ bytebuffer << first_string << second_string
260
+ bytebuffer.position = first_string.length
261
+ bytebuffer.limit = first_string.length + second_string.length
262
+ bytebuffer.compact
263
+
264
+ expect(bytebuffer.position).to eq second_string.length
265
+ expect(bytebuffer.limit).to eq capacity
266
+ expect(bytebuffer.flip.get).to eq second_string
267
+ end
268
+ end
269
+
270
+ describe "#each" do
271
+ it "iterates over data in the buffer" do
272
+ bytebuffer << example_string
273
+ bytebuffer.flip
274
+
275
+ bytes = []
276
+ bytebuffer.each { |byte| bytes << byte }
277
+ expect(bytes).to eq example_string.bytes
278
+ end
279
+ end
280
+
281
+ describe "#inspect" do
282
+ it "inspects the buffer offsets" do
283
+ regex = /\A#<NIO::ByteBuffer:.*? @position=0 @limit=#{capacity} @capacity=#{capacity}>\z/
284
+ expect(bytebuffer.inspect).to match(regex)
285
+ end
286
+ end
287
+
288
+ context "I/O" do
289
+ let(:addr) { "127.0.0.1" }
290
+ let(:server) { TCPServer.new(addr, 0) }
291
+ let(:port) { server.local_address.ip_port }
292
+ let(:client) { TCPSocket.new(addr, port) }
293
+ let(:peer) { server_thread.value }
294
+
295
+ let(:server_thread) do
296
+ server
297
+
298
+ thread = Thread.new { server.accept }
299
+ Thread.pass while thread.status && thread.status != "sleep"
300
+
301
+ thread
302
+ end
303
+
304
+ before do
305
+ server_thread
306
+ client
307
+ end
308
+
309
+ after do
310
+ server_thread.kill if server_thread.alive?
311
+
312
+ server.close rescue nil
313
+ client.close rescue nil
314
+ peer.close rescue nil
315
+ end
316
+
317
+ describe "#read_from" do
318
+ it "reads data into the buffer" do
319
+ client.write(example_string)
320
+ expect(bytebuffer.read_from(peer)).to eq example_string.length
321
+ bytebuffer.flip
322
+
323
+ expect(bytebuffer.get).to eq example_string
324
+ end
325
+
326
+ it "raises NIO::ByteBuffer::OverflowError if the buffer is already full" do
327
+ client.write(example_string)
328
+ bytebuffer << "X" * capacity
329
+ expect { bytebuffer.read_from(peer) }.to raise_error(NIO::ByteBuffer::OverflowError)
330
+ end
331
+
332
+ it "returns 0 if no data is available" do
333
+ expect(bytebuffer.read_from(peer)).to eq 0
334
+ end
335
+ end
336
+
337
+ describe "#write_to" do
338
+ it "writes data from the buffer" do
339
+ bytebuffer << example_string
340
+ bytebuffer.flip
341
+
342
+ expect(bytebuffer.write_to(client)).to eq example_string.length
343
+ client.close
344
+
345
+ expect(peer.read(example_string.length)).to eq example_string
346
+ end
347
+
348
+ it "raises NIO::ByteBuffer::UnderflowError if the buffer is out of data" do
349
+ bytebuffer.flip
350
+ expect { bytebuffer.write_to(peer) }.to raise_error(NIO::ByteBuffer::UnderflowError)
351
+ end
352
+ end
353
+ end
354
+ end