nio4r 1.2.1-java → 2.0.0-java
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.
- checksums.yaml +4 -4
- data/.rspec +0 -1
- data/.rubocop.yml +31 -38
- data/.ruby-version +1 -0
- data/.travis.yml +15 -14
- data/CHANGES.md +75 -42
- data/Gemfile +10 -5
- data/Guardfile +10 -0
- data/LICENSE.txt +1 -1
- data/README.md +57 -161
- data/Rakefile +2 -1
- data/examples/echo_server.rb +1 -0
- data/ext/libev/Changes +4 -13
- data/ext/libev/ev.c +101 -74
- data/ext/libev/ev.h +3 -3
- data/ext/libev/ev_epoll.c +6 -3
- data/ext/libev/ev_kqueue.c +8 -4
- data/ext/libev/ev_poll.c +6 -3
- data/ext/libev/ev_port.c +8 -4
- data/ext/libev/ev_select.c +4 -2
- data/ext/nio4r/bytebuffer.c +421 -0
- data/ext/nio4r/extconf.rb +2 -10
- data/ext/nio4r/monitor.c +93 -46
- data/ext/nio4r/nio4r.h +11 -13
- data/ext/nio4r/org/nio4r/ByteBuffer.java +295 -0
- data/ext/nio4r/org/nio4r/Monitor.java +164 -0
- data/ext/nio4r/org/nio4r/Nio4r.java +22 -391
- data/ext/nio4r/org/nio4r/Selector.java +278 -0
- data/ext/nio4r/selector.c +55 -53
- data/lib/nio.rb +4 -3
- data/lib/nio/bytebuffer.rb +222 -0
- data/lib/nio/monitor.rb +64 -4
- data/lib/nio/selector.rb +52 -20
- data/lib/nio/version.rb +1 -1
- data/nio4r.gemspec +25 -19
- data/spec/nio/acceptables_spec.rb +6 -4
- data/spec/nio/bytebuffer_spec.rb +349 -0
- data/spec/nio/monitor_spec.rb +122 -79
- data/spec/nio/selectables/pipe_spec.rb +5 -1
- data/spec/nio/selectables/ssl_socket_spec.rb +15 -12
- data/spec/nio/selectables/tcp_socket_spec.rb +42 -31
- data/spec/nio/selectables/udp_socket_spec.rb +2 -0
- data/spec/nio/selector_spec.rb +10 -4
- data/spec/spec_helper.rb +24 -3
- data/spec/support/selectable_examples.rb +7 -5
- data/tasks/extension.rake +2 -0
- data/tasks/rspec.rake +2 -0
- data/tasks/rubocop.rake +2 -0
- metadata +21 -14
- 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
|
-
|
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
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
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
|
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
|
-
|
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 */
|