nio4r 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -156,10 +156,11 @@ top of. nio4r provides a minimal API such that individual Ruby implementers
156
156
  may choose to produce optimized versions for their platform, without having
157
157
  to maintain a large codebase.
158
158
 
159
- As of the time of writing, the current implementation is
160
- * ~200 lines of Ruby code
161
- * ~700 lines of "custom" C code (not counting libev)
162
- * ~400 lines of Java code
159
+ As of the time of writing, the current implementation is (approximately):
160
+
161
+ * 200 lines of Ruby code
162
+ * 700 lines of "custom" C code (not counting libev)
163
+ * 400 lines of Java code
163
164
 
164
165
  nio4r is also not a replacement for Kinder Gentler IO (KGIO), a set of
165
166
  advanced Ruby IO APIs. At some point in the future nio4r might provide a
@@ -1,6 +1,8 @@
1
1
  package org.nio4r;
2
2
 
3
3
  import java.util.Iterator;
4
+ import java.util.Map;
5
+ import java.util.HashMap;
4
6
  import java.io.IOException;
5
7
  import java.nio.channels.Channel;
6
8
  import java.nio.channels.SocketChannel;
@@ -94,6 +96,7 @@ public class Nio4r implements Library {
94
96
 
95
97
  public class Selector extends RubyObject {
96
98
  private java.nio.channels.Selector selector;
99
+ private HashMap<SelectableChannel,SelectionKey> cancelledKeys;
97
100
 
98
101
  public Selector(final Ruby ruby, RubyClass rubyClass) {
99
102
  super(ruby, rubyClass);
@@ -101,8 +104,9 @@ public class Nio4r implements Library {
101
104
 
102
105
  @JRubyMethod
103
106
  public IRubyObject initialize(ThreadContext context) {
107
+ this.cancelledKeys = new HashMap<SelectableChannel,SelectionKey>();
104
108
  try {
105
- selector = java.nio.channels.Selector.open();
109
+ this.selector = java.nio.channels.Selector.open();
106
110
  } catch(IOException ie) {
107
111
  throw context.runtime.newIOError(ie.getLocalizedMessage());
108
112
  }
@@ -113,7 +117,7 @@ public class Nio4r implements Library {
113
117
  @JRubyMethod
114
118
  public IRubyObject close(ThreadContext context) {
115
119
  try {
116
- selector.close();
120
+ this.selector.close();
117
121
  } catch(IOException ie) {
118
122
  throw context.runtime.newIOError(ie.getLocalizedMessage());
119
123
  }
@@ -124,7 +128,7 @@ public class Nio4r implements Library {
124
128
  @JRubyMethod(name = "closed?")
125
129
  public IRubyObject isClosed(ThreadContext context) {
126
130
  Ruby runtime = context.getRuntime();
127
- return selector.isOpen() ? runtime.getFalse() : runtime.getTrue();
131
+ return this.selector.isOpen() ? runtime.getFalse() : runtime.getTrue();
128
132
  }
129
133
 
130
134
  @JRubyMethod
@@ -147,12 +151,18 @@ public class Nio4r implements Library {
147
151
  int interestOps = Nio4r.symbolToInterestOps(runtime, channel, interests);
148
152
  SelectionKey key;
149
153
 
150
- try {
151
- key = channel.register(selector, interestOps);
152
- } catch(java.lang.IllegalArgumentException ia) {
153
- throw runtime.newArgumentError("mode not supported for this object: " + interests);
154
- } catch(java.nio.channels.ClosedChannelException cce) {
155
- throw context.runtime.newIOError(cce.getLocalizedMessage());
154
+ key = this.cancelledKeys.remove(channel);
155
+
156
+ if(key != null) {
157
+ key.interestOps(interestOps);
158
+ } else {
159
+ try {
160
+ key = channel.register(this.selector, interestOps);
161
+ } catch(java.lang.IllegalArgumentException ia) {
162
+ throw runtime.newArgumentError("mode not supported for this object: " + interests);
163
+ } catch(java.nio.channels.ClosedChannelException cce) {
164
+ throw context.runtime.newIOError(cce.getLocalizedMessage());
165
+ }
156
166
  }
157
167
 
158
168
  RubyClass monitorClass = runtime.getModule("NIO").getClass("Monitor");
@@ -172,13 +182,14 @@ public class Nio4r implements Library {
172
182
  }
173
183
 
174
184
  SelectableChannel channel = (SelectableChannel)raw_channel;
175
- SelectionKey key = channel.keyFor(selector);
185
+ SelectionKey key = channel.keyFor(this.selector);
176
186
 
177
187
  if(key == null)
178
188
  return context.nil;
179
189
 
180
190
  Monitor monitor = (Monitor)key.attachment();
181
- monitor.close(context);
191
+ monitor.close(context, runtime.getFalse());
192
+ cancelledKeys.put(channel, key);
182
193
 
183
194
  return monitor;
184
195
  }
@@ -193,7 +204,7 @@ public class Nio4r implements Library {
193
204
  }
194
205
 
195
206
  SelectableChannel channel = (SelectableChannel)raw_channel;
196
- SelectionKey key = channel.keyFor(selector);
207
+ SelectionKey key = channel.keyFor(this.selector);
197
208
 
198
209
  if(key == null)
199
210
  return context.nil;
@@ -222,11 +233,11 @@ public class Nio4r implements Library {
222
233
 
223
234
  RubyArray array = null;
224
235
  if(!block.isGiven()) {
225
- array = runtime.newArray(selector.selectedKeys().size());
236
+ array = runtime.newArray(this.selector.selectedKeys().size());
226
237
  }
227
238
 
228
- Iterator selectedKeys = selector.selectedKeys().iterator();
229
- while (selectedKeys.hasNext()) {
239
+ Iterator selectedKeys = this.selector.selectedKeys().iterator();
240
+ while(selectedKeys.hasNext()) {
230
241
  SelectionKey key = (SelectionKey)selectedKeys.next();
231
242
  processKey(key);
232
243
  selectedKeys.remove();
@@ -259,7 +270,7 @@ public class Nio4r implements Library {
259
270
  if(ready <= 0)
260
271
  return context.nil;
261
272
 
262
- Iterator selectedKeys = selector.selectedKeys().iterator();
273
+ Iterator selectedKeys = this.selector.selectedKeys().iterator();
263
274
  while (selectedKeys.hasNext()) {
264
275
  SelectionKey key = (SelectionKey)selectedKeys.next();
265
276
  processKey(key);
@@ -271,17 +282,25 @@ public class Nio4r implements Library {
271
282
  }
272
283
 
273
284
  private int doSelect(Ruby runtime, IRubyObject timeout) {
285
+ Iterator cancelledKeys = this.cancelledKeys.entrySet().iterator();
286
+ while(cancelledKeys.hasNext()) {
287
+ Map.Entry entry = (Map.Entry)cancelledKeys.next();
288
+ SelectionKey key = (SelectionKey)entry.getValue();
289
+ key.cancel();
290
+ cancelledKeys.remove();
291
+ }
292
+
274
293
  try {
275
294
  if(timeout.isNil()) {
276
- return selector.select();
295
+ return this.selector.select();
277
296
  } else {
278
297
  double t = RubyNumeric.num2dbl(timeout);
279
298
  if(t == 0) {
280
- return selector.selectNow();
299
+ return this.selector.selectNow();
281
300
  } else if(t < 0) {
282
301
  throw runtime.newArgumentError("time interval must be positive");
283
302
  } else {
284
- return selector.select((long)(t * 1000));
303
+ return this.selector.select((long)(t * 1000));
285
304
  }
286
305
  }
287
306
  } catch(IOException ie) {
@@ -304,11 +323,11 @@ public class Nio4r implements Library {
304
323
 
305
324
  @JRubyMethod
306
325
  public IRubyObject wakeup(ThreadContext context) {
307
- if(!selector.isOpen()) {
326
+ if(!this.selector.isOpen()) {
308
327
  throw context.getRuntime().newIOError("selector is closed");
309
328
  }
310
329
 
311
- selector.wakeup();
330
+ this.selector.wakeup();
312
331
  return context.nil;
313
332
  }
314
333
  }
@@ -328,14 +347,14 @@ public class Nio4r implements Library {
328
347
  this.interests = interests;
329
348
  this.selector = selector;
330
349
 
331
- value = context.nil;
332
- closed = context.getRuntime().getFalse();
350
+ this.value = context.nil;
351
+ this.closed = context.getRuntime().getFalse();
333
352
 
334
353
  return context.nil;
335
354
  }
336
355
 
337
- public void setSelectionKey(SelectionKey k) {
338
- key = k;
356
+ public void setSelectionKey(SelectionKey key) {
357
+ this.key = key;
339
358
  key.attach(this);
340
359
  }
341
360
 
@@ -362,7 +381,7 @@ public class Nio4r implements Library {
362
381
  @JRubyMethod(name = "readable?")
363
382
  public IRubyObject isReadable(ThreadContext context) {
364
383
  Ruby runtime = context.getRuntime();
365
- int readyOps = key.readyOps();
384
+ int readyOps = this.key.readyOps();
366
385
 
367
386
  if((readyOps & SelectionKey.OP_READ) != 0 || (readyOps & SelectionKey.OP_ACCEPT) != 0) {
368
387
  return runtime.getTrue();
@@ -374,7 +393,7 @@ public class Nio4r implements Library {
374
393
  @JRubyMethod(name = {"writable?", "writeable?"})
375
394
  public IRubyObject writable(ThreadContext context) {
376
395
  Ruby runtime = context.getRuntime();
377
- int readyOps = key.readyOps();
396
+ int readyOps = this.key.readyOps();
378
397
 
379
398
  if((readyOps & SelectionKey.OP_WRITE) != 0 || (readyOps & SelectionKey.OP_CONNECT) != 0) {
380
399
  return runtime.getTrue();
@@ -385,25 +404,35 @@ public class Nio4r implements Library {
385
404
 
386
405
  @JRubyMethod(name = "value")
387
406
  public IRubyObject getValue(ThreadContext context) {
388
- return value;
407
+ return this.value;
389
408
  }
390
409
 
391
410
  @JRubyMethod(name = "value=")
392
411
  public IRubyObject setValue(ThreadContext context, IRubyObject obj) {
393
- value = obj;
412
+ this.value = obj;
394
413
  return context.nil;
395
414
  }
396
415
 
397
416
  @JRubyMethod
398
417
  public IRubyObject close(ThreadContext context) {
399
- key.cancel();
400
- closed = context.getRuntime().getTrue();
418
+ return close(context, context.getRuntime().getTrue());
419
+ }
420
+
421
+ @JRubyMethod
422
+ public IRubyObject close(ThreadContext context, IRubyObject deregister) {
423
+ Ruby runtime = context.getRuntime();
424
+ this.closed = runtime.getTrue();
425
+
426
+ if(deregister == runtime.getTrue()) {
427
+ selector.callMethod(context, "deregister", io);
428
+ }
429
+
401
430
  return context.nil;
402
431
  }
403
432
 
404
433
  @JRubyMethod(name = "closed?")
405
434
  public IRubyObject isClosed(ThreadContext context) {
406
- return closed;
435
+ return this.closed;
407
436
  }
408
437
  }
409
438
  }
data/ext/nio4r/selector.c CHANGED
@@ -9,7 +9,6 @@
9
9
  #include <fcntl.h>
10
10
 
11
11
  static VALUE mNIO = Qnil;
12
- static VALUE cNIO_Channel = Qnil;
13
12
  static VALUE cNIO_Monitor = Qnil;
14
13
  static VALUE cNIO_Selector = Qnil;
15
14
 
@@ -52,8 +51,6 @@ static void NIO_Selector_wakeup_callback(struct ev_loop *ev_loop, struct ev_io *
52
51
  void Init_NIO_Selector()
53
52
  {
54
53
  mNIO = rb_define_module("NIO");
55
- cNIO_Channel = rb_define_class_under(mNIO, "Channel", rb_cObject);
56
- cNIO_Monitor = rb_define_class_under(mNIO, "Monitor", rb_cObject);
57
54
  cNIO_Selector = rb_define_class_under(mNIO, "Selector", rb_cObject);
58
55
  rb_define_alloc_func(cNIO_Selector, NIO_Selector_allocate);
59
56
 
@@ -66,6 +63,8 @@ void Init_NIO_Selector()
66
63
  rb_define_method(cNIO_Selector, "wakeup", NIO_Selector_wakeup, 0);
67
64
  rb_define_method(cNIO_Selector, "close", NIO_Selector_close, 0);
68
65
  rb_define_method(cNIO_Selector, "closed?", NIO_Selector_closed, 0);
66
+
67
+ cNIO_Monitor = rb_define_class_under(mNIO, "Monitor", rb_cObject);
69
68
  }
70
69
 
71
70
  /* Create the libev event loop and incoming event buffer */
data/lib/nio/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module NIO
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -6,11 +6,13 @@ require 'spec_helper'
6
6
  TIMEOUT_PRECISION = 0.1
7
7
 
8
8
  describe NIO::Selector do
9
+ let(:pair) { IO.pipe }
10
+ let(:reader) { pair.first }
11
+ let(:writer) { pair.last }
12
+
9
13
  context "register" do
10
14
  it "registers IO objects" do
11
- pipe, _ = IO.pipe
12
-
13
- monitor = subject.register(pipe, :r)
15
+ monitor = subject.register(reader, :r)
14
16
  monitor.should_not be_closed
15
17
  end
16
18
 
@@ -20,25 +22,37 @@ describe NIO::Selector do
20
22
  end
21
23
 
22
24
  it "knows which IO objects are registered" do
23
- reader, writer = IO.pipe
24
25
  subject.register(reader, :r)
25
-
26
26
  subject.should be_registered(reader)
27
27
  subject.should_not be_registered(writer)
28
28
  end
29
29
 
30
30
  it "deregisters IO objects" do
31
- pipe, _ = IO.pipe
31
+ subject.register(reader, :r)
32
32
 
33
- subject.register(pipe, :r)
34
- monitor = subject.deregister(pipe)
35
- subject.should_not be_registered(pipe)
33
+ monitor = subject.deregister(reader)
34
+ subject.should_not be_registered(reader)
36
35
  monitor.should be_closed
37
36
  end
38
37
 
38
+ # This spec might seem a bit silly, but this actually something the
39
+ # Java NIO API specifically precludes that we need to work around
40
+ it "allows reregistration of the same IO object across select calls" do
41
+ monitor = subject.register(reader, :r)
42
+ writer << "ohai"
43
+
44
+ subject.select.should include monitor
45
+ reader.read(4).should == "ohai"
46
+ subject.deregister(reader)
47
+
48
+ new_monitor = subject.register(reader, :r)
49
+ writer << "thar"
50
+ subject.select.should include new_monitor
51
+ reader.read(4).should == "thar"
52
+ end
53
+
39
54
  context "timeouts" do
40
55
  it "waits for a timeout when selecting" do
41
- reader, writer = IO.pipe
42
56
  monitor = subject.register(reader, :r)
43
57
 
44
58
  payload = "hi there"
@@ -56,7 +70,6 @@ describe NIO::Selector do
56
70
  end
57
71
 
58
72
  it "raises ArgumentError if given a negative timeout" do
59
- reader, _ = IO.pipe
60
73
  subject.register(reader, :r)
61
74
 
62
75
  expect { subject.select(-1) }.to raise_exception(ArgumentError)
@@ -65,8 +78,7 @@ describe NIO::Selector do
65
78
 
66
79
  context "wakeup" do
67
80
  it "wakes up if signaled to from another thread" do
68
- pipe, _ = IO.pipe
69
- subject.register(pipe, :r)
81
+ subject.register(reader, :r)
70
82
 
71
83
  thread = Thread.new do
72
84
  started_at = Time.now
@@ -91,18 +103,16 @@ describe NIO::Selector do
91
103
 
92
104
  context "select" do
93
105
  it "selects IO objects" do
94
- readable, writer = IO.pipe
95
106
  writer << "ohai"
107
+ unready, _ = IO.pipe
96
108
 
97
- unreadable, _ = IO.pipe
98
-
99
- readable_monitor = subject.register(readable, :r)
100
- unreadable_monitor = subject.register(unreadable, :r)
109
+ reader_monitor = subject.register(reader, :r)
110
+ unready_monitor = subject.register(unready, :r)
101
111
 
102
112
  selected = subject.select(0)
103
113
  selected.size.should == 1
104
- selected.should include(readable_monitor)
105
- selected.should_not include(unreadable_monitor)
114
+ selected.should include(reader_monitor)
115
+ selected.should_not include(unready_monitor)
106
116
  end
107
117
 
108
118
  it "iterates across selected objects with a block" do
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.3.0
4
+ version: 0.3.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2012-02-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake-compiler
16
- requirement: &70285234600680 !ruby/object:Gem::Requirement
16
+ requirement: &70256512025560 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70285234600680
24
+ version_requirements: *70256512025560
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake
27
- requirement: &70285234600040 !ruby/object:Gem::Requirement
27
+ requirement: &70256512024940 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70285234600040
35
+ version_requirements: *70256512024940
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &70285234599480 !ruby/object:Gem::Requirement
38
+ requirement: &70256512040740 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70285234599480
46
+ version_requirements: *70256512040740
47
47
  description: New IO for Ruby
48
48
  email:
49
49
  - tony.arcieri@gmail.com