nio4r 2.0.0.pre → 2.0.0

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rubocop.yml +31 -38
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +4 -21
  6. data/CHANGES.md +75 -42
  7. data/Gemfile +11 -3
  8. data/Guardfile +10 -0
  9. data/LICENSE.txt +1 -1
  10. data/README.md +32 -136
  11. data/Rakefile +2 -0
  12. data/examples/echo_server.rb +1 -0
  13. data/ext/libev/Changes +4 -13
  14. data/ext/libev/ev.c +100 -74
  15. data/ext/libev/ev.h +3 -3
  16. data/ext/libev/ev_epoll.c +6 -3
  17. data/ext/libev/ev_kqueue.c +8 -4
  18. data/ext/libev/ev_poll.c +6 -3
  19. data/ext/libev/ev_port.c +8 -4
  20. data/ext/libev/ev_select.c +4 -2
  21. data/ext/nio4r/bytebuffer.c +265 -257
  22. data/ext/nio4r/extconf.rb +2 -10
  23. data/ext/nio4r/monitor.c +93 -46
  24. data/ext/nio4r/nio4r.h +5 -15
  25. data/ext/nio4r/org/nio4r/ByteBuffer.java +193 -209
  26. data/ext/nio4r/org/nio4r/Monitor.java +164 -0
  27. data/ext/nio4r/org/nio4r/Nio4r.java +13 -391
  28. data/ext/nio4r/org/nio4r/Selector.java +278 -0
  29. data/ext/nio4r/selector.c +52 -52
  30. data/lib/nio.rb +3 -3
  31. data/lib/nio/bytebuffer.rb +179 -132
  32. data/lib/nio/monitor.rb +64 -4
  33. data/lib/nio/selector.rb +36 -13
  34. data/lib/nio/version.rb +1 -1
  35. data/nio4r.gemspec +25 -19
  36. data/spec/nio/acceptables_spec.rb +6 -4
  37. data/spec/nio/bytebuffer_spec.rb +323 -51
  38. data/spec/nio/monitor_spec.rb +122 -79
  39. data/spec/nio/selectables/pipe_spec.rb +5 -1
  40. data/spec/nio/selectables/ssl_socket_spec.rb +15 -12
  41. data/spec/nio/selectables/tcp_socket_spec.rb +42 -31
  42. data/spec/nio/selectables/udp_socket_spec.rb +2 -0
  43. data/spec/nio/selector_spec.rb +10 -4
  44. data/spec/spec_helper.rb +24 -3
  45. data/spec/support/selectable_examples.rb +7 -5
  46. data/tasks/extension.rake +2 -0
  47. data/tasks/rspec.rake +2 -0
  48. data/tasks/rubocop.rake +2 -0
  49. metadata +15 -11
  50. data/.rubocop_todo.yml +0 -35
@@ -0,0 +1,278 @@
1
+ package org.nio4r;
2
+
3
+ import java.util.Iterator;
4
+ import java.util.Map;
5
+ import java.util.HashMap;
6
+ import java.io.IOException;
7
+ import java.nio.channels.Channel;
8
+ import java.nio.channels.SelectableChannel;
9
+ import java.nio.channels.SelectionKey;
10
+
11
+ import org.jruby.Ruby;
12
+ import org.jruby.RubyArray;
13
+ import org.jruby.RubyClass;
14
+ import org.jruby.RubyIO;
15
+ import org.jruby.RubyNumeric;
16
+ import org.jruby.RubyObject;
17
+ import org.jruby.anno.JRubyMethod;
18
+ import org.jruby.runtime.Block;
19
+ import org.jruby.runtime.ThreadContext;
20
+ import org.jruby.runtime.builtin.IRubyObject;
21
+
22
+ import org.nio4r.Monitor;
23
+
24
+ public class Selector extends RubyObject {
25
+ private java.nio.channels.Selector selector;
26
+ private HashMap<SelectableChannel,SelectionKey> cancelledKeys;
27
+ private volatile boolean wakeupFired;
28
+
29
+ public Selector(final Ruby ruby, RubyClass rubyClass) {
30
+ super(ruby, rubyClass);
31
+ }
32
+
33
+ @JRubyMethod
34
+ public IRubyObject initialize(ThreadContext context) {
35
+ this.cancelledKeys = new HashMap<SelectableChannel,SelectionKey>();
36
+ this.wakeupFired = false;
37
+
38
+ try {
39
+ this.selector = java.nio.channels.Selector.open();
40
+ } catch(IOException ie) {
41
+ throw context.runtime.newIOError(ie.getLocalizedMessage());
42
+ }
43
+
44
+ return context.nil;
45
+ }
46
+
47
+ @JRubyMethod
48
+ public IRubyObject backend(ThreadContext context) {
49
+ return context.runtime.newSymbol("java");
50
+ }
51
+
52
+ @JRubyMethod
53
+ public IRubyObject close(ThreadContext context) {
54
+ try {
55
+ this.selector.close();
56
+ } catch(IOException ie) {
57
+ throw context.runtime.newIOError(ie.getLocalizedMessage());
58
+ }
59
+
60
+ return context.nil;
61
+ }
62
+
63
+ @JRubyMethod(name = "closed?")
64
+ public IRubyObject isClosed(ThreadContext context) {
65
+ Ruby runtime = context.getRuntime();
66
+ return this.selector.isOpen() ? runtime.getFalse() : runtime.getTrue();
67
+ }
68
+
69
+ @JRubyMethod(name = "empty?")
70
+ public IRubyObject isEmpty(ThreadContext context) {
71
+ Ruby runtime = context.getRuntime();
72
+ return this.selector.keys().isEmpty() ? runtime.getTrue() : runtime.getFalse();
73
+ }
74
+
75
+ @JRubyMethod
76
+ public IRubyObject register(ThreadContext context, IRubyObject io, IRubyObject interests) {
77
+ Ruby runtime = context.getRuntime();
78
+ Channel rawChannel = RubyIO.convertToIO(context, io).getChannel();
79
+
80
+ if(!this.selector.isOpen()) {
81
+ throw context.getRuntime().newIOError("selector is closed");
82
+ }
83
+
84
+ if(!(rawChannel instanceof SelectableChannel)) {
85
+ throw runtime.newArgumentError("not a selectable IO object");
86
+ }
87
+
88
+ SelectableChannel channel = (SelectableChannel)rawChannel;
89
+
90
+ try {
91
+ channel.configureBlocking(false);
92
+ } catch(IOException ie) {
93
+ throw runtime.newIOError(ie.getLocalizedMessage());
94
+ }
95
+
96
+ int interestOps = Nio4r.symbolToInterestOps(runtime, channel, interests);
97
+ SelectionKey key;
98
+
99
+ key = this.cancelledKeys.remove(channel);
100
+
101
+ if(key != null) {
102
+ key.interestOps(interestOps);
103
+ } else {
104
+ try {
105
+ key = channel.register(this.selector, interestOps);
106
+ } catch(java.lang.IllegalArgumentException ia) {
107
+ throw runtime.newArgumentError("mode not supported for this object: " + interests);
108
+ } catch(java.nio.channels.ClosedChannelException cce) {
109
+ throw context.runtime.newIOError(cce.getLocalizedMessage());
110
+ }
111
+ }
112
+
113
+ RubyClass monitorClass = runtime.getModule("NIO").getClass("Monitor");
114
+ Monitor monitor = (Monitor)monitorClass.newInstance(context, io, interests, this, null);
115
+ monitor.setSelectionKey(key);
116
+
117
+ return monitor;
118
+ }
119
+
120
+ @JRubyMethod
121
+ public IRubyObject deregister(ThreadContext context, IRubyObject io) {
122
+ Ruby runtime = context.getRuntime();
123
+ Channel rawChannel = RubyIO.convertToIO(context, io).getChannel();
124
+
125
+ if(!(rawChannel instanceof SelectableChannel)) {
126
+ throw runtime.newArgumentError("not a selectable IO object");
127
+ }
128
+
129
+ SelectableChannel channel = (SelectableChannel)rawChannel;
130
+ SelectionKey key = channel.keyFor(this.selector);
131
+
132
+ if(key == null)
133
+ return context.nil;
134
+
135
+ Monitor monitor = (Monitor)key.attachment();
136
+ monitor.close(context, runtime.getFalse());
137
+ cancelledKeys.put(channel, key);
138
+
139
+ return monitor;
140
+ }
141
+
142
+ @JRubyMethod(name = "registered?")
143
+ public IRubyObject isRegistered(ThreadContext context, IRubyObject io) {
144
+ Ruby runtime = context.getRuntime();
145
+ Channel rawChannel = RubyIO.convertToIO(context, io).getChannel();
146
+
147
+ if(!(rawChannel instanceof SelectableChannel)) {
148
+ throw runtime.newArgumentError("not a selectable IO object");
149
+ }
150
+
151
+ SelectableChannel channel = (SelectableChannel)rawChannel;
152
+ SelectionKey key = channel.keyFor(this.selector);
153
+
154
+ if(key == null)
155
+ return context.nil;
156
+
157
+
158
+ if(((Monitor)key.attachment()).isClosed(context) == runtime.getTrue()) {
159
+ return runtime.getFalse();
160
+ } else {
161
+ return runtime.getTrue();
162
+ }
163
+ }
164
+
165
+ @JRubyMethod
166
+ public synchronized IRubyObject select(ThreadContext context, Block block) {
167
+ return select(context, context.nil, block);
168
+ }
169
+
170
+ @JRubyMethod
171
+ public synchronized IRubyObject select(ThreadContext context, IRubyObject timeout, Block block) {
172
+ Ruby runtime = context.getRuntime();
173
+
174
+ if(!this.selector.isOpen()) {
175
+ throw context.getRuntime().newIOError("selector is closed");
176
+ }
177
+
178
+ this.wakeupFired = false;
179
+ int ready = doSelect(runtime, context, timeout);
180
+
181
+ /* Timeout */
182
+ if(ready <= 0 && !this.wakeupFired) {
183
+ return context.nil;
184
+ }
185
+
186
+ RubyArray array = null;
187
+
188
+ if(!block.isGiven()) {
189
+ array = runtime.newArray(this.selector.selectedKeys().size());
190
+ }
191
+
192
+ Iterator selectedKeys = this.selector.selectedKeys().iterator();
193
+ while(selectedKeys.hasNext()) {
194
+ SelectionKey key = (SelectionKey)selectedKeys.next();
195
+ processKey(key);
196
+ selectedKeys.remove();
197
+
198
+ if(block.isGiven()) {
199
+ block.call(context, (IRubyObject)key.attachment());
200
+ } else {
201
+ array.add(key.attachment());
202
+ }
203
+ }
204
+
205
+ if(block.isGiven()) {
206
+ return RubyNumeric.int2fix(runtime, ready);
207
+ } else {
208
+ return array;
209
+ }
210
+ }
211
+
212
+ /* Run the selector */
213
+ private int doSelect(Ruby runtime, ThreadContext context, IRubyObject timeout) {
214
+ int result;
215
+
216
+ cancelKeys();
217
+ try {
218
+ context.getThread().beforeBlockingCall();
219
+ if(timeout.isNil()) {
220
+ result = this.selector.select();
221
+ } else {
222
+ double t = RubyNumeric.num2dbl(timeout);
223
+ if(t == 0) {
224
+ result = this.selector.selectNow();
225
+ } else if(t < 0) {
226
+ throw runtime.newArgumentError("time interval must be positive");
227
+ } else {
228
+ long timeoutMilliSeconds = (long)(t * 1000);
229
+ if(timeoutMilliSeconds == 0) {
230
+ result = this.selector.selectNow();
231
+ } else {
232
+ result = this.selector.select(timeoutMilliSeconds);
233
+ }
234
+ }
235
+ }
236
+ context.getThread().afterBlockingCall();
237
+ return result;
238
+ } catch(IOException ie) {
239
+ throw runtime.newIOError(ie.getLocalizedMessage());
240
+ }
241
+ }
242
+
243
+ /* Flush our internal buffer of cancelled keys */
244
+ private void cancelKeys() {
245
+ Iterator cancelledKeys = this.cancelledKeys.entrySet().iterator();
246
+ while(cancelledKeys.hasNext()) {
247
+ Map.Entry entry = (Map.Entry)cancelledKeys.next();
248
+ SelectionKey key = (SelectionKey)entry.getValue();
249
+ key.cancel();
250
+ cancelledKeys.remove();
251
+ }
252
+ }
253
+
254
+ // Remove connect interest from connected sockets
255
+ // See: http://stackoverflow.com/questions/204186/java-nio-select-returns-without-selected-keys-why
256
+ private void processKey(SelectionKey key) {
257
+ if((key.readyOps() & SelectionKey.OP_CONNECT) != 0) {
258
+ int interestOps = key.interestOps();
259
+
260
+ interestOps &= ~SelectionKey.OP_CONNECT;
261
+ interestOps |= SelectionKey.OP_WRITE;
262
+
263
+ key.interestOps(interestOps);
264
+ }
265
+ }
266
+
267
+ @JRubyMethod
268
+ public IRubyObject wakeup(ThreadContext context) {
269
+ if(!this.selector.isOpen()) {
270
+ throw context.getRuntime().newIOError("selector is closed");
271
+ }
272
+
273
+ this.wakeupFired = true;
274
+ this.selector.wakeup();
275
+
276
+ return context.nil;
277
+ }
278
+ }
@@ -29,6 +29,7 @@ static void NIO_Selector_free(struct NIO_Selector *loop);
29
29
 
30
30
  /* Methods */
31
31
  static VALUE NIO_Selector_initialize(VALUE self);
32
+ static VALUE NIO_Selector_backend(VALUE self);
32
33
  static VALUE NIO_Selector_register(VALUE self, VALUE selectable, VALUE interest);
33
34
  static VALUE NIO_Selector_deregister(VALUE self, VALUE io);
34
35
  static VALUE NIO_Selector_is_registered(VALUE self, VALUE io);
@@ -65,6 +66,7 @@ void Init_NIO_Selector()
65
66
  rb_define_alloc_func(cNIO_Selector, NIO_Selector_allocate);
66
67
 
67
68
  rb_define_method(cNIO_Selector, "initialize", NIO_Selector_initialize, 0);
69
+ rb_define_method(cNIO_Selector, "backend", NIO_Selector_backend, 0);
68
70
  rb_define_method(cNIO_Selector, "register", NIO_Selector_register, 2);
69
71
  rb_define_method(cNIO_Selector, "deregister", NIO_Selector_deregister, 1);
70
72
  rb_define_method(cNIO_Selector, "registered?", NIO_Selector_is_registered, 1);
@@ -101,6 +103,7 @@ static VALUE NIO_Selector_allocate(VALUE klass)
101
103
 
102
104
  selector = (struct NIO_Selector *)xmalloc(sizeof(struct NIO_Selector));
103
105
  selector->ev_loop = ev_loop_new(0);
106
+
104
107
  ev_init(&selector->timer, NIO_Selector_timeout_callback);
105
108
 
106
109
  selector->wakeup_reader = fds[0];
@@ -108,9 +111,10 @@ static VALUE NIO_Selector_allocate(VALUE klass)
108
111
 
109
112
  ev_io_init(&selector->wakeup, NIO_Selector_wakeup_callback, selector->wakeup_reader, EV_READ);
110
113
  selector->wakeup.data = (void *)selector;
114
+
111
115
  ev_io_start(selector->ev_loop, &selector->wakeup);
112
116
 
113
- selector->closed = selector->selecting = selector->ready_count = 0;
117
+ selector->closed = selector->selecting = selector->wakeup_fired = selector->ready_count = 0;
114
118
  selector->ready_array = Qnil;
115
119
 
116
120
  return Data_Wrap_Struct(klass, NIO_Selector_mark, NIO_Selector_free, selector);
@@ -139,6 +143,7 @@ static void NIO_Selector_shutdown(struct NIO_Selector *selector)
139
143
  ev_loop_destroy(selector->ev_loop);
140
144
  selector->ev_loop = 0;
141
145
  }
146
+
142
147
  selector->closed = 1;
143
148
  }
144
149
 
@@ -165,6 +170,30 @@ static VALUE NIO_Selector_initialize(VALUE self)
165
170
  return Qnil;
166
171
  }
167
172
 
173
+ static VALUE NIO_Selector_backend(VALUE self) {
174
+ struct NIO_Selector *selector;
175
+
176
+ Data_Get_Struct(self, struct NIO_Selector, selector);
177
+ if(selector->closed) {
178
+ rb_raise(rb_eIOError, "selector is closed");
179
+ }
180
+
181
+ switch (ev_backend(selector->ev_loop)) {
182
+ case EVBACKEND_EPOLL:
183
+ return ID2SYM(rb_intern("epoll"));
184
+ case EVBACKEND_POLL:
185
+ return ID2SYM(rb_intern("poll"));
186
+ case EVBACKEND_KQUEUE:
187
+ return ID2SYM(rb_intern("kqueue"));
188
+ case EVBACKEND_SELECT:
189
+ return ID2SYM(rb_intern("select"));
190
+ case EVBACKEND_PORT:
191
+ return ID2SYM(rb_intern("port"));
192
+ }
193
+
194
+ return ID2SYM(rb_intern("unknown"));
195
+ }
196
+
168
197
  /* Synchronize around a reentrant selector lock */
169
198
  static VALUE NIO_Selector_synchronize(VALUE self, VALUE (*func)(VALUE *args), VALUE *args)
170
199
  {
@@ -309,26 +338,32 @@ static VALUE NIO_Selector_select_synchronized(VALUE *args)
309
338
  }
310
339
 
311
340
  ready = NIO_Selector_run(selector, args[1]);
312
- if(ready > 0) {
313
- if(rb_block_given_p()) {
314
- return INT2NUM(ready);
315
- } else {
316
- ready_array = selector->ready_array;
341
+
342
+ /* Timeout */
343
+ if(ready < 0) {
344
+ if(!rb_block_given_p()) {
317
345
  selector->ready_array = Qnil;
318
- return ready_array;
319
346
  }
347
+
348
+ return Qnil;
349
+ }
350
+
351
+ if(rb_block_given_p()) {
352
+ return INT2NUM(ready);
320
353
  } else {
354
+ ready_array = selector->ready_array;
321
355
  selector->ready_array = Qnil;
322
- return Qnil;
356
+ return ready_array;
323
357
  }
324
358
  }
325
359
 
326
360
  static int NIO_Selector_run(struct NIO_Selector *selector, VALUE timeout)
327
361
  {
328
362
  int result;
363
+
329
364
  selector->selecting = 1;
365
+ selector->wakeup_fired = 0;
330
366
 
331
- #if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) || defined(HAVE_RB_THREAD_ALONE)
332
367
  /* Implement the optional timeout (if any) as a ev_timer */
333
368
  if(timeout != Qnil) {
334
369
  /* It seems libev is not a fan of timers being zero, so fudge a little */
@@ -337,54 +372,19 @@ static int NIO_Selector_run(struct NIO_Selector *selector, VALUE timeout)
337
372
  } else {
338
373
  ev_timer_stop(selector->ev_loop, &selector->timer);
339
374
  }
340
- #else
341
- /* Store when we started the loop so we can calculate the timeout */
342
- ev_tstamp started_at = ev_now(selector->ev_loop);
343
- #endif
344
375
 
345
- #if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
346
376
  /* libev is patched to release the GIL when it makes its system call */
347
377
  ev_loop(selector->ev_loop, EVLOOP_ONESHOT);
348
- #elif defined(HAVE_RB_THREAD_ALONE)
349
- /* If we're the only thread we can make a blocking system call */
350
- if(rb_thread_alone()) {
351
- #else
352
- /* If we don't have rb_thread_alone() we can't block */
353
- if(0) {
354
- #endif /* defined(HAVE_RB_THREAD_BLOCKING_REGION) */
355
-
356
- #if !defined(HAVE_RB_THREAD_BLOCKING_REGION) && !defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
357
- TRAP_BEG;
358
- ev_loop(selector->ev_loop, EVLOOP_ONESHOT);
359
- TRAP_END;
360
- } else {
361
- /* We need to busy wait as not to stall the green thread scheduler
362
- Ruby 1.8: just say no! :( */
363
- ev_timer_init(&selector->timer, NIO_Selector_timeout_callback, BUSYWAIT_INTERVAL, BUSYWAIT_INTERVAL);
364
- ev_timer_start(selector->ev_loop, &selector->timer);
365
-
366
- /* Loop until we receive events */
367
- while(selector->selecting && !selector->ready_count) {
368
- TRAP_BEG;
369
- ev_loop(selector->ev_loop, EVLOOP_ONESHOT);
370
- TRAP_END;
371
-
372
- /* Run the next green thread */
373
- rb_thread_schedule();
374
-
375
- /* Break if the timeout has elapsed */
376
- if(timeout != Qnil && ev_now(selector->ev_loop) - started_at >= NUM2DBL(timeout))
377
- break;
378
- }
379
-
380
- ev_timer_stop(selector->ev_loop, &selector->timer);
381
- }
382
- #endif /* defined(HAVE_RB_THREAD_BLOCKING_REGION) */
383
378
 
384
379
  result = selector->ready_count;
385
380
  selector->selecting = selector->ready_count = 0;
386
381
 
387
- return result;
382
+ if(result > 0 || selector->wakeup_fired) {
383
+ selector->wakeup_fired = 0;
384
+ return result;
385
+ } else {
386
+ return -1;
387
+ }
388
388
  }
389
389
 
390
390
  /* Wake the selector up from another thread */
@@ -397,7 +397,9 @@ static VALUE NIO_Selector_wakeup(VALUE self)
397
397
  rb_raise(rb_eIOError, "selector is closed");
398
398
  }
399
399
 
400
+ selector->wakeup_fired = 1;
400
401
  write(selector->wakeup_writer, "\0", 1);
402
+
401
403
  return Qnil;
402
404
  }
403
405
 
@@ -447,8 +449,6 @@ static VALUE NIO_Selector_is_empty(VALUE self)
447
449
  /* Called whenever a timeout fires on the event loop */
448
450
  static void NIO_Selector_timeout_callback(struct ev_loop *ev_loop, struct ev_timer *timer, int revents)
449
451
  {
450
- /* We don't actually need to do anything here, the mere firing of the
451
- timer is sufficient to interrupt the selector. However, libev still wants a callback */
452
452
  }
453
453
 
454
454
  /* Called whenever a wakeup request is sent to a selector */