nio4r 0.2.2-java → 0.3.0-java

Sign up to get free protection for your applications and to get access to all the features.
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