nio4r 1.2.1-java → 2.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
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 +15 -14
  6. data/CHANGES.md +75 -42
  7. data/Gemfile +10 -5
  8. data/Guardfile +10 -0
  9. data/LICENSE.txt +1 -1
  10. data/README.md +57 -161
  11. data/Rakefile +2 -1
  12. data/examples/echo_server.rb +1 -0
  13. data/ext/libev/Changes +4 -13
  14. data/ext/libev/ev.c +101 -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 +421 -0
  22. data/ext/nio4r/extconf.rb +2 -10
  23. data/ext/nio4r/monitor.c +93 -46
  24. data/ext/nio4r/nio4r.h +11 -13
  25. data/ext/nio4r/org/nio4r/ByteBuffer.java +295 -0
  26. data/ext/nio4r/org/nio4r/Monitor.java +164 -0
  27. data/ext/nio4r/org/nio4r/Nio4r.java +22 -391
  28. data/ext/nio4r/org/nio4r/Selector.java +278 -0
  29. data/ext/nio4r/selector.c +55 -53
  30. data/lib/nio.rb +4 -3
  31. data/lib/nio/bytebuffer.rb +222 -0
  32. data/lib/nio/monitor.rb +64 -4
  33. data/lib/nio/selector.rb +52 -20
  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 +349 -0
  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 +21 -14
  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
+ }
data/ext/nio4r/selector.c CHANGED
@@ -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);
@@ -93,12 +95,15 @@ static VALUE NIO_Selector_allocate(VALUE klass)
93
95
  rb_sys_fail("pipe");
94
96
  }
95
97
 
96
- if(fcntl(fds[0], F_SETFL, O_NONBLOCK) < 0) {
98
+ /* Use non-blocking reads/writes during wakeup, in case the buffer is full */
99
+ if(fcntl(fds[0], F_SETFL, O_NONBLOCK) < 0 ||
100
+ fcntl(fds[1], F_SETFL, O_NONBLOCK) < 0) {
97
101
  rb_sys_fail("fcntl");
98
102
  }
99
103
 
100
104
  selector = (struct NIO_Selector *)xmalloc(sizeof(struct NIO_Selector));
101
105
  selector->ev_loop = ev_loop_new(0);
106
+
102
107
  ev_init(&selector->timer, NIO_Selector_timeout_callback);
103
108
 
104
109
  selector->wakeup_reader = fds[0];
@@ -106,9 +111,10 @@ static VALUE NIO_Selector_allocate(VALUE klass)
106
111
 
107
112
  ev_io_init(&selector->wakeup, NIO_Selector_wakeup_callback, selector->wakeup_reader, EV_READ);
108
113
  selector->wakeup.data = (void *)selector;
114
+
109
115
  ev_io_start(selector->ev_loop, &selector->wakeup);
110
116
 
111
- selector->closed = selector->selecting = selector->ready_count = 0;
117
+ selector->closed = selector->selecting = selector->wakeup_fired = selector->ready_count = 0;
112
118
  selector->ready_array = Qnil;
113
119
 
114
120
  return Data_Wrap_Struct(klass, NIO_Selector_mark, NIO_Selector_free, selector);
@@ -137,6 +143,7 @@ static void NIO_Selector_shutdown(struct NIO_Selector *selector)
137
143
  ev_loop_destroy(selector->ev_loop);
138
144
  selector->ev_loop = 0;
139
145
  }
146
+
140
147
  selector->closed = 1;
141
148
  }
142
149
 
@@ -163,6 +170,30 @@ static VALUE NIO_Selector_initialize(VALUE self)
163
170
  return Qnil;
164
171
  }
165
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
+
166
197
  /* Synchronize around a reentrant selector lock */
167
198
  static VALUE NIO_Selector_synchronize(VALUE self, VALUE (*func)(VALUE *args), VALUE *args)
168
199
  {
@@ -307,26 +338,32 @@ static VALUE NIO_Selector_select_synchronized(VALUE *args)
307
338
  }
308
339
 
309
340
  ready = NIO_Selector_run(selector, args[1]);
310
- if(ready > 0) {
311
- if(rb_block_given_p()) {
312
- return INT2NUM(ready);
313
- } else {
314
- ready_array = selector->ready_array;
341
+
342
+ /* Timeout */
343
+ if(ready < 0) {
344
+ if(!rb_block_given_p()) {
315
345
  selector->ready_array = Qnil;
316
- return ready_array;
317
346
  }
347
+
348
+ return Qnil;
349
+ }
350
+
351
+ if(rb_block_given_p()) {
352
+ return INT2NUM(ready);
318
353
  } else {
354
+ ready_array = selector->ready_array;
319
355
  selector->ready_array = Qnil;
320
- return Qnil;
356
+ return ready_array;
321
357
  }
322
358
  }
323
359
 
324
360
  static int NIO_Selector_run(struct NIO_Selector *selector, VALUE timeout)
325
361
  {
326
362
  int result;
363
+
327
364
  selector->selecting = 1;
365
+ selector->wakeup_fired = 0;
328
366
 
329
- #if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) || defined(HAVE_RB_THREAD_ALONE)
330
367
  /* Implement the optional timeout (if any) as a ev_timer */
331
368
  if(timeout != Qnil) {
332
369
  /* It seems libev is not a fan of timers being zero, so fudge a little */
@@ -335,54 +372,19 @@ static int NIO_Selector_run(struct NIO_Selector *selector, VALUE timeout)
335
372
  } else {
336
373
  ev_timer_stop(selector->ev_loop, &selector->timer);
337
374
  }
338
- #else
339
- /* Store when we started the loop so we can calculate the timeout */
340
- ev_tstamp started_at = ev_now(selector->ev_loop);
341
- #endif
342
375
 
343
- #if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
344
376
  /* libev is patched to release the GIL when it makes its system call */
345
377
  ev_loop(selector->ev_loop, EVLOOP_ONESHOT);
346
- #elif defined(HAVE_RB_THREAD_ALONE)
347
- /* If we're the only thread we can make a blocking system call */
348
- if(rb_thread_alone()) {
349
- #else
350
- /* If we don't have rb_thread_alone() we can't block */
351
- if(0) {
352
- #endif /* defined(HAVE_RB_THREAD_BLOCKING_REGION) */
353
-
354
- #if !defined(HAVE_RB_THREAD_BLOCKING_REGION) && !defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
355
- TRAP_BEG;
356
- ev_loop(selector->ev_loop, EVLOOP_ONESHOT);
357
- TRAP_END;
358
- } else {
359
- /* We need to busy wait as not to stall the green thread scheduler
360
- Ruby 1.8: just say no! :( */
361
- ev_timer_init(&selector->timer, NIO_Selector_timeout_callback, BUSYWAIT_INTERVAL, BUSYWAIT_INTERVAL);
362
- ev_timer_start(selector->ev_loop, &selector->timer);
363
-
364
- /* Loop until we receive events */
365
- while(selector->selecting && !selector->ready_count) {
366
- TRAP_BEG;
367
- ev_loop(selector->ev_loop, EVLOOP_ONESHOT);
368
- TRAP_END;
369
-
370
- /* Run the next green thread */
371
- rb_thread_schedule();
372
-
373
- /* Break if the timeout has elapsed */
374
- if(timeout != Qnil && ev_now(selector->ev_loop) - started_at >= NUM2DBL(timeout))
375
- break;
376
- }
377
-
378
- ev_timer_stop(selector->ev_loop, &selector->timer);
379
- }
380
- #endif /* defined(HAVE_RB_THREAD_BLOCKING_REGION) */
381
378
 
382
379
  result = selector->ready_count;
383
380
  selector->selecting = selector->ready_count = 0;
384
381
 
385
- return result;
382
+ if(result > 0 || selector->wakeup_fired) {
383
+ selector->wakeup_fired = 0;
384
+ return result;
385
+ } else {
386
+ return -1;
387
+ }
386
388
  }
387
389
 
388
390
  /* Wake the selector up from another thread */
@@ -395,7 +397,9 @@ static VALUE NIO_Selector_wakeup(VALUE self)
395
397
  rb_raise(rb_eIOError, "selector is closed");
396
398
  }
397
399
 
400
+ selector->wakeup_fired = 1;
398
401
  write(selector->wakeup_writer, "\0", 1);
402
+
399
403
  return Qnil;
400
404
  }
401
405
 
@@ -445,8 +449,6 @@ static VALUE NIO_Selector_is_empty(VALUE self)
445
449
  /* Called whenever a timeout fires on the event loop */
446
450
  static void NIO_Selector_timeout_callback(struct ev_loop *ev_loop, struct ev_timer *timer, int revents)
447
451
  {
448
- /* We don't actually need to do anything here, the mere firing of the
449
- timer is sufficient to interrupt the selector. However, libev still wants a callback */
450
452
  }
451
453
 
452
454
  /* Called whenever a wakeup request is sent to a selector */