nio4r 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ 0.2.0
2
+ -----
3
+ * NIO::Monitor#readiness API to query readiness, along with #readable? and
4
+ #writable? helper methods
5
+ * NIO::Selector#select_each API which avoids memory allocations if possible
6
+ * Bugfixes for the JRuby implementation
7
+
8
+ 0.1.0
9
+ -----
10
+ * Initial release. Merry Christmas!
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'http://rubygems.org'
1
+ source :rubygems
2
2
 
3
3
  # Specify your gem's dependencies in nio4r.gemspec
4
4
  gemspec
data/README.md CHANGED
@@ -32,56 +32,104 @@ Platform notes:
32
32
 
33
33
  * MRI/YARV and Rubinius implement nio4j with a C extension based on libev,
34
34
  which provides a high performance binding to native IO APIs
35
- * JRuby uses a special backend based on Java NIO which provides good performance
36
- even when monitoring large numbers of IO objects
35
+ * JRuby uses a special backend based on the high performance Java NIO subsystem
37
36
  * A pure Ruby implementation is also provided for Ruby implementations which
38
37
  don't implement the MRI C extension API
39
38
 
40
39
  Usage
41
40
  -----
42
41
 
42
+ ### Selectors
43
+
43
44
  The NIO::Selector class is the main API provided by nio4r. Use it where you
44
45
  might otherwise use Kernel.select, but want to monitor the same set of IO
45
46
  objects across multiple select calls rather than having to reregister them
46
47
  every single time:
47
48
 
48
- selector = NIO::Selector.new
49
- reader, writer = IO.pipe
50
- monitor = selector.register(reader, :r)
51
-
52
- You can monitor IO objects for read readiness (with the :r parameter), write
53
- readiness (with the :w parameter), or both (with :rw). After registering an
54
- IO object with the selector, you'll get a NIO::Monitor object which you can
55
- use for managing how a particular IO object is being monitored. Monitors will
56
- store an arbitrary value of your choice, which provides an easy way to implement
57
- callbacks:
58
-
59
- >> monitor = selector.register(reader, :r)
60
- => #<NIO::Monitor:0xfbc>
61
- >> monitor.value = proc { puts "Got some data: #{monitor.io.read_nonblock(4096)}" }
62
- => #<Proc:0x1000@(irb):4>
63
- >> writer << "Hi there!"
64
- => #<IO:0x103c>
65
- >> ready = selector.select
66
- => [#<NIO::Monitor:0xfbc>]
67
- >> ready.each { |m| m.value.call }
68
- Got some data: Hi there!
69
- => [#<NIO::Monitor:0xfbc>]
49
+ ```ruby
50
+ require 'nio'
51
+
52
+ selector = NIO::Selector.new
53
+ ```
54
+
55
+ To monitor IO objects, attach them to the selector with the NIO::Selector#register
56
+ method, monitoring them for read readiness with the :r parameter, write
57
+ readiness with the :w parameter, or both with :rw.
58
+
59
+ ```ruby
60
+ >> reader, writer = IO.pipe
61
+ => [#<IO:0xf30>, #<IO:0xf34>]
62
+ >> monitor = selector.register(reader, :r)
63
+ => #<NIO::Monitor:0xfbc>
64
+ ```
65
+
66
+ After registering an IO object with the selector, you'll get a NIO::Monitor
67
+ object which you can use for managing how a particular IO object is being
68
+ monitored. Monitors will store an arbitrary value of your choice, which
69
+ provides an easy way to implement callbacks:
70
+
71
+ ```ruby
72
+ >> monitor = selector.register(reader, :r)
73
+ => #<NIO::Monitor:0xfbc>
74
+ >> monitor.value = proc { puts "Got some data: #{monitor.io.read_nonblock(4096)}" }
75
+ => #<Proc:0x1000@(irb):4>
76
+ ```
70
77
 
71
78
  The main method of importance is NIO::Selector#select, which monitors all
72
79
  registered IO objects and returns an array of monitors that are ready.
80
+
81
+ ```ruby
82
+ >> writer << "Hi there!"
83
+ => #<IO:0x103c>
84
+ >> ready = selector.select
85
+ => [#<NIO::Monitor:0xfbc>]
86
+ >> ready.each { |m| m.value.call }
87
+ Got some data: Hi there!
88
+ => [#<NIO::Monitor:0xfbc>]
89
+ ```
90
+
73
91
  By default, NIO::Selector#select will block indefinitely until one of the IO
74
92
  objects being monitored becomes ready. However, you can also pass a timeout to
75
93
  wait in seconds to NIO::Selector#select just like you can with Kernel.select:
76
94
 
77
- ready = selector.select(15) # Wait 15 seconds
95
+ ```ruby
96
+ ready = selector.select(15) # Wait 15 seconds
97
+ ```
78
98
 
79
99
  If a timeout occurs, ready will be nil.
80
100
 
101
+ You can avoid allocating an array each time you call NIO::Selector#select by
102
+ using NIO::Selector#select_each instead. This method successively yields ready
103
+ NIO::Monitor objects:
104
+
105
+ ```ruby
106
+ >> selector.select_each { |m| m.value.call }
107
+ Got some data: Hi there!
108
+ => 1
109
+ ```
110
+
81
111
  When you're done monitoring a particular IO object, just deregister it from
82
112
  the selector:
83
113
 
84
- selector.deregister(reader)
114
+ ```ruby
115
+ selector.deregister(reader)
116
+ ```
117
+
118
+ ### Monitors
119
+
120
+ Monitors provide methods which let you introspect on why a particular IO
121
+ object was selected. These methods are not thread safe unless you are holding
122
+ the selector lock (i.e. if you're in a #select_each callback). Only use them
123
+ if you aren't concerned with thread safety, or you're within a #select_each
124
+ callback:
125
+
126
+ - ***#interests***: what this monitor is interested in (:r, :w, or :rw)
127
+ - ***#readiness***: what the monitored IO object is ready for according to the last select operation
128
+ - ***#readable?***: was the IO readable last time it was selected?
129
+ - ***#writable?***: was the IO writable last time it was selected?
130
+
131
+ Monitors also support a ***#value*** and ***#value=*** method for storing a
132
+ handle to an arbitrary object of your choice (e.g. a proc)
85
133
 
86
134
  Concurrency
87
135
  -----------
@@ -108,7 +156,7 @@ to maintain a large codebase. As of the time of writing, the current
108
156
  implementation is a little over 100 lines of code for both the pure Ruby and
109
157
  JRuby backends. The native extension uses approximately 500 lines of C code.
110
158
 
111
- nio4r is also not a replacement for Kindler Gentler IO (KGIO), a set of
159
+ nio4r is also not a replacement for Kinder Gentler IO (KGIO), a set of
112
160
  advanced Ruby IO APIs. At some point in the future nio4r might provide a
113
161
  cross-platform implementation that uses KGIO on CRubies, and Java NIO on JRuby,
114
162
  however this is not the case today.
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.push File.expand_path('../../lib', __FILE__)
4
+ require 'nio'
5
+ require 'socket'
6
+
7
+ class EchoServer
8
+ def initialize(host, port)
9
+ @selector = NIO::Selector.new
10
+
11
+ puts "Listening on #{host}:#{port}"
12
+ @server = TCPServer.new(host, port)
13
+
14
+ monitor = @selector.register(@server, :r)
15
+ monitor.value = proc { accept }
16
+ end
17
+
18
+ def run
19
+ while true
20
+ @selector.select_each { |monitor| monitor.value.call(monitor) }
21
+ end
22
+ end
23
+
24
+ def accept
25
+ socket = @server.accept
26
+ monitor = @selector.register(socket, :r)
27
+ monitor.value = proc { read(socket) }
28
+ end
29
+
30
+ def read(socket)
31
+ data = socket.read_nonblock(4096)
32
+ socket.write_nonblock(data)
33
+ end
34
+ end
35
+
36
+ if $0 == __FILE__
37
+ EchoServer.new("localhost", 1234).run
38
+ end
@@ -20,8 +20,11 @@ static VALUE NIO_Monitor_close(VALUE self);
20
20
  static VALUE NIO_Monitor_is_closed(VALUE self);
21
21
  static VALUE NIO_Monitor_io(VALUE self);
22
22
  static VALUE NIO_Monitor_interests(VALUE self);
23
+ static VALUE NIO_Monitor_is_readable(VALUE self);
24
+ static VALUE NIO_Monitor_is_writable(VALUE self);
23
25
  static VALUE NIO_Monitor_value(VALUE self);
24
26
  static VALUE NIO_Monitor_set_value(VALUE self, VALUE obj);
27
+ static VALUE NIO_Monitor_readiness(VALUE self);
25
28
 
26
29
  /* Internal functions */
27
30
  static void NIO_Monitor_callback(struct ev_loop *ev_loop, struct ev_io *io, int revents);
@@ -46,6 +49,10 @@ void Init_NIO_Monitor()
46
49
  rb_define_method(cNIO_Monitor, "interests", NIO_Monitor_interests, 0);
47
50
  rb_define_method(cNIO_Monitor, "value", NIO_Monitor_value, 0);
48
51
  rb_define_method(cNIO_Monitor, "value=", NIO_Monitor_set_value, 1);
52
+ rb_define_method(cNIO_Monitor, "readiness", NIO_Monitor_readiness, 0);
53
+ rb_define_method(cNIO_Monitor, "readable?", NIO_Monitor_is_readable, 0);
54
+ rb_define_method(cNIO_Monitor, "writable?", NIO_Monitor_is_writable, 0);
55
+ rb_define_method(cNIO_Monitor, "writeable?", NIO_Monitor_is_writable, 0);
49
56
  }
50
57
 
51
58
  static VALUE NIO_Monitor_allocate(VALUE klass)
@@ -68,7 +75,6 @@ static VALUE NIO_Monitor_initialize(VALUE self, VALUE selector_obj, VALUE io, VA
68
75
  {
69
76
  struct NIO_Monitor *monitor;
70
77
  struct NIO_Selector *selector;
71
- int events;
72
78
  ID interests_id;
73
79
 
74
80
  #if HAVE_RB_IO_T
@@ -79,21 +85,21 @@ static VALUE NIO_Monitor_initialize(VALUE self, VALUE selector_obj, VALUE io, VA
79
85
 
80
86
  interests_id = SYM2ID(interests);
81
87
 
88
+ Data_Get_Struct(self, struct NIO_Monitor, monitor);
89
+
82
90
  if(interests_id == rb_intern("r")) {
83
- events = EV_READ;
91
+ monitor->interests = EV_READ;
84
92
  } else if(interests_id == rb_intern("w")) {
85
- events = EV_WRITE;
93
+ monitor->interests = EV_WRITE;
86
94
  } else if(interests_id == rb_intern("rw")) {
87
- events = EV_READ | EV_WRITE;
95
+ monitor->interests = EV_READ | EV_WRITE;
88
96
  } else {
89
97
  rb_raise(rb_eArgError, "invalid event type %s (must be :r, :w, or :rw)",
90
98
  RSTRING_PTR(rb_funcall(interests, rb_intern("inspect"), 0, 0)));
91
99
  }
92
100
 
93
- Data_Get_Struct(self, struct NIO_Monitor, monitor);
94
-
95
101
  GetOpenFile(rb_convert_type(io, T_FILE, "IO", "to_io"), fptr);
96
- ev_io_init(&monitor->ev_io, NIO_Monitor_callback, FPTR_TO_FD(fptr), events);
102
+ ev_io_init(&monitor->ev_io, NIO_Monitor_callback, FPTR_TO_FD(fptr), monitor->interests);
97
103
 
98
104
  rb_ivar_set(self, rb_intern("selector"), selector_obj);
99
105
  rb_ivar_set(self, rb_intern("io"), io);
@@ -154,11 +160,53 @@ static VALUE NIO_Monitor_set_value(VALUE self, VALUE obj)
154
160
  return rb_ivar_set(self, rb_intern("value"), obj);
155
161
  }
156
162
 
163
+ static VALUE NIO_Monitor_readiness(VALUE self)
164
+ {
165
+ struct NIO_Monitor *monitor;
166
+ Data_Get_Struct(self, struct NIO_Monitor, monitor);
167
+
168
+ if((monitor->revents & (EV_READ | EV_WRITE)) == (EV_READ | EV_WRITE)) {
169
+ return ID2SYM(rb_intern("rw"));
170
+ } else if(monitor->revents & EV_READ) {
171
+ return ID2SYM(rb_intern("r"));
172
+ } else if(monitor->revents & EV_WRITE) {
173
+ return ID2SYM(rb_intern("w"));
174
+ } else {
175
+ return Qnil;
176
+ }
177
+ }
178
+
179
+ static VALUE NIO_Monitor_is_readable(VALUE self)
180
+ {
181
+ struct NIO_Monitor *monitor;
182
+ Data_Get_Struct(self, struct NIO_Monitor, monitor);
183
+
184
+ if(monitor->revents & EV_READ) {
185
+ return Qtrue;
186
+ } else {
187
+ return Qfalse;
188
+ }
189
+ }
190
+
191
+ static VALUE NIO_Monitor_is_writable(VALUE self)
192
+ {
193
+ struct NIO_Monitor *monitor;
194
+ Data_Get_Struct(self, struct NIO_Monitor, monitor);
195
+
196
+ if(monitor->revents & EV_WRITE) {
197
+ return Qtrue;
198
+ } else {
199
+ return Qfalse;
200
+ }
201
+ }
202
+
157
203
  /* libev callback fired whenever this monitor gets events */
158
204
  static void NIO_Monitor_callback(struct ev_loop *ev_loop, struct ev_io *io, int revents)
159
205
  {
160
206
  struct NIO_Monitor *monitor = (struct NIO_Monitor *)io->data;
161
207
 
162
208
  assert(monitor->selector != 0);
209
+ monitor->revents = revents;
210
+
163
211
  NIO_Selector_handle_event(monitor->selector, monitor->self, revents);
164
212
  }
@@ -31,6 +31,7 @@ struct NIO_callback_data
31
31
  struct NIO_Monitor
32
32
  {
33
33
  VALUE self;
34
+ int interests, revents;
34
35
  struct ev_io ev_io;
35
36
  struct NIO_Selector *selector;
36
37
  };
@@ -23,6 +23,7 @@ static VALUE NIO_Selector_register(VALUE self, VALUE selectable, VALUE interest)
23
23
  static VALUE NIO_Selector_deregister(VALUE self, VALUE io);
24
24
  static VALUE NIO_Selector_is_registered(VALUE self, VALUE io);
25
25
  static VALUE NIO_Selector_select(int argc, VALUE *argv, VALUE self);
26
+ static VALUE NIO_Selector_select_each(int argc, VALUE *argv, VALUE self);
26
27
  static VALUE NIO_Selector_wakeup(VALUE self);
27
28
  static VALUE NIO_Selector_close(VALUE self);
28
29
  static VALUE NIO_Selector_closed(VALUE self);
@@ -33,6 +34,8 @@ static VALUE NIO_Selector_unlock(VALUE lock);
33
34
  static VALUE NIO_Selector_register_synchronized(VALUE *args);
34
35
  static VALUE NIO_Selector_deregister_synchronized(VALUE *args);
35
36
  static VALUE NIO_Selector_select_synchronized(VALUE *args);
37
+ static VALUE NIO_Selector_select_each_synchronized(VALUE *args);
38
+ static int NIO_Selector_fill_ready_buffer(VALUE *args);
36
39
  static VALUE NIO_Selector_run_evloop(void *ptr);
37
40
  static void NIO_Selector_timeout_callback(struct ev_loop *ev_loop, struct ev_timer *timer, int revents);
38
41
  static void NIO_Selector_wakeup_callback(struct ev_loop *ev_loop, struct ev_async *async, int revents);
@@ -57,6 +60,7 @@ void Init_NIO_Selector()
57
60
  rb_define_method(cNIO_Selector, "deregister", NIO_Selector_deregister, 1);
58
61
  rb_define_method(cNIO_Selector, "registered?", NIO_Selector_is_registered, 1);
59
62
  rb_define_method(cNIO_Selector, "select", NIO_Selector_select, -1);
63
+ rb_define_method(cNIO_Selector, "select_each", NIO_Selector_select_each, -1);
60
64
  rb_define_method(cNIO_Selector, "wakeup", NIO_Selector_wakeup, 0);
61
65
  rb_define_method(cNIO_Selector, "close", NIO_Selector_close, 0);
62
66
  rb_define_method(cNIO_Selector, "closed?", NIO_Selector_closed, 0);
@@ -126,19 +130,35 @@ static VALUE NIO_Selector_initialize(VALUE self)
126
130
  return Qnil;
127
131
  }
128
132
 
129
- /* Synchronize the given function with the selector mutex */
133
+ /* Synchronize around a reentrant selector lock */
130
134
  static VALUE NIO_Selector_synchronize(VALUE self, VALUE (*func)(VALUE *args), VALUE *args)
131
135
  {
132
- VALUE lock;
136
+ VALUE current_thread, lock_holder, lock;
133
137
 
134
- lock = rb_ivar_get(self, rb_intern("lock"));
135
- rb_funcall(lock, rb_intern("lock"), 0, 0);
136
- return rb_ensure(func, (VALUE)args, NIO_Selector_unlock, lock);
138
+ current_thread = rb_thread_current();
139
+ lock_holder = rb_ivar_get(self, rb_intern("lock_holder"));
140
+
141
+ if(lock_holder != rb_thread_current()) {
142
+ lock = rb_ivar_get(self, rb_intern("lock"));
143
+ rb_funcall(lock, rb_intern("lock"), 0, 0);
144
+ rb_ivar_set(self, rb_intern("lock_holder"), rb_thread_current());
145
+
146
+ /* We've acquired the lock, so ensure we unlock it */
147
+ return rb_ensure(func, (VALUE)args, NIO_Selector_unlock, self);
148
+ } else {
149
+ /* We already hold the selector lock, so no need to unlock it */
150
+ func(args);
151
+ }
137
152
  }
138
153
 
139
154
  /* Unlock the selector mutex */
140
- static VALUE NIO_Selector_unlock(VALUE lock)
155
+ static VALUE NIO_Selector_unlock(VALUE self)
141
156
  {
157
+ VALUE lock;
158
+
159
+ rb_ivar_set(self, rb_intern("lock_holder"), Qnil);
160
+
161
+ lock = rb_ivar_get(self, rb_intern("lock"));
142
162
  rb_funcall(lock, rb_intern("unlock"), 0, 0);
143
163
  }
144
164
 
@@ -225,11 +245,63 @@ static VALUE NIO_Selector_select(int argc, VALUE *argv, VALUE self)
225
245
  return NIO_Selector_synchronize(self, NIO_Selector_select_synchronized, args);
226
246
  }
227
247
 
248
+ /* Select from all registered IO objects */
249
+ static VALUE NIO_Selector_select_each(int argc, VALUE *argv, VALUE self)
250
+ {
251
+ VALUE timeout, array;
252
+ VALUE args[2];
253
+
254
+ if(!rb_block_given_p()) {
255
+ rb_raise(rb_eArgError, "no block given");
256
+ }
257
+
258
+ rb_scan_args(argc, argv, "01", &timeout);
259
+
260
+ args[0] = self;
261
+ args[1] = timeout;
262
+
263
+ return NIO_Selector_synchronize(self, NIO_Selector_select_each_synchronized, args);
264
+ }
265
+
228
266
  /* Internal implementation of select with the selector lock held */
229
267
  static VALUE NIO_Selector_select_synchronized(VALUE *args)
230
268
  {
231
- VALUE self, timeout, result;
232
269
  struct NIO_Selector *selector;
270
+ int ready = NIO_Selector_fill_ready_buffer(args);
271
+
272
+ Data_Get_Struct(args[0], struct NIO_Selector, selector);
273
+
274
+ if(ready > 0) {
275
+ return rb_ary_new4(ready, selector->ready_buffer);
276
+ } else {
277
+ return Qnil;
278
+ }
279
+ }
280
+
281
+ /* Internal implementation of select with the selector lock held */
282
+ static VALUE NIO_Selector_select_each_synchronized(VALUE *args)
283
+ {
284
+ struct NIO_Selector *selector;
285
+ int i, ready = NIO_Selector_fill_ready_buffer(args);
286
+
287
+ Data_Get_Struct(args[0], struct NIO_Selector, selector);
288
+
289
+ if(ready > 0) {
290
+ for(i = 0; i < ready; i++) {
291
+ rb_yield(selector->ready_buffer[i]);
292
+ }
293
+
294
+ return INT2NUM(ready);
295
+ } else {
296
+ return Qnil;
297
+ }
298
+ }
299
+
300
+ static int NIO_Selector_fill_ready_buffer(VALUE *args)
301
+ {
302
+ VALUE self, timeout;
303
+ struct NIO_Selector *selector;
304
+ int result;
233
305
 
234
306
  self = args[0];
235
307
  timeout = args[1];
@@ -289,11 +361,7 @@ static VALUE NIO_Selector_select_synchronized(VALUE *args)
289
361
  }
290
362
  #endif /* defined(HAVE_RB_THREAD_BLOCKING_REGION) */
291
363
 
292
- if(!selector->ready_count) {
293
- return Qnil;
294
- }
295
-
296
- result = rb_ary_new4(selector->ready_count, selector->ready_buffer);
364
+ result = selector->ready_count;
297
365
  selector->selecting = selector->ready_count = 0;
298
366
 
299
367
  return result;
@@ -1,7 +1,7 @@
1
1
  module NIO
2
2
  # Monitors watch Channels for specific events
3
3
  class Monitor
4
- attr_accessor :value
4
+ attr_accessor :value, :io
5
5
 
6
6
  # :nodoc
7
7
  def initialize(io, selection_key)
@@ -15,6 +15,22 @@ module NIO
15
15
  Selector.iops2sym @key.interestOps
16
16
  end
17
17
 
18
+ # What is the IO object ready for?
19
+ def readiness
20
+ Selector.iops2sym @key.readyOps
21
+ end
22
+
23
+ # Is the IO object readable?
24
+ def readable?
25
+ readiness == :r || readiness == :rw
26
+ end
27
+
28
+ # Is the IO object writable?
29
+ def writable?
30
+ readiness == :w || readiness == :rw
31
+ end
32
+ alias_method :writeable?, :writable?
33
+
18
34
  # Is this monitor closed?
19
35
  def closed?; @closed; end
20
36
 
@@ -5,14 +5,22 @@ module NIO
5
5
  java_import "java.nio.channels.SelectionKey"
6
6
 
7
7
  # Convert nio4r interest symbols to Java NIO interest ops
8
- def self.sym2iops(interest)
8
+ def self.sym2iops(interest, channel)
9
9
  case interest
10
10
  when :r
11
- interest = SelectionKey::OP_READ
11
+ if channel.validOps & SelectionKey::OP_ACCEPT != 0
12
+ SelectionKey::OP_ACCEPT
13
+ else
14
+ SelectionKey::OP_READ
15
+ end
12
16
  when :w
13
- interest = SelectionKey::OP_WRITE
17
+ if channel.respond_to? :connected? and not channel.connected?
18
+ SelectionKey::OP_CONNECT
19
+ else
20
+ SelectionKey::OP_WRITE
21
+ end
14
22
  when :rw
15
- interest = SelectionKey::OP_READ | SelectionKey::OP_WRITE
23
+ super(:r, channel) | super(:w, channel)
16
24
  else raise ArgumentError, "invalid interest type: #{interest}"
17
25
  end
18
26
  end
@@ -20,12 +28,13 @@ module NIO
20
28
  # Convert Java NIO interest ops to the corresponding Ruby symbols
21
29
  def self.iops2sym(interest_ops)
22
30
  case interest_ops
23
- when SelectionKey::OP_READ
31
+ when SelectionKey::OP_READ, SelectionKey::OP_ACCEPT
24
32
  :r
25
- when SelectionKey::OP_WRITE
33
+ when SelectionKey::OP_WRITE, SelectionKey::OP_CONNECT
26
34
  :w
27
35
  when SelectionKey::OP_READ | SelectionKey::OP_WRITE
28
36
  :rw
37
+ when 0 then nil
29
38
  else raise ArgumentError, "unknown interest op combination: 0x#{interest_ops.to_s(16)}"
30
39
  end
31
40
  end
@@ -44,15 +53,14 @@ module NIO
44
53
  def register(io, interest)
45
54
  java_channel = io.to_channel
46
55
  java_channel.configureBlocking(false)
47
-
48
- interest_ops = self.class.sym2iops(interest)
56
+ interest_ops = self.class.sym2iops(interest, java_channel)
49
57
 
50
58
  begin
51
59
  selector_key = java_channel.register @java_selector, interest_ops
52
60
  rescue NativeException => ex
53
61
  case ex.cause
54
62
  when java.lang.IllegalArgumentException
55
- raise ArgumentError, "invalid interest type for #{channel}: #{interest}"
63
+ raise ArgumentError, "invalid interest type for #{java_channel}: #{interest}"
56
64
  else raise
57
65
  end
58
66
  end
@@ -79,15 +87,32 @@ module NIO
79
87
 
80
88
  # Select which monitors are ready
81
89
  def select(timeout = nil)
90
+ selected = []
91
+ ready = select_each(timeout) { |monitor| selected << monitor }
92
+ return unless ready
93
+
94
+ selected
95
+ end
96
+
97
+ # Iterate across all selectable monitors
98
+ def select_each(timeout = nil)
82
99
  @select_lock.synchronize do
83
- if timeout
100
+ if timeout == 0
101
+ # The Java NIO API thinks zero means you want to BLOCK FOREVER o_O
102
+ # How about we don't block at all instead?
103
+ ready = @java_selector.selectNow
104
+ elsif timeout
84
105
  ready = @java_selector.select(timeout * 1000)
85
106
  else
86
107
  ready = @java_selector.select
87
108
  end
88
109
 
89
110
  return unless ready > 0 # timeout or wakeup
90
- @java_selector.selectedKeys.map { |key| key.attachment }
111
+
112
+ @java_selector.selectedKeys.each { |key| yield key.attachment }
113
+ @java_selector.selectedKeys.clear
114
+
115
+ ready
91
116
  end
92
117
  end
93
118
 
@@ -2,7 +2,7 @@ module NIO
2
2
  # Monitors watch IO objects for specific events
3
3
  class Monitor
4
4
  attr_reader :io, :interests
5
- attr_accessor :value
5
+ attr_accessor :value, :readiness
6
6
 
7
7
  # :nodoc
8
8
  def initialize(io, interests)
@@ -10,6 +10,17 @@ module NIO
10
10
  @closed = false
11
11
  end
12
12
 
13
+ # Is the IO object readable?
14
+ def readable?
15
+ readiness == :r || readiness == :rw
16
+ end
17
+
18
+ # Is the IO object writable?
19
+ def writable?
20
+ readiness == :w || readiness == :rw
21
+ end
22
+ alias_method :writeable?, :writable?
23
+
13
24
  # Is this monitor closed?
14
25
  def closed?; @closed; end
15
26
 
@@ -54,10 +54,8 @@ 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 = ready_readers
58
- results.concat ready_writers if ready_writers
59
-
60
- results.map! do |io|
57
+ results = []
58
+ ready_readers.each do |io|
61
59
  if io == @wakeup
62
60
  # Clear all wakeup signals we've received by reading them
63
61
  # Wakeups should have level triggered behavior
@@ -71,12 +69,35 @@ module NIO
71
69
 
72
70
  return
73
71
  else
74
- @selectables[io]
72
+ monitor = @selectables[io]
73
+ monitor.readiness = :r
74
+ results << monitor
75
+ end
76
+ end
77
+
78
+ ready_readwriters = ready_readers & ready_writers
79
+ ready_writers = ready_writers - ready_readwriters
80
+
81
+ [[ready_writers, :w], [ready_readwriters, :rw]].each do |ios, readiness|
82
+ ios.each do |io|
83
+ monitor = @selectables[io]
84
+ monitor.readiness = readiness
85
+ results << monitor
75
86
  end
76
87
  end
88
+
89
+ results
77
90
  end
78
91
  end
79
92
 
93
+ # Select for ready monitors, successively yielding each one in a block
94
+ def select_each(timeout = nil, &block)
95
+ selected = select(timeout)
96
+ return unless selected
97
+ selected.each(&block)
98
+ selected.size
99
+ end
100
+
80
101
  # Wake up other threads waiting on this selector
81
102
  def wakeup
82
103
  # Send the selector a signal in the form of writing data to a pipe
@@ -1,3 +1,3 @@
1
1
  module NIO
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -14,9 +14,9 @@ Gem::Specification.new do |gem|
14
14
  gem.name = "nio4r"
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = NIO::VERSION
17
- gem.extensions = ["ext/nio4r/extconf.rb"] unless defined?(JRUBY_VERSION)
17
+ gem.extensions = ["ext/nio4r/extconf.rb"]
18
18
 
19
19
  gem.add_development_dependency "rake-compiler", "~> 0.7.9"
20
20
  gem.add_development_dependency "rake"
21
- gem.add_development_dependency "rspec", ">= 2.7.0"
21
+ gem.add_development_dependency "rspec", "~> 2.7.0"
22
22
  end
@@ -1,23 +1,18 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe NIO::Monitor do
4
- let :readable_pipe do
5
- pipe, peer = IO.pipe
6
- peer << "data"
7
- pipe
4
+ let :readable do
5
+ reader, writer = IO.pipe
6
+ writer << "have some data"
7
+ reader
8
8
  end
9
9
 
10
- # let :unreadable_pipe do
11
- # pipe, _ = IO.pipe
12
- # pipe
13
- # end
14
-
15
10
  let :selector do
16
11
  NIO::Selector.new
17
12
  end
18
13
 
19
14
  # Monitors are created by registering IO objects or channels with a selector
20
- subject { selector.register(readable_pipe, :r) }
15
+ subject { selector.register(readable, :r) }
21
16
 
22
17
  it "knows its interests" do
23
18
  subject.interests.should == :r
@@ -28,6 +23,21 @@ describe NIO::Monitor do
28
23
  subject.value.should == 42
29
24
  end
30
25
 
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
+ writer << "loldata"
32
+ selector = NIO::Selector.new
33
+ subject = selector.register(reader, :r)
34
+
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
39
+ end
40
+
31
41
  it "closes" do
32
42
  subject.should_not be_closed
33
43
  subject.close
@@ -67,6 +67,45 @@ describe NIO::Selector do
67
67
  end
68
68
  end
69
69
 
70
+ context "select_each" do
71
+ it "iterates across ready selectables" do
72
+ readable1, writer = IO.pipe
73
+ writer << "ohai"
74
+
75
+ readable2, writer = IO.pipe
76
+ writer << "ohai"
77
+
78
+ unreadable, _ = IO.pipe
79
+
80
+ monitor1 = subject.register(readable1, :r)
81
+ monitor2 = subject.register(readable2, :r)
82
+ monitor3 = subject.register(unreadable, :r)
83
+
84
+ readables = []
85
+ subject.select_each { |monitor| readables << monitor }
86
+
87
+ readables.should include(monitor1)
88
+ readables.should include(monitor2)
89
+ readables.should_not include(monitor3)
90
+ end
91
+
92
+ it "allows new monitors to be registered in the select_each block" do
93
+ server = TCPServer.new("localhost", 10001)
94
+
95
+ monitor = subject.register(server, :r)
96
+ connector = TCPSocket.open("localhost", 10001)
97
+
98
+ block_fired = false
99
+ subject.select_each do |monitor|
100
+ block_fired = true
101
+ socket = server.accept
102
+ subject.register(socket, :r).should be_a NIO::Monitor
103
+ end
104
+
105
+ block_fired.should be_true
106
+ end
107
+ end
108
+
70
109
  it "closes" do
71
110
  subject.close
72
111
  subject.should be_closed
@@ -139,8 +178,12 @@ describe NIO::Selector do
139
178
  end
140
179
 
141
180
  let :unreadable_subject do
142
- TCPServer.new("localhost", tcp_port + 1)
143
- TCPSocket.open("localhost", tcp_port + 1)
181
+ if defined?(JRUBY_VERSION) and ENV['TRAVIS']
182
+ pending "This is sporadically showing up readable on JRuby in CI"
183
+ else
184
+ TCPServer.new("localhost", tcp_port + 1)
185
+ TCPSocket.open("localhost", tcp_port + 1)
186
+ end
144
187
  end
145
188
 
146
189
  let :writable_subject do
@@ -194,4 +237,33 @@ describe NIO::Selector do
194
237
  it_behaves_like "an NIO selectable"
195
238
  end
196
239
  end
240
+
241
+ context "acceptables" do
242
+ shared_context "an NIO acceptable" do
243
+ it "selects for read readiness" do
244
+ waiting_monitor = subject.register(unacceptable_subject, :r)
245
+ ready_monitor = subject.register(acceptable_subject, :r)
246
+
247
+ ready_monitors = subject.select
248
+ ready_monitors.should include ready_monitor
249
+ ready_monitors.should_not include waiting_monitor
250
+ end
251
+ end
252
+
253
+ context TCPServer do
254
+ let(:tcp_port) { 23456 }
255
+
256
+ let :acceptable_subject do
257
+ server = TCPServer.new("localhost", tcp_port)
258
+ TCPSocket.open("localhost", tcp_port)
259
+ server
260
+ end
261
+
262
+ let :unacceptable_subject do
263
+ TCPServer.new("localhost", tcp_port + 1)
264
+ end
265
+
266
+ it_behaves_like "an NIO acceptable"
267
+ end
268
+ end
197
269
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nio4r
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-26 00:00:00.000000000 Z
12
+ date: 2012-01-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake-compiler
16
- requirement: &70311592959600 !ruby/object:Gem::Requirement
16
+ requirement: &70186951376840 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 0.7.9
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70311592959600
24
+ version_requirements: *70186951376840
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake
27
- requirement: &70311592959040 !ruby/object:Gem::Requirement
27
+ requirement: &70186951376120 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,18 +32,18 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70311592959040
35
+ version_requirements: *70186951376120
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &70311592957620 !ruby/object:Gem::Requirement
38
+ requirement: &70186951375540 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
- - - ! '>='
41
+ - - ~>
42
42
  - !ruby/object:Gem::Version
43
43
  version: 2.7.0
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70311592957620
46
+ version_requirements: *70186951375540
47
47
  description: New IO for Ruby
48
48
  email:
49
49
  - tony.arcieri@gmail.com
@@ -55,10 +55,12 @@ files:
55
55
  - .gitignore
56
56
  - .rspec
57
57
  - .travis.yml
58
+ - CHANGES.md
58
59
  - Gemfile
59
60
  - LICENSE.txt
60
61
  - README.md
61
62
  - Rakefile
63
+ - examples/echo_server.rb
62
64
  - ext/libev/Changes
63
65
  - ext/libev/LICENSE
64
66
  - ext/libev/README
@@ -120,3 +122,4 @@ test_files:
120
122
  - spec/nio/monitor_spec.rb
121
123
  - spec/nio/selector_spec.rb
122
124
  - spec/spec_helper.rb
125
+ has_rdoc: