nio4r 1.2.1 → 2.5.3
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 +5 -5
- data/.github/workflows/workflow.yml +43 -0
- data/.gitignore +1 -0
- data/.rspec +0 -1
- data/.rubocop.yml +70 -31
- data/CHANGES.md +190 -42
- data/Gemfile +8 -4
- data/Guardfile +10 -0
- data/README.md +102 -147
- data/Rakefile +3 -4
- data/examples/echo_server.rb +3 -2
- data/ext/libev/Changes +44 -13
- data/ext/libev/README +2 -1
- data/ext/libev/ev.c +314 -225
- data/ext/libev/ev.h +90 -88
- data/ext/libev/ev_epoll.c +30 -16
- data/ext/libev/ev_kqueue.c +19 -9
- data/ext/libev/ev_linuxaio.c +642 -0
- data/ext/libev/ev_poll.c +19 -11
- data/ext/libev/ev_port.c +13 -6
- data/ext/libev/ev_select.c +4 -2
- data/ext/libev/ev_vars.h +14 -3
- data/ext/libev/ev_wrap.h +16 -0
- data/ext/nio4r/bytebuffer.c +429 -0
- data/ext/nio4r/extconf.rb +17 -30
- data/ext/nio4r/monitor.c +113 -49
- data/ext/nio4r/nio4r.h +11 -13
- data/ext/nio4r/org/nio4r/ByteBuffer.java +293 -0
- data/ext/nio4r/org/nio4r/Monitor.java +175 -0
- data/ext/nio4r/org/nio4r/Nio4r.java +22 -391
- data/ext/nio4r/org/nio4r/Selector.java +299 -0
- data/ext/nio4r/selector.c +155 -68
- data/lib/nio.rb +4 -4
- data/lib/nio/bytebuffer.rb +229 -0
- data/lib/nio/monitor.rb +73 -11
- data/lib/nio/selector.rb +64 -21
- data/lib/nio/version.rb +1 -1
- data/nio4r.gemspec +34 -20
- data/{tasks → rakelib}/extension.rake +4 -0
- data/{tasks → rakelib}/rspec.rake +2 -0
- data/{tasks → rakelib}/rubocop.rake +2 -0
- data/spec/nio/acceptables_spec.rb +5 -5
- data/spec/nio/bytebuffer_spec.rb +354 -0
- data/spec/nio/monitor_spec.rb +128 -79
- data/spec/nio/selectables/pipe_spec.rb +12 -3
- data/spec/nio/selectables/ssl_socket_spec.rb +61 -29
- data/spec/nio/selectables/tcp_socket_spec.rb +47 -34
- data/spec/nio/selectables/udp_socket_spec.rb +24 -7
- data/spec/nio/selector_spec.rb +65 -16
- data/spec/spec_helper.rb +12 -3
- data/spec/support/selectable_examples.rb +45 -18
- metadata +33 -23
- data/.rubocop_todo.yml +0 -35
- data/.travis.yml +0 -27
- data/LICENSE.txt +0 -20
- data/ext/libev/README.embed +0 -3
- data/ext/libev/test_libev_win32.c +0 -123
@@ -0,0 +1,299 @@
|
|
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
|
+
import java.nio.channels.CancelledKeyException;
|
11
|
+
|
12
|
+
import org.jruby.Ruby;
|
13
|
+
import org.jruby.RubyArray;
|
14
|
+
import org.jruby.RubyClass;
|
15
|
+
import org.jruby.RubyIO;
|
16
|
+
import org.jruby.RubyNumeric;
|
17
|
+
import org.jruby.RubyObject;
|
18
|
+
import org.jruby.anno.JRubyMethod;
|
19
|
+
import org.jruby.runtime.Block;
|
20
|
+
import org.jruby.runtime.ThreadContext;
|
21
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
22
|
+
import org.jruby.util.io.OpenFile;
|
23
|
+
|
24
|
+
import org.nio4r.Monitor;
|
25
|
+
|
26
|
+
public class Selector extends RubyObject {
|
27
|
+
private java.nio.channels.Selector selector;
|
28
|
+
private HashMap<SelectableChannel,SelectionKey> cancelledKeys;
|
29
|
+
private volatile boolean wakeupFired;
|
30
|
+
|
31
|
+
public Selector(final Ruby ruby, RubyClass rubyClass) {
|
32
|
+
super(ruby, rubyClass);
|
33
|
+
}
|
34
|
+
|
35
|
+
@JRubyMethod(meta = true)
|
36
|
+
public static IRubyObject backends(ThreadContext context, IRubyObject self) {
|
37
|
+
return context.runtime.newArray(context.runtime.newSymbol("java"));
|
38
|
+
}
|
39
|
+
|
40
|
+
@JRubyMethod
|
41
|
+
public IRubyObject initialize(ThreadContext context) {
|
42
|
+
initialize(context, context.runtime.newSymbol("java"));
|
43
|
+
return context.nil;
|
44
|
+
}
|
45
|
+
|
46
|
+
@JRubyMethod
|
47
|
+
public IRubyObject initialize(ThreadContext context, IRubyObject backend) {
|
48
|
+
if(backend != context.runtime.newSymbol("java")) {
|
49
|
+
throw context.runtime.newArgumentError(":java is the only supported backend");
|
50
|
+
}
|
51
|
+
|
52
|
+
this.cancelledKeys = new HashMap<SelectableChannel,SelectionKey>();
|
53
|
+
this.wakeupFired = false;
|
54
|
+
|
55
|
+
try {
|
56
|
+
this.selector = java.nio.channels.Selector.open();
|
57
|
+
} catch(IOException ie) {
|
58
|
+
throw context.runtime.newIOError(ie.getLocalizedMessage());
|
59
|
+
}
|
60
|
+
|
61
|
+
return context.nil;
|
62
|
+
}
|
63
|
+
|
64
|
+
@JRubyMethod
|
65
|
+
public IRubyObject backend(ThreadContext context) {
|
66
|
+
return context.runtime.newSymbol("java");
|
67
|
+
}
|
68
|
+
|
69
|
+
@JRubyMethod
|
70
|
+
public IRubyObject close(ThreadContext context) {
|
71
|
+
try {
|
72
|
+
this.selector.close();
|
73
|
+
} catch(IOException ie) {
|
74
|
+
throw context.runtime.newIOError(ie.getLocalizedMessage());
|
75
|
+
}
|
76
|
+
|
77
|
+
return context.nil;
|
78
|
+
}
|
79
|
+
|
80
|
+
@JRubyMethod(name = "closed?")
|
81
|
+
public IRubyObject isClosed(ThreadContext context) {
|
82
|
+
Ruby runtime = context.getRuntime();
|
83
|
+
return this.selector.isOpen() ? runtime.getFalse() : runtime.getTrue();
|
84
|
+
}
|
85
|
+
|
86
|
+
@JRubyMethod(name = "empty?")
|
87
|
+
public IRubyObject isEmpty(ThreadContext context) {
|
88
|
+
Ruby runtime = context.getRuntime();
|
89
|
+
return this.selector.keys().isEmpty() ? runtime.getTrue() : runtime.getFalse();
|
90
|
+
}
|
91
|
+
|
92
|
+
@JRubyMethod
|
93
|
+
public IRubyObject register(ThreadContext context, IRubyObject io, IRubyObject interests) {
|
94
|
+
Ruby runtime = context.getRuntime();
|
95
|
+
Channel rawChannel = RubyIO.convertToIO(context, io).getChannel();
|
96
|
+
|
97
|
+
if(!this.selector.isOpen()) {
|
98
|
+
throw context.getRuntime().newIOError("selector is closed");
|
99
|
+
}
|
100
|
+
|
101
|
+
if(!(rawChannel instanceof SelectableChannel)) {
|
102
|
+
throw runtime.newArgumentError("not a selectable IO object");
|
103
|
+
}
|
104
|
+
|
105
|
+
SelectableChannel channel = (SelectableChannel)rawChannel;
|
106
|
+
|
107
|
+
try {
|
108
|
+
channel.configureBlocking(false);
|
109
|
+
} catch(IOException ie) {
|
110
|
+
throw runtime.newIOError(ie.getLocalizedMessage());
|
111
|
+
}
|
112
|
+
|
113
|
+
int interestOps = Nio4r.symbolToInterestOps(runtime, channel, interests);
|
114
|
+
SelectionKey key;
|
115
|
+
|
116
|
+
key = this.cancelledKeys.remove(channel);
|
117
|
+
|
118
|
+
if(key != null) {
|
119
|
+
key.interestOps(interestOps);
|
120
|
+
} else {
|
121
|
+
try {
|
122
|
+
key = channel.register(this.selector, interestOps);
|
123
|
+
} catch(java.lang.IllegalArgumentException ia) {
|
124
|
+
throw runtime.newArgumentError("mode not supported for this object: " + interests);
|
125
|
+
} catch(java.nio.channels.ClosedChannelException cce) {
|
126
|
+
throw context.runtime.newIOError(cce.getLocalizedMessage());
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
130
|
+
RubyClass monitorClass = runtime.getModule("NIO").getClass("Monitor");
|
131
|
+
Monitor monitor = (Monitor)monitorClass.newInstance(context, io, interests, this, null);
|
132
|
+
monitor.setSelectionKey(key);
|
133
|
+
|
134
|
+
return monitor;
|
135
|
+
}
|
136
|
+
|
137
|
+
@JRubyMethod
|
138
|
+
public IRubyObject deregister(ThreadContext context, IRubyObject io) {
|
139
|
+
Ruby runtime = context.getRuntime();
|
140
|
+
OpenFile file = RubyIO.convertToIO(context, io).getOpenFileInitialized();
|
141
|
+
if (file.fd() == null)
|
142
|
+
return context.nil;
|
143
|
+
Channel rawChannel = file.channel();
|
144
|
+
|
145
|
+
if(!(rawChannel instanceof SelectableChannel)) {
|
146
|
+
throw runtime.newArgumentError("not a selectable IO object");
|
147
|
+
}
|
148
|
+
|
149
|
+
SelectableChannel channel = (SelectableChannel)rawChannel;
|
150
|
+
SelectionKey key = channel.keyFor(this.selector);
|
151
|
+
|
152
|
+
if(key == null)
|
153
|
+
return context.nil;
|
154
|
+
|
155
|
+
Monitor monitor = (Monitor)key.attachment();
|
156
|
+
monitor.close(context, runtime.getFalse());
|
157
|
+
cancelledKeys.put(channel, key);
|
158
|
+
|
159
|
+
return monitor;
|
160
|
+
}
|
161
|
+
|
162
|
+
@JRubyMethod(name = "registered?")
|
163
|
+
public IRubyObject isRegistered(ThreadContext context, IRubyObject io) {
|
164
|
+
Ruby runtime = context.getRuntime();
|
165
|
+
Channel rawChannel = RubyIO.convertToIO(context, io).getChannel();
|
166
|
+
|
167
|
+
if(!(rawChannel instanceof SelectableChannel)) {
|
168
|
+
throw runtime.newArgumentError("not a selectable IO object");
|
169
|
+
}
|
170
|
+
|
171
|
+
SelectableChannel channel = (SelectableChannel)rawChannel;
|
172
|
+
SelectionKey key = channel.keyFor(this.selector);
|
173
|
+
|
174
|
+
if(key == null)
|
175
|
+
return context.nil;
|
176
|
+
|
177
|
+
|
178
|
+
if(((Monitor)key.attachment()).isClosed(context) == runtime.getTrue()) {
|
179
|
+
return runtime.getFalse();
|
180
|
+
} else {
|
181
|
+
return runtime.getTrue();
|
182
|
+
}
|
183
|
+
}
|
184
|
+
|
185
|
+
@JRubyMethod
|
186
|
+
public synchronized IRubyObject select(ThreadContext context, Block block) {
|
187
|
+
return select(context, context.nil, block);
|
188
|
+
}
|
189
|
+
|
190
|
+
@JRubyMethod
|
191
|
+
public synchronized IRubyObject select(ThreadContext context, IRubyObject timeout, Block block) {
|
192
|
+
Ruby runtime = context.getRuntime();
|
193
|
+
|
194
|
+
if(!this.selector.isOpen()) {
|
195
|
+
throw context.getRuntime().newIOError("selector is closed");
|
196
|
+
}
|
197
|
+
|
198
|
+
this.wakeupFired = false;
|
199
|
+
int ready = doSelect(runtime, context, timeout);
|
200
|
+
|
201
|
+
/* Timeout */
|
202
|
+
if(ready <= 0 && !this.wakeupFired) {
|
203
|
+
return context.nil;
|
204
|
+
}
|
205
|
+
|
206
|
+
RubyArray array = null;
|
207
|
+
|
208
|
+
if(!block.isGiven()) {
|
209
|
+
array = runtime.newArray(this.selector.selectedKeys().size());
|
210
|
+
}
|
211
|
+
|
212
|
+
Iterator selectedKeys = this.selector.selectedKeys().iterator();
|
213
|
+
while(selectedKeys.hasNext()) {
|
214
|
+
SelectionKey key = (SelectionKey)selectedKeys.next();
|
215
|
+
processKey(key);
|
216
|
+
|
217
|
+
selectedKeys.remove();
|
218
|
+
|
219
|
+
if(block.isGiven()) {
|
220
|
+
block.call(context, (IRubyObject)key.attachment());
|
221
|
+
} else {
|
222
|
+
array.add(key.attachment());
|
223
|
+
}
|
224
|
+
}
|
225
|
+
|
226
|
+
if(block.isGiven()) {
|
227
|
+
return RubyNumeric.int2fix(runtime, ready);
|
228
|
+
} else {
|
229
|
+
return array;
|
230
|
+
}
|
231
|
+
}
|
232
|
+
|
233
|
+
/* Run the selector */
|
234
|
+
private int doSelect(Ruby runtime, ThreadContext context, IRubyObject timeout) {
|
235
|
+
int result;
|
236
|
+
|
237
|
+
cancelKeys();
|
238
|
+
try {
|
239
|
+
context.getThread().beforeBlockingCall();
|
240
|
+
if(timeout.isNil()) {
|
241
|
+
result = this.selector.select();
|
242
|
+
} else {
|
243
|
+
double t = RubyNumeric.num2dbl(timeout);
|
244
|
+
if(t == 0) {
|
245
|
+
result = this.selector.selectNow();
|
246
|
+
} else if(t < 0) {
|
247
|
+
throw runtime.newArgumentError("time interval must be positive");
|
248
|
+
} else {
|
249
|
+
long timeoutMilliSeconds = (long)(t * 1000);
|
250
|
+
if(timeoutMilliSeconds == 0) {
|
251
|
+
result = this.selector.selectNow();
|
252
|
+
} else {
|
253
|
+
result = this.selector.select(timeoutMilliSeconds);
|
254
|
+
}
|
255
|
+
}
|
256
|
+
}
|
257
|
+
context.getThread().afterBlockingCall();
|
258
|
+
return result;
|
259
|
+
} catch(IOException ie) {
|
260
|
+
throw runtime.newIOError(ie.getLocalizedMessage());
|
261
|
+
}
|
262
|
+
}
|
263
|
+
|
264
|
+
/* Flush our internal buffer of cancelled keys */
|
265
|
+
private void cancelKeys() {
|
266
|
+
Iterator cancelledKeys = this.cancelledKeys.entrySet().iterator();
|
267
|
+
while(cancelledKeys.hasNext()) {
|
268
|
+
Map.Entry entry = (Map.Entry)cancelledKeys.next();
|
269
|
+
SelectionKey key = (SelectionKey)entry.getValue();
|
270
|
+
key.cancel();
|
271
|
+
cancelledKeys.remove();
|
272
|
+
}
|
273
|
+
}
|
274
|
+
|
275
|
+
// Remove connect interest from connected sockets
|
276
|
+
// See: http://stackoverflow.com/questions/204186/java-nio-select-returns-without-selected-keys-why
|
277
|
+
private void processKey(SelectionKey key) {
|
278
|
+
if(key.isValid() && (key.readyOps() & SelectionKey.OP_CONNECT) != 0) {
|
279
|
+
int interestOps = key.interestOps();
|
280
|
+
|
281
|
+
interestOps &= ~SelectionKey.OP_CONNECT;
|
282
|
+
interestOps |= SelectionKey.OP_WRITE;
|
283
|
+
|
284
|
+
key.interestOps(interestOps);
|
285
|
+
}
|
286
|
+
}
|
287
|
+
|
288
|
+
@JRubyMethod
|
289
|
+
public IRubyObject wakeup(ThreadContext context) {
|
290
|
+
if(!this.selector.isOpen()) {
|
291
|
+
throw context.getRuntime().newIOError("selector is closed");
|
292
|
+
}
|
293
|
+
|
294
|
+
this.wakeupFired = true;
|
295
|
+
this.selector.wakeup();
|
296
|
+
|
297
|
+
return context.nil;
|
298
|
+
}
|
299
|
+
}
|
data/ext/nio4r/selector.c
CHANGED
@@ -27,8 +27,12 @@ static void NIO_Selector_mark(struct NIO_Selector *loop);
|
|
27
27
|
static void NIO_Selector_shutdown(struct NIO_Selector *selector);
|
28
28
|
static void NIO_Selector_free(struct NIO_Selector *loop);
|
29
29
|
|
30
|
-
/*
|
31
|
-
static VALUE
|
30
|
+
/* Class methods */
|
31
|
+
static VALUE NIO_Selector_supported_backends(VALUE klass);
|
32
|
+
|
33
|
+
/* Instance methods */
|
34
|
+
static VALUE NIO_Selector_initialize(int argc, VALUE *argv, VALUE self);
|
35
|
+
static VALUE NIO_Selector_backend(VALUE self);
|
32
36
|
static VALUE NIO_Selector_register(VALUE self, VALUE selectable, VALUE interest);
|
33
37
|
static VALUE NIO_Selector_deregister(VALUE self, VALUE io);
|
34
38
|
static VALUE NIO_Selector_is_registered(VALUE self, VALUE io);
|
@@ -64,7 +68,9 @@ void Init_NIO_Selector()
|
|
64
68
|
cNIO_Selector = rb_define_class_under(mNIO, "Selector", rb_cObject);
|
65
69
|
rb_define_alloc_func(cNIO_Selector, NIO_Selector_allocate);
|
66
70
|
|
67
|
-
|
71
|
+
rb_define_singleton_method(cNIO_Selector, "backends", NIO_Selector_supported_backends, 0);
|
72
|
+
rb_define_method(cNIO_Selector, "initialize", NIO_Selector_initialize, -1);
|
73
|
+
rb_define_method(cNIO_Selector, "backend", NIO_Selector_backend, 0);
|
68
74
|
rb_define_method(cNIO_Selector, "register", NIO_Selector_register, 2);
|
69
75
|
rb_define_method(cNIO_Selector, "deregister", NIO_Selector_deregister, 1);
|
70
76
|
rb_define_method(cNIO_Selector, "registered?", NIO_Selector_is_registered, 1);
|
@@ -93,12 +99,17 @@ static VALUE NIO_Selector_allocate(VALUE klass)
|
|
93
99
|
rb_sys_fail("pipe");
|
94
100
|
}
|
95
101
|
|
96
|
-
|
102
|
+
/* Use non-blocking reads/writes during wakeup, in case the buffer is full */
|
103
|
+
if(fcntl(fds[0], F_SETFL, O_NONBLOCK) < 0 ||
|
104
|
+
fcntl(fds[1], F_SETFL, O_NONBLOCK) < 0) {
|
97
105
|
rb_sys_fail("fcntl");
|
98
106
|
}
|
99
107
|
|
100
108
|
selector = (struct NIO_Selector *)xmalloc(sizeof(struct NIO_Selector));
|
101
|
-
|
109
|
+
|
110
|
+
/* Defer initializing the loop to #initialize */
|
111
|
+
selector->ev_loop = 0;
|
112
|
+
|
102
113
|
ev_init(&selector->timer, NIO_Selector_timeout_callback);
|
103
114
|
|
104
115
|
selector->wakeup_reader = fds[0];
|
@@ -106,9 +117,8 @@ static VALUE NIO_Selector_allocate(VALUE klass)
|
|
106
117
|
|
107
118
|
ev_io_init(&selector->wakeup, NIO_Selector_wakeup_callback, selector->wakeup_reader, EV_READ);
|
108
119
|
selector->wakeup.data = (void *)selector;
|
109
|
-
ev_io_start(selector->ev_loop, &selector->wakeup);
|
110
120
|
|
111
|
-
selector->closed = selector->selecting = selector->ready_count = 0;
|
121
|
+
selector->closed = selector->selecting = selector->wakeup_fired = selector->ready_count = 0;
|
112
122
|
selector->ready_array = Qnil;
|
113
123
|
|
114
124
|
return Data_Wrap_Struct(klass, NIO_Selector_mark, NIO_Selector_free, selector);
|
@@ -137,6 +147,7 @@ static void NIO_Selector_shutdown(struct NIO_Selector *selector)
|
|
137
147
|
ev_loop_destroy(selector->ev_loop);
|
138
148
|
selector->ev_loop = 0;
|
139
149
|
}
|
150
|
+
|
140
151
|
selector->closed = 1;
|
141
152
|
}
|
142
153
|
|
@@ -147,12 +158,83 @@ static void NIO_Selector_free(struct NIO_Selector *selector)
|
|
147
158
|
xfree(selector);
|
148
159
|
}
|
149
160
|
|
161
|
+
/* Return an array of symbols for supported backends */
|
162
|
+
static VALUE NIO_Selector_supported_backends(VALUE klass) {
|
163
|
+
unsigned int backends = ev_supported_backends();
|
164
|
+
VALUE result = rb_ary_new();
|
165
|
+
|
166
|
+
if(backends & EVBACKEND_EPOLL) {
|
167
|
+
rb_ary_push(result, ID2SYM(rb_intern("epoll")));
|
168
|
+
}
|
169
|
+
|
170
|
+
if(backends & EVBACKEND_POLL) {
|
171
|
+
rb_ary_push(result, ID2SYM(rb_intern("poll")));
|
172
|
+
}
|
173
|
+
|
174
|
+
if(backends & EVBACKEND_KQUEUE) {
|
175
|
+
rb_ary_push(result, ID2SYM(rb_intern("kqueue")));
|
176
|
+
}
|
177
|
+
|
178
|
+
if(backends & EVBACKEND_SELECT) {
|
179
|
+
rb_ary_push(result, ID2SYM(rb_intern("select")));
|
180
|
+
}
|
181
|
+
|
182
|
+
if(backends & EVBACKEND_PORT) {
|
183
|
+
rb_ary_push(result, ID2SYM(rb_intern("port")));
|
184
|
+
}
|
185
|
+
|
186
|
+
return result;
|
187
|
+
}
|
188
|
+
|
150
189
|
/* Create a new selector. This is more or less the pure Ruby version
|
151
190
|
translated into an MRI cext */
|
152
|
-
static VALUE NIO_Selector_initialize(VALUE self)
|
191
|
+
static VALUE NIO_Selector_initialize(int argc, VALUE *argv, VALUE self)
|
153
192
|
{
|
193
|
+
ID backend_id;
|
194
|
+
VALUE backend;
|
154
195
|
VALUE lock;
|
155
196
|
|
197
|
+
struct NIO_Selector *selector;
|
198
|
+
unsigned int flags = 0;
|
199
|
+
|
200
|
+
Data_Get_Struct(self, struct NIO_Selector, selector);
|
201
|
+
|
202
|
+
rb_scan_args(argc, argv, "01", &backend);
|
203
|
+
|
204
|
+
if(backend != Qnil) {
|
205
|
+
if(!rb_ary_includes(NIO_Selector_supported_backends(CLASS_OF(self)), backend)) {
|
206
|
+
rb_raise(rb_eArgError, "unsupported backend: %s",
|
207
|
+
RSTRING_PTR(rb_funcall(backend, rb_intern("inspect"), 0)));
|
208
|
+
}
|
209
|
+
|
210
|
+
backend_id = SYM2ID(backend);
|
211
|
+
|
212
|
+
if(backend_id == rb_intern("epoll")) {
|
213
|
+
flags = EVBACKEND_EPOLL;
|
214
|
+
} else if(backend_id == rb_intern("poll")) {
|
215
|
+
flags = EVBACKEND_POLL;
|
216
|
+
} else if(backend_id == rb_intern("kqueue")) {
|
217
|
+
flags = EVBACKEND_KQUEUE;
|
218
|
+
} else if(backend_id == rb_intern("select")) {
|
219
|
+
flags = EVBACKEND_SELECT;
|
220
|
+
} else if(backend_id == rb_intern("port")) {
|
221
|
+
flags = EVBACKEND_PORT;
|
222
|
+
} else {
|
223
|
+
rb_raise(rb_eArgError, "unsupported backend: %s",
|
224
|
+
RSTRING_PTR(rb_funcall(backend, rb_intern("inspect"), 0)));
|
225
|
+
}
|
226
|
+
}
|
227
|
+
|
228
|
+
/* Ensure the selector loop has not yet been initialized */
|
229
|
+
assert(!selector->ev_loop);
|
230
|
+
|
231
|
+
selector->ev_loop = ev_loop_new(flags);
|
232
|
+
if(!selector->ev_loop) {
|
233
|
+
rb_raise(rb_eIOError, "error initializing event loop");
|
234
|
+
}
|
235
|
+
|
236
|
+
ev_io_start(selector->ev_loop, &selector->wakeup);
|
237
|
+
|
156
238
|
rb_ivar_set(self, rb_intern("selectables"), rb_hash_new());
|
157
239
|
rb_ivar_set(self, rb_intern("lock_holder"), Qnil);
|
158
240
|
|
@@ -163,6 +245,30 @@ static VALUE NIO_Selector_initialize(VALUE self)
|
|
163
245
|
return Qnil;
|
164
246
|
}
|
165
247
|
|
248
|
+
static VALUE NIO_Selector_backend(VALUE self) {
|
249
|
+
struct NIO_Selector *selector;
|
250
|
+
|
251
|
+
Data_Get_Struct(self, struct NIO_Selector, selector);
|
252
|
+
if(selector->closed) {
|
253
|
+
rb_raise(rb_eIOError, "selector is closed");
|
254
|
+
}
|
255
|
+
|
256
|
+
switch (ev_backend(selector->ev_loop)) {
|
257
|
+
case EVBACKEND_EPOLL:
|
258
|
+
return ID2SYM(rb_intern("epoll"));
|
259
|
+
case EVBACKEND_POLL:
|
260
|
+
return ID2SYM(rb_intern("poll"));
|
261
|
+
case EVBACKEND_KQUEUE:
|
262
|
+
return ID2SYM(rb_intern("kqueue"));
|
263
|
+
case EVBACKEND_SELECT:
|
264
|
+
return ID2SYM(rb_intern("select"));
|
265
|
+
case EVBACKEND_PORT:
|
266
|
+
return ID2SYM(rb_intern("port"));
|
267
|
+
}
|
268
|
+
|
269
|
+
return ID2SYM(rb_intern("unknown"));
|
270
|
+
}
|
271
|
+
|
166
272
|
/* Synchronize around a reentrant selector lock */
|
167
273
|
static VALUE NIO_Selector_synchronize(VALUE self, VALUE (*func)(VALUE *args), VALUE *args)
|
168
274
|
{
|
@@ -173,7 +279,7 @@ static VALUE NIO_Selector_synchronize(VALUE self, VALUE (*func)(VALUE *args), VA
|
|
173
279
|
|
174
280
|
if(lock_holder != current_thread) {
|
175
281
|
lock = rb_ivar_get(self, rb_intern("lock"));
|
176
|
-
rb_funcall(lock, rb_intern("lock"), 0
|
282
|
+
rb_funcall(lock, rb_intern("lock"), 0);
|
177
283
|
rb_ivar_set(self, rb_intern("lock_holder"), current_thread);
|
178
284
|
|
179
285
|
/* We've acquired the lock, so ensure we unlock it */
|
@@ -192,7 +298,7 @@ static VALUE NIO_Selector_unlock(VALUE self)
|
|
192
298
|
rb_ivar_set(self, rb_intern("lock_holder"), Qnil);
|
193
299
|
|
194
300
|
lock = rb_ivar_get(self, rb_intern("lock"));
|
195
|
-
rb_funcall(lock, rb_intern("unlock"), 0
|
301
|
+
rb_funcall(lock, rb_intern("unlock"), 0);
|
196
302
|
|
197
303
|
return Qnil;
|
198
304
|
}
|
@@ -307,82 +413,61 @@ static VALUE NIO_Selector_select_synchronized(VALUE *args)
|
|
307
413
|
}
|
308
414
|
|
309
415
|
ready = NIO_Selector_run(selector, args[1]);
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
ready_array = selector->ready_array;
|
416
|
+
|
417
|
+
/* Timeout */
|
418
|
+
if(ready < 0) {
|
419
|
+
if(!rb_block_given_p()) {
|
315
420
|
selector->ready_array = Qnil;
|
316
|
-
return ready_array;
|
317
421
|
}
|
422
|
+
|
423
|
+
return Qnil;
|
424
|
+
}
|
425
|
+
|
426
|
+
if(rb_block_given_p()) {
|
427
|
+
return INT2NUM(ready);
|
318
428
|
} else {
|
429
|
+
ready_array = selector->ready_array;
|
319
430
|
selector->ready_array = Qnil;
|
320
|
-
return
|
431
|
+
return ready_array;
|
321
432
|
}
|
322
433
|
}
|
323
434
|
|
324
435
|
static int NIO_Selector_run(struct NIO_Selector *selector, VALUE timeout)
|
325
436
|
{
|
437
|
+
int ev_run_flags = EVRUN_ONCE;
|
326
438
|
int result;
|
439
|
+
double timeout_val;
|
440
|
+
|
327
441
|
selector->selecting = 1;
|
442
|
+
selector->wakeup_fired = 0;
|
328
443
|
|
329
|
-
|
330
|
-
|
331
|
-
if(timeout != Qnil) {
|
332
|
-
/* It seems libev is not a fan of timers being zero, so fudge a little */
|
333
|
-
selector->timer.repeat = NUM2DBL(timeout) + 0.0001;
|
334
|
-
ev_timer_again(selector->ev_loop, &selector->timer);
|
335
|
-
} else {
|
444
|
+
if(timeout == Qnil) {
|
445
|
+
/* Don't fire a wakeup timeout if we weren't passed one */
|
336
446
|
ev_timer_stop(selector->ev_loop, &selector->timer);
|
337
|
-
}
|
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
|
-
|
343
|
-
#if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
|
344
|
-
/* libev is patched to release the GIL when it makes its system call */
|
345
|
-
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
447
|
} else {
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
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;
|
448
|
+
timeout_val = NUM2DBL(timeout);
|
449
|
+
if(timeout_val == 0) {
|
450
|
+
/* If we've been given an explicit timeout of 0, perform a non-blocking
|
451
|
+
select operation */
|
452
|
+
ev_run_flags = EVRUN_NOWAIT;
|
453
|
+
} else {
|
454
|
+
selector->timer.repeat = timeout_val;
|
455
|
+
ev_timer_again(selector->ev_loop, &selector->timer);
|
376
456
|
}
|
377
|
-
|
378
|
-
ev_timer_stop(selector->ev_loop, &selector->timer);
|
379
457
|
}
|
380
|
-
|
458
|
+
|
459
|
+
/* libev is patched to release the GIL when it makes its system call */
|
460
|
+
ev_run(selector->ev_loop, ev_run_flags);
|
381
461
|
|
382
462
|
result = selector->ready_count;
|
383
463
|
selector->selecting = selector->ready_count = 0;
|
384
464
|
|
385
|
-
|
465
|
+
if(result > 0 || selector->wakeup_fired) {
|
466
|
+
selector->wakeup_fired = 0;
|
467
|
+
return result;
|
468
|
+
} else {
|
469
|
+
return -1;
|
470
|
+
}
|
386
471
|
}
|
387
472
|
|
388
473
|
/* Wake the selector up from another thread */
|
@@ -395,7 +480,9 @@ static VALUE NIO_Selector_wakeup(VALUE self)
|
|
395
480
|
rb_raise(rb_eIOError, "selector is closed");
|
396
481
|
}
|
397
482
|
|
483
|
+
selector->wakeup_fired = 1;
|
398
484
|
write(selector->wakeup_writer, "\0", 1);
|
485
|
+
|
399
486
|
return Qnil;
|
400
487
|
}
|
401
488
|
|
@@ -445,8 +532,6 @@ static VALUE NIO_Selector_is_empty(VALUE self)
|
|
445
532
|
/* Called whenever a timeout fires on the event loop */
|
446
533
|
static void NIO_Selector_timeout_callback(struct ev_loop *ev_loop, struct ev_timer *timer, int revents)
|
447
534
|
{
|
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
535
|
}
|
451
536
|
|
452
537
|
/* Called whenever a wakeup request is sent to a selector */
|
@@ -467,6 +552,8 @@ void NIO_Selector_monitor_callback(struct ev_loop *ev_loop, struct ev_io *io, in
|
|
467
552
|
struct NIO_Selector *selector = monitor_data->selector;
|
468
553
|
VALUE monitor = monitor_data->self;
|
469
554
|
|
555
|
+
assert(monitor_data->interests != 0);
|
556
|
+
|
470
557
|
assert(selector != 0);
|
471
558
|
selector->ready_count++;
|
472
559
|
monitor_data->revents = revents;
|