nio4r 0.2.2-java → 0.3.0-java

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/ext/nio4r/selector.c CHANGED
@@ -209,9 +209,9 @@ static VALUE NIO_Selector_register_synchronized(VALUE *args)
209
209
  rb_raise(rb_eArgError, "this IO is already registered with selector");
210
210
 
211
211
  /* Create a new NIO::Monitor */
212
- monitor_args[0] = self;
213
- monitor_args[1] = io;
214
- monitor_args[2] = interests;
212
+ monitor_args[0] = io;
213
+ monitor_args[1] = interests;
214
+ monitor_args[2] = self;
215
215
 
216
216
  monitor = rb_class_new_instance(3, monitor_args, cNIO_Monitor);
217
217
  rb_hash_aset(selectables, io, monitor);
@@ -239,7 +239,7 @@ static VALUE NIO_Selector_deregister_synchronized(VALUE *args)
239
239
  monitor = rb_hash_delete(selectables, io);
240
240
 
241
241
  if(monitor != Qnil) {
242
- rb_funcall(monitor, rb_intern("close"), 0, 0);
242
+ rb_funcall(monitor, rb_intern("close"), 1, Qfalse);
243
243
  }
244
244
 
245
245
  return monitor;
@@ -262,6 +262,10 @@ static VALUE NIO_Selector_select(int argc, VALUE *argv, VALUE self)
262
262
 
263
263
  rb_scan_args(argc, argv, "01", &timeout);
264
264
 
265
+ if(timeout != Qnil && NUM2DBL(timeout) < 0) {
266
+ rb_raise(rb_eArgError, "time interval must be positive");
267
+ }
268
+
265
269
  args[0] = self;
266
270
  args[1] = timeout;
267
271
 
@@ -280,6 +284,10 @@ static VALUE NIO_Selector_select_each(int argc, VALUE *argv, VALUE self)
280
284
 
281
285
  rb_scan_args(argc, argv, "01", &timeout);
282
286
 
287
+ if(timeout != Qnil && NUM2DBL(timeout) < 0) {
288
+ rb_raise(rb_eArgError, "time interval must be positive");
289
+ }
290
+
283
291
  args[0] = self;
284
292
  args[1] = timeout;
285
293
 
@@ -290,12 +298,21 @@ static VALUE NIO_Selector_select_each(int argc, VALUE *argv, VALUE self)
290
298
  static VALUE NIO_Selector_select_synchronized(VALUE *args)
291
299
  {
292
300
  struct NIO_Selector *selector;
293
- int ready = NIO_Selector_fill_ready_buffer(args);
301
+ int i, ready = NIO_Selector_fill_ready_buffer(args);
294
302
 
295
303
  Data_Get_Struct(args[0], struct NIO_Selector, selector);
296
304
 
297
305
  if(ready > 0) {
298
- return rb_ary_new4(ready, selector->ready_buffer);
306
+ if(rb_block_given_p()) {
307
+ for(i = 0; i < ready; i++) {
308
+ rb_yield(selector->ready_buffer[i]);
309
+ }
310
+
311
+ return INT2NUM(ready);
312
+ } else {
313
+ /* new4 memcpys the ready buffer */
314
+ return rb_ary_new4(ready, selector->ready_buffer);
315
+ }
299
316
  } else {
300
317
  return Qnil;
301
318
  }
@@ -335,7 +352,8 @@ static int NIO_Selector_fill_ready_buffer(VALUE *args)
335
352
  #if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_ALONE)
336
353
  /* Implement the optional timeout (if any) as a ev_timer */
337
354
  if(timeout != Qnil) {
338
- selector->timer.repeat = NUM2DBL(timeout);
355
+ /* It seems libev is not a fan of timers being zero, so fudge a little */
356
+ selector->timer.repeat = NUM2DBL(timeout) + 0.0001;
339
357
  ev_timer_again(selector->ev_loop, &selector->timer);
340
358
  } else {
341
359
  ev_timer_stop(selector->ev_loop, &selector->timer);
@@ -455,7 +473,17 @@ static void NIO_Selector_wakeup_callback(struct ev_loop *ev_loop, struct ev_io *
455
473
 
456
474
  /* This gets called from individual monitors. We must be careful here because
457
475
  the GIL isn't held, so we must rely only on standard C and can't touch
458
- anything Ruby-related */
476
+ anything Ruby-related.
477
+
478
+ It's scary because there's a VALUE here, and VALUEs are a Ruby thing,
479
+ however we're not going to dereference that VALUE or attempt to do anything
480
+ with it. We just treat it as completely opaque until we have the GIL back.
481
+
482
+ In order for this function to even get called, the monitor the VALUE points to
483
+ must be attached to this Selector, and if it's attached to this Selector
484
+ then we hold a reference to it in the @selectables instance variable, so
485
+ there's no danger of this monitor getting garbage collected before we have
486
+ the GIL back as we hold a reference. */
459
487
  void NIO_Selector_handle_event(struct NIO_Selector *selector, VALUE monitor, int revents)
460
488
  {
461
489
  /* Grow the ready buffer if it's too small */
data/lib/nio.rb CHANGED
@@ -15,16 +15,13 @@ if ENV["NIO4R_PURE"]
15
15
  require 'nio/selector'
16
16
  NIO::ENGINE = 'select'
17
17
  else
18
+ require 'nio4r_ext'
19
+
18
20
  if defined?(JRUBY_VERSION)
19
21
  require 'java'
20
- require 'nio/jruby/monitor'
21
- require 'nio/jruby/selector'
22
+ org.nio4r.Nio4r.new.load(JRuby.runtime, false)
22
23
  NIO::ENGINE = 'java'
23
24
  else
24
- require 'nio4r_ext'
25
25
  NIO::ENGINE = 'libev'
26
26
  end
27
- end
28
-
29
- # TIMTOWTDI!!!
30
- Nio = NIO
27
+ end
data/lib/nio/monitor.rb CHANGED
@@ -1,12 +1,22 @@
1
1
  module NIO
2
2
  # Monitors watch IO objects for specific events
3
3
  class Monitor
4
- attr_reader :io, :interests
4
+ attr_reader :io, :interests, :selector
5
5
  attr_accessor :value, :readiness
6
6
 
7
7
  # :nodoc
8
- def initialize(io, interests)
9
- @io, @interests = io, interests
8
+ def initialize(io, interests, selector)
9
+ unless io.is_a?(IO)
10
+ if IO.respond_to? :try_convert
11
+ io = IO.try_convert(io)
12
+ elsif io.respond_to? :to_io
13
+ io = io.to_io
14
+ end
15
+
16
+ raise TypeError, "can't convert #{io.class} into IO" unless io.is_a? IO
17
+ end
18
+
19
+ @io, @interests, @selector = io, interests, selector
10
20
  @closed = false
11
21
  end
12
22
 
@@ -25,8 +35,9 @@ module NIO
25
35
  def closed?; @closed; end
26
36
 
27
37
  # Deactivate this monitor
28
- def close
38
+ def close(deregister = true)
29
39
  @closed = true
40
+ @selector.deregister(io) if deregister
30
41
  end
31
42
  end
32
43
  end
data/lib/nio/selector.rb CHANGED
@@ -20,7 +20,7 @@ module NIO
20
20
  @lock.synchronize do
21
21
  raise ArgumentError, "this IO is already registered with the selector" if @selectables[io]
22
22
 
23
- monitor = Monitor.new(io, interest)
23
+ monitor = Monitor.new(io, interest, self)
24
24
  @selectables[io] = monitor
25
25
 
26
26
  monitor
@@ -31,7 +31,7 @@ module NIO
31
31
  def deregister(io)
32
32
  @lock.synchronize do
33
33
  monitor = @selectables.delete io
34
- monitor.close if monitor
34
+ monitor.close(false) if monitor and not monitor.closed?
35
35
  monitor
36
36
  end
37
37
  end
@@ -54,7 +54,12 @@ module NIO
54
54
  ready_readers, ready_writers = Kernel.select readers, writers, [], timeout
55
55
  return unless ready_readers # timeout or wakeup
56
56
 
57
- results = []
57
+ if block_given?
58
+ result = 0
59
+ else
60
+ result = []
61
+ end
62
+
58
63
  ready_readers.each do |io|
59
64
  if io == @wakeup
60
65
  # Clear all wakeup signals we've received by reading them
@@ -71,7 +76,13 @@ module NIO
71
76
  else
72
77
  monitor = @selectables[io]
73
78
  monitor.readiness = :r
74
- results << monitor
79
+
80
+ if block_given?
81
+ yield monitor
82
+ result += 1
83
+ else
84
+ result << monitor
85
+ end
75
86
  end
76
87
  end
77
88
 
@@ -82,11 +93,17 @@ module NIO
82
93
  ios.each do |io|
83
94
  monitor = @selectables[io]
84
95
  monitor.readiness = readiness
85
- results << monitor
96
+
97
+ if block_given?
98
+ yield monitor
99
+ result += 1
100
+ else
101
+ result << monitor
102
+ end
86
103
  end
87
104
  end
88
105
 
89
- results
106
+ result
90
107
  end
91
108
  end
92
109
 
data/lib/nio/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module NIO
2
- VERSION = "0.2.2"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/nio4r_ext.jar ADDED
Binary file
data/nio4r.gemspec CHANGED
@@ -15,8 +15,9 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = NIO::VERSION
17
17
  # gem.extensions = ["ext/nio4r/extconf.rb"]
18
- gem.platform = 'java'
19
- gem.add_development_dependency "rake-compiler", "~> 0.7.9"
18
+ gem.platform = 'java'
19
+
20
+ gem.add_development_dependency "rake-compiler"
20
21
  gem.add_development_dependency "rake"
21
- gem.add_development_dependency "rspec", "~> 2.7.0"
22
+ gem.add_development_dependency "rspec"
22
23
  end
@@ -0,0 +1,30 @@
1
+ describe "NIO acceptables" do
2
+ shared_context "an NIO acceptable" do
3
+ let(:selector) { NIO::Selector.new }
4
+
5
+ it "selects for read readiness" do
6
+ waiting_monitor = selector.register(unacceptable_subject, :r)
7
+ ready_monitor = selector.register(acceptable_subject, :r)
8
+
9
+ ready_monitors = selector.select
10
+ ready_monitors.should include ready_monitor
11
+ ready_monitors.should_not include waiting_monitor
12
+ end
13
+ end
14
+
15
+ describe TCPServer do
16
+ let(:tcp_port) { 23456 }
17
+
18
+ let :acceptable_subject do
19
+ server = TCPServer.new("localhost", tcp_port)
20
+ TCPSocket.open("localhost", tcp_port)
21
+ server
22
+ end
23
+
24
+ let :unacceptable_subject do
25
+ TCPServer.new("localhost", tcp_port + 1)
26
+ end
27
+
28
+ it_behaves_like "an NIO acceptable"
29
+ end
30
+ end
@@ -1,21 +1,26 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe NIO::Monitor do
4
- let :readable do
5
- reader, writer = IO.pipe
6
- writer << "have some data"
7
- reader
8
- end
4
+ let(:pipes) { IO.pipe }
5
+ let(:reader) { pipes.first }
6
+ let(:writer) { pipes.last }
7
+ let(:selector) { NIO::Selector.new }
8
+
9
+ subject { selector.register(reader, :rw) }
10
+ let(:peer) { selector.register(writer, :rw) }
11
+ after { selector.close }
9
12
 
10
- let :selector do
11
- NIO::Selector.new
13
+ it "knows its interests" do
14
+ subject.interests.should == :rw
15
+ peer.interests.should == :rw
12
16
  end
13
17
 
14
- # Monitors are created by registering IO objects or channels with a selector
15
- subject { selector.register(readable, :r) }
18
+ it "knows its IO object" do
19
+ subject.io.should == reader
20
+ end
16
21
 
17
- it "knows its interests" do
18
- subject.interests.should == :r
22
+ it "knows its selector" do
23
+ subject.selector.should == selector
19
24
  end
20
25
 
21
26
  it "stores arbitrary values" do
@@ -23,24 +28,34 @@ describe NIO::Monitor do
23
28
  subject.value.should == 42
24
29
  end
25
30
 
26
- it "knows what IO objects are ready for" do
27
- # Perhaps let bindings are just confusing me but they're not producing
28
- # what I want. Manually doing the setup here does
29
- # FIXME: Hey RSpec wizards! Fix this!
30
- reader, writer = IO.pipe
31
+ it "knows what operations IO objects are ready for" do
32
+ # For whatever odd reason this breaks unless we eagerly evaluate subject
33
+ reader_monitor, writer_monitor = subject, peer
34
+
35
+ selected = selector.select(0)
36
+ selected.should_not include(reader_monitor)
37
+ selected.should include(writer_monitor)
38
+
39
+ writer_monitor.readiness.should == :w
40
+ writer_monitor.should_not be_readable
41
+ writer_monitor.should be_writable
42
+
31
43
  writer << "loldata"
32
- selector = NIO::Selector.new
33
- subject = selector.register(reader, :r)
34
44
 
35
- # Here's where the spec really begins
36
- selector.select(1).should include(subject)
37
- subject.readiness.should == :r
38
- subject.should be_readable
45
+ selected = selector.select(0)
46
+ selected.should include(reader_monitor)
47
+
48
+ reader_monitor.readiness.should == :r
49
+ reader_monitor.should be_readable
50
+ reader_monitor.should_not be_writable
39
51
  end
40
52
 
41
53
  it "closes" do
42
54
  subject.should_not be_closed
55
+ selector.registered?(reader).should be_true
56
+
43
57
  subject.close
44
58
  subject.should be_closed
59
+ selector.registered?(reader).should be_false
45
60
  end
46
61
  end
@@ -0,0 +1,178 @@
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 pair.first end
43
+ let :readable_subject do
44
+ pipe, peer = pair
45
+ peer << "data"
46
+ pipe
47
+ end
48
+
49
+ let :writable_subject do pair.last end
50
+ let :unwritable_subject do
51
+ reader, pipe = IO.pipe
52
+
53
+ begin
54
+ pipe.write_nonblock "JUNK IN THE TUBES"
55
+ _, writers = select [], [pipe], [], 0
56
+ rescue Errno::EPIPE
57
+ break
58
+ end while writers and writers.include? pipe
59
+
60
+ pipe
61
+ end
62
+
63
+ it_behaves_like "an NIO selectable"
64
+ it_behaves_like "an NIO selectable stream"
65
+ end
66
+
67
+ describe TCPSocket do
68
+ let(:tcp_port) { 12345 }
69
+
70
+ let :readable_subject do
71
+ server = TCPServer.new("localhost", tcp_port)
72
+ sock = TCPSocket.open("localhost", tcp_port)
73
+ peer = server.accept
74
+ peer << "data"
75
+ sock
76
+ end
77
+
78
+ let :unreadable_subject do
79
+ TCPServer.new("localhost", tcp_port + 1)
80
+ sock = TCPSocket.new("localhost", tcp_port + 1)
81
+
82
+ # Sanity check to make sure we actually produced an unreadable socket
83
+ if select([sock], [], [], 0)
84
+ pending "Failed to produce an unreadable socket"
85
+ end
86
+
87
+ sock
88
+ end
89
+
90
+ let :writable_subject do
91
+ TCPServer.new("localhost", tcp_port + 2)
92
+ TCPSocket.new("localhost", tcp_port + 2)
93
+ end
94
+
95
+ let :unwritable_subject do
96
+ server = TCPServer.new("localhost", tcp_port + 3)
97
+ sock = TCPSocket.open("localhost", tcp_port + 3)
98
+ peer = server.accept
99
+
100
+ begin
101
+ sock.write_nonblock "X" * 1024
102
+ _, writers = select [], [sock], [], 0
103
+ end while writers and writers.include? sock
104
+
105
+ # I think the kernel might manage to drain its buffer a bit even after
106
+ # the socket first goes unwritable. Attempt to sleep past this and then
107
+ # attempt to write again
108
+ sleep 0.1
109
+
110
+ # Once more for good measure!
111
+ begin
112
+ sock.write_nonblock "X" * 1024
113
+ rescue Errno::EWOULDBLOCK
114
+ end
115
+
116
+ # Sanity check to make sure we actually produced an unwritable socket
117
+ if select([], [sock], [], 0)
118
+ pending "Failed to produce an unwritable socket"
119
+ end
120
+
121
+ sock
122
+ end
123
+
124
+ let :pair do
125
+ server = TCPServer.new("localhost", tcp_port + 4)
126
+ client = TCPSocket.open("localhost", tcp_port + 4)
127
+ [client, server.accept]
128
+ end
129
+
130
+ it_behaves_like "an NIO selectable"
131
+ it_behaves_like "an NIO selectable stream"
132
+
133
+ it "selects writable when connected" do
134
+ server = TCPServer.new('127.0.0.1', tcp_port + 5)
135
+
136
+ client = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
137
+ monitor = selector.register(client, :w)
138
+
139
+ expect do
140
+ client.connect_nonblock Socket.sockaddr_in(tcp_port + 5, '127.0.0.1')
141
+ end.to raise_exception Errno::EINPROGRESS
142
+
143
+ selector.select(0).should include monitor
144
+ result = client.getsockopt(::Socket::SOL_SOCKET, ::Socket::SO_ERROR)
145
+ result.unpack('i').first.should be_zero
146
+ end
147
+ end
148
+
149
+ describe UDPSocket do
150
+ let(:udp_port) { 23456 }
151
+
152
+ let :readable_subject do
153
+ sock = UDPSocket.new
154
+ sock.bind('localhost', udp_port)
155
+
156
+ peer = UDPSocket.new
157
+ peer.send("hi there", 0, 'localhost', udp_port)
158
+
159
+ sock
160
+ end
161
+
162
+ let :unreadable_subject do
163
+ sock = UDPSocket.new
164
+ sock.bind('localhost', udp_port + 1)
165
+ sock
166
+ end
167
+
168
+ let :writable_subject do
169
+ pending "come up with a writable UDPSocket example"
170
+ end
171
+
172
+ let :unwritable_subject do
173
+ pending "come up with a UDPSocket that's blocked on writing"
174
+ end
175
+
176
+ it_behaves_like "an NIO selectable"
177
+ end
178
+ end