nio4r 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.md +10 -0
- data/Gemfile +1 -1
- data/README.md +75 -27
- data/examples/echo_server.rb +38 -0
- data/ext/nio4r/monitor.c +55 -7
- data/ext/nio4r/nio4r.h +1 -0
- data/ext/nio4r/selector.c +80 -12
- data/lib/nio/jruby/monitor.rb +17 -1
- data/lib/nio/jruby/selector.rb +36 -11
- data/lib/nio/monitor.rb +12 -1
- data/lib/nio/selector.rb +26 -5
- data/lib/nio/version.rb +1 -1
- data/nio4r.gemspec +2 -2
- data/spec/nio/monitor_spec.rb +20 -10
- data/spec/nio/selector_spec.rb +74 -2
- metadata +12 -9
data/CHANGES.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
0.2.0
|
2
|
+
-----
|
3
|
+
* NIO::Monitor#readiness API to query readiness, along with #readable? and
|
4
|
+
#writable? helper methods
|
5
|
+
* NIO::Selector#select_each API which avoids memory allocations if possible
|
6
|
+
* Bugfixes for the JRuby implementation
|
7
|
+
|
8
|
+
0.1.0
|
9
|
+
-----
|
10
|
+
* Initial release. Merry Christmas!
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -32,56 +32,104 @@ Platform notes:
|
|
32
32
|
|
33
33
|
* MRI/YARV and Rubinius implement nio4j with a C extension based on libev,
|
34
34
|
which provides a high performance binding to native IO APIs
|
35
|
-
* JRuby uses a special backend based on
|
36
|
-
even when monitoring large numbers of IO objects
|
35
|
+
* JRuby uses a special backend based on the high performance Java NIO subsystem
|
37
36
|
* A pure Ruby implementation is also provided for Ruby implementations which
|
38
37
|
don't implement the MRI C extension API
|
39
38
|
|
40
39
|
Usage
|
41
40
|
-----
|
42
41
|
|
42
|
+
### Selectors
|
43
|
+
|
43
44
|
The NIO::Selector class is the main API provided by nio4r. Use it where you
|
44
45
|
might otherwise use Kernel.select, but want to monitor the same set of IO
|
45
46
|
objects across multiple select calls rather than having to reregister them
|
46
47
|
every single time:
|
47
48
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
IO
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
49
|
+
```ruby
|
50
|
+
require 'nio'
|
51
|
+
|
52
|
+
selector = NIO::Selector.new
|
53
|
+
```
|
54
|
+
|
55
|
+
To monitor IO objects, attach them to the selector with the NIO::Selector#register
|
56
|
+
method, monitoring them for read readiness with the :r parameter, write
|
57
|
+
readiness with the :w parameter, or both with :rw.
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
>> reader, writer = IO.pipe
|
61
|
+
=> [#<IO:0xf30>, #<IO:0xf34>]
|
62
|
+
>> monitor = selector.register(reader, :r)
|
63
|
+
=> #<NIO::Monitor:0xfbc>
|
64
|
+
```
|
65
|
+
|
66
|
+
After registering an IO object with the selector, you'll get a NIO::Monitor
|
67
|
+
object which you can use for managing how a particular IO object is being
|
68
|
+
monitored. Monitors will store an arbitrary value of your choice, which
|
69
|
+
provides an easy way to implement callbacks:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
>> monitor = selector.register(reader, :r)
|
73
|
+
=> #<NIO::Monitor:0xfbc>
|
74
|
+
>> monitor.value = proc { puts "Got some data: #{monitor.io.read_nonblock(4096)}" }
|
75
|
+
=> #<Proc:0x1000@(irb):4>
|
76
|
+
```
|
70
77
|
|
71
78
|
The main method of importance is NIO::Selector#select, which monitors all
|
72
79
|
registered IO objects and returns an array of monitors that are ready.
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
>> writer << "Hi there!"
|
83
|
+
=> #<IO:0x103c>
|
84
|
+
>> ready = selector.select
|
85
|
+
=> [#<NIO::Monitor:0xfbc>]
|
86
|
+
>> ready.each { |m| m.value.call }
|
87
|
+
Got some data: Hi there!
|
88
|
+
=> [#<NIO::Monitor:0xfbc>]
|
89
|
+
```
|
90
|
+
|
73
91
|
By default, NIO::Selector#select will block indefinitely until one of the IO
|
74
92
|
objects being monitored becomes ready. However, you can also pass a timeout to
|
75
93
|
wait in seconds to NIO::Selector#select just like you can with Kernel.select:
|
76
94
|
|
77
|
-
|
95
|
+
```ruby
|
96
|
+
ready = selector.select(15) # Wait 15 seconds
|
97
|
+
```
|
78
98
|
|
79
99
|
If a timeout occurs, ready will be nil.
|
80
100
|
|
101
|
+
You can avoid allocating an array each time you call NIO::Selector#select by
|
102
|
+
using NIO::Selector#select_each instead. This method successively yields ready
|
103
|
+
NIO::Monitor objects:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
>> selector.select_each { |m| m.value.call }
|
107
|
+
Got some data: Hi there!
|
108
|
+
=> 1
|
109
|
+
```
|
110
|
+
|
81
111
|
When you're done monitoring a particular IO object, just deregister it from
|
82
112
|
the selector:
|
83
113
|
|
84
|
-
|
114
|
+
```ruby
|
115
|
+
selector.deregister(reader)
|
116
|
+
```
|
117
|
+
|
118
|
+
### Monitors
|
119
|
+
|
120
|
+
Monitors provide methods which let you introspect on why a particular IO
|
121
|
+
object was selected. These methods are not thread safe unless you are holding
|
122
|
+
the selector lock (i.e. if you're in a #select_each callback). Only use them
|
123
|
+
if you aren't concerned with thread safety, or you're within a #select_each
|
124
|
+
callback:
|
125
|
+
|
126
|
+
- ***#interests***: what this monitor is interested in (:r, :w, or :rw)
|
127
|
+
- ***#readiness***: what the monitored IO object is ready for according to the last select operation
|
128
|
+
- ***#readable?***: was the IO readable last time it was selected?
|
129
|
+
- ***#writable?***: was the IO writable last time it was selected?
|
130
|
+
|
131
|
+
Monitors also support a ***#value*** and ***#value=*** method for storing a
|
132
|
+
handle to an arbitrary object of your choice (e.g. a proc)
|
85
133
|
|
86
134
|
Concurrency
|
87
135
|
-----------
|
@@ -108,7 +156,7 @@ to maintain a large codebase. As of the time of writing, the current
|
|
108
156
|
implementation is a little over 100 lines of code for both the pure Ruby and
|
109
157
|
JRuby backends. The native extension uses approximately 500 lines of C code.
|
110
158
|
|
111
|
-
nio4r is also not a replacement for
|
159
|
+
nio4r is also not a replacement for Kinder Gentler IO (KGIO), a set of
|
112
160
|
advanced Ruby IO APIs. At some point in the future nio4r might provide a
|
113
161
|
cross-platform implementation that uses KGIO on CRubies, and Java NIO on JRuby,
|
114
162
|
however this is not the case today.
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.push File.expand_path('../../lib', __FILE__)
|
4
|
+
require 'nio'
|
5
|
+
require 'socket'
|
6
|
+
|
7
|
+
class EchoServer
|
8
|
+
def initialize(host, port)
|
9
|
+
@selector = NIO::Selector.new
|
10
|
+
|
11
|
+
puts "Listening on #{host}:#{port}"
|
12
|
+
@server = TCPServer.new(host, port)
|
13
|
+
|
14
|
+
monitor = @selector.register(@server, :r)
|
15
|
+
monitor.value = proc { accept }
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
while true
|
20
|
+
@selector.select_each { |monitor| monitor.value.call(monitor) }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def accept
|
25
|
+
socket = @server.accept
|
26
|
+
monitor = @selector.register(socket, :r)
|
27
|
+
monitor.value = proc { read(socket) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def read(socket)
|
31
|
+
data = socket.read_nonblock(4096)
|
32
|
+
socket.write_nonblock(data)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
if $0 == __FILE__
|
37
|
+
EchoServer.new("localhost", 1234).run
|
38
|
+
end
|
data/ext/nio4r/monitor.c
CHANGED
@@ -20,8 +20,11 @@ static VALUE NIO_Monitor_close(VALUE self);
|
|
20
20
|
static VALUE NIO_Monitor_is_closed(VALUE self);
|
21
21
|
static VALUE NIO_Monitor_io(VALUE self);
|
22
22
|
static VALUE NIO_Monitor_interests(VALUE self);
|
23
|
+
static VALUE NIO_Monitor_is_readable(VALUE self);
|
24
|
+
static VALUE NIO_Monitor_is_writable(VALUE self);
|
23
25
|
static VALUE NIO_Monitor_value(VALUE self);
|
24
26
|
static VALUE NIO_Monitor_set_value(VALUE self, VALUE obj);
|
27
|
+
static VALUE NIO_Monitor_readiness(VALUE self);
|
25
28
|
|
26
29
|
/* Internal functions */
|
27
30
|
static void NIO_Monitor_callback(struct ev_loop *ev_loop, struct ev_io *io, int revents);
|
@@ -46,6 +49,10 @@ void Init_NIO_Monitor()
|
|
46
49
|
rb_define_method(cNIO_Monitor, "interests", NIO_Monitor_interests, 0);
|
47
50
|
rb_define_method(cNIO_Monitor, "value", NIO_Monitor_value, 0);
|
48
51
|
rb_define_method(cNIO_Monitor, "value=", NIO_Monitor_set_value, 1);
|
52
|
+
rb_define_method(cNIO_Monitor, "readiness", NIO_Monitor_readiness, 0);
|
53
|
+
rb_define_method(cNIO_Monitor, "readable?", NIO_Monitor_is_readable, 0);
|
54
|
+
rb_define_method(cNIO_Monitor, "writable?", NIO_Monitor_is_writable, 0);
|
55
|
+
rb_define_method(cNIO_Monitor, "writeable?", NIO_Monitor_is_writable, 0);
|
49
56
|
}
|
50
57
|
|
51
58
|
static VALUE NIO_Monitor_allocate(VALUE klass)
|
@@ -68,7 +75,6 @@ static VALUE NIO_Monitor_initialize(VALUE self, VALUE selector_obj, VALUE io, VA
|
|
68
75
|
{
|
69
76
|
struct NIO_Monitor *monitor;
|
70
77
|
struct NIO_Selector *selector;
|
71
|
-
int events;
|
72
78
|
ID interests_id;
|
73
79
|
|
74
80
|
#if HAVE_RB_IO_T
|
@@ -79,21 +85,21 @@ static VALUE NIO_Monitor_initialize(VALUE self, VALUE selector_obj, VALUE io, VA
|
|
79
85
|
|
80
86
|
interests_id = SYM2ID(interests);
|
81
87
|
|
88
|
+
Data_Get_Struct(self, struct NIO_Monitor, monitor);
|
89
|
+
|
82
90
|
if(interests_id == rb_intern("r")) {
|
83
|
-
|
91
|
+
monitor->interests = EV_READ;
|
84
92
|
} else if(interests_id == rb_intern("w")) {
|
85
|
-
|
93
|
+
monitor->interests = EV_WRITE;
|
86
94
|
} else if(interests_id == rb_intern("rw")) {
|
87
|
-
|
95
|
+
monitor->interests = EV_READ | EV_WRITE;
|
88
96
|
} else {
|
89
97
|
rb_raise(rb_eArgError, "invalid event type %s (must be :r, :w, or :rw)",
|
90
98
|
RSTRING_PTR(rb_funcall(interests, rb_intern("inspect"), 0, 0)));
|
91
99
|
}
|
92
100
|
|
93
|
-
Data_Get_Struct(self, struct NIO_Monitor, monitor);
|
94
|
-
|
95
101
|
GetOpenFile(rb_convert_type(io, T_FILE, "IO", "to_io"), fptr);
|
96
|
-
ev_io_init(&monitor->ev_io, NIO_Monitor_callback, FPTR_TO_FD(fptr),
|
102
|
+
ev_io_init(&monitor->ev_io, NIO_Monitor_callback, FPTR_TO_FD(fptr), monitor->interests);
|
97
103
|
|
98
104
|
rb_ivar_set(self, rb_intern("selector"), selector_obj);
|
99
105
|
rb_ivar_set(self, rb_intern("io"), io);
|
@@ -154,11 +160,53 @@ static VALUE NIO_Monitor_set_value(VALUE self, VALUE obj)
|
|
154
160
|
return rb_ivar_set(self, rb_intern("value"), obj);
|
155
161
|
}
|
156
162
|
|
163
|
+
static VALUE NIO_Monitor_readiness(VALUE self)
|
164
|
+
{
|
165
|
+
struct NIO_Monitor *monitor;
|
166
|
+
Data_Get_Struct(self, struct NIO_Monitor, monitor);
|
167
|
+
|
168
|
+
if((monitor->revents & (EV_READ | EV_WRITE)) == (EV_READ | EV_WRITE)) {
|
169
|
+
return ID2SYM(rb_intern("rw"));
|
170
|
+
} else if(monitor->revents & EV_READ) {
|
171
|
+
return ID2SYM(rb_intern("r"));
|
172
|
+
} else if(monitor->revents & EV_WRITE) {
|
173
|
+
return ID2SYM(rb_intern("w"));
|
174
|
+
} else {
|
175
|
+
return Qnil;
|
176
|
+
}
|
177
|
+
}
|
178
|
+
|
179
|
+
static VALUE NIO_Monitor_is_readable(VALUE self)
|
180
|
+
{
|
181
|
+
struct NIO_Monitor *monitor;
|
182
|
+
Data_Get_Struct(self, struct NIO_Monitor, monitor);
|
183
|
+
|
184
|
+
if(monitor->revents & EV_READ) {
|
185
|
+
return Qtrue;
|
186
|
+
} else {
|
187
|
+
return Qfalse;
|
188
|
+
}
|
189
|
+
}
|
190
|
+
|
191
|
+
static VALUE NIO_Monitor_is_writable(VALUE self)
|
192
|
+
{
|
193
|
+
struct NIO_Monitor *monitor;
|
194
|
+
Data_Get_Struct(self, struct NIO_Monitor, monitor);
|
195
|
+
|
196
|
+
if(monitor->revents & EV_WRITE) {
|
197
|
+
return Qtrue;
|
198
|
+
} else {
|
199
|
+
return Qfalse;
|
200
|
+
}
|
201
|
+
}
|
202
|
+
|
157
203
|
/* libev callback fired whenever this monitor gets events */
|
158
204
|
static void NIO_Monitor_callback(struct ev_loop *ev_loop, struct ev_io *io, int revents)
|
159
205
|
{
|
160
206
|
struct NIO_Monitor *monitor = (struct NIO_Monitor *)io->data;
|
161
207
|
|
162
208
|
assert(monitor->selector != 0);
|
209
|
+
monitor->revents = revents;
|
210
|
+
|
163
211
|
NIO_Selector_handle_event(monitor->selector, monitor->self, revents);
|
164
212
|
}
|
data/ext/nio4r/nio4r.h
CHANGED
data/ext/nio4r/selector.c
CHANGED
@@ -23,6 +23,7 @@ static VALUE NIO_Selector_register(VALUE self, VALUE selectable, VALUE interest)
|
|
23
23
|
static VALUE NIO_Selector_deregister(VALUE self, VALUE io);
|
24
24
|
static VALUE NIO_Selector_is_registered(VALUE self, VALUE io);
|
25
25
|
static VALUE NIO_Selector_select(int argc, VALUE *argv, VALUE self);
|
26
|
+
static VALUE NIO_Selector_select_each(int argc, VALUE *argv, VALUE self);
|
26
27
|
static VALUE NIO_Selector_wakeup(VALUE self);
|
27
28
|
static VALUE NIO_Selector_close(VALUE self);
|
28
29
|
static VALUE NIO_Selector_closed(VALUE self);
|
@@ -33,6 +34,8 @@ static VALUE NIO_Selector_unlock(VALUE lock);
|
|
33
34
|
static VALUE NIO_Selector_register_synchronized(VALUE *args);
|
34
35
|
static VALUE NIO_Selector_deregister_synchronized(VALUE *args);
|
35
36
|
static VALUE NIO_Selector_select_synchronized(VALUE *args);
|
37
|
+
static VALUE NIO_Selector_select_each_synchronized(VALUE *args);
|
38
|
+
static int NIO_Selector_fill_ready_buffer(VALUE *args);
|
36
39
|
static VALUE NIO_Selector_run_evloop(void *ptr);
|
37
40
|
static void NIO_Selector_timeout_callback(struct ev_loop *ev_loop, struct ev_timer *timer, int revents);
|
38
41
|
static void NIO_Selector_wakeup_callback(struct ev_loop *ev_loop, struct ev_async *async, int revents);
|
@@ -57,6 +60,7 @@ void Init_NIO_Selector()
|
|
57
60
|
rb_define_method(cNIO_Selector, "deregister", NIO_Selector_deregister, 1);
|
58
61
|
rb_define_method(cNIO_Selector, "registered?", NIO_Selector_is_registered, 1);
|
59
62
|
rb_define_method(cNIO_Selector, "select", NIO_Selector_select, -1);
|
63
|
+
rb_define_method(cNIO_Selector, "select_each", NIO_Selector_select_each, -1);
|
60
64
|
rb_define_method(cNIO_Selector, "wakeup", NIO_Selector_wakeup, 0);
|
61
65
|
rb_define_method(cNIO_Selector, "close", NIO_Selector_close, 0);
|
62
66
|
rb_define_method(cNIO_Selector, "closed?", NIO_Selector_closed, 0);
|
@@ -126,19 +130,35 @@ static VALUE NIO_Selector_initialize(VALUE self)
|
|
126
130
|
return Qnil;
|
127
131
|
}
|
128
132
|
|
129
|
-
/* Synchronize
|
133
|
+
/* Synchronize around a reentrant selector lock */
|
130
134
|
static VALUE NIO_Selector_synchronize(VALUE self, VALUE (*func)(VALUE *args), VALUE *args)
|
131
135
|
{
|
132
|
-
VALUE lock;
|
136
|
+
VALUE current_thread, lock_holder, lock;
|
133
137
|
|
134
|
-
|
135
|
-
|
136
|
-
|
138
|
+
current_thread = rb_thread_current();
|
139
|
+
lock_holder = rb_ivar_get(self, rb_intern("lock_holder"));
|
140
|
+
|
141
|
+
if(lock_holder != rb_thread_current()) {
|
142
|
+
lock = rb_ivar_get(self, rb_intern("lock"));
|
143
|
+
rb_funcall(lock, rb_intern("lock"), 0, 0);
|
144
|
+
rb_ivar_set(self, rb_intern("lock_holder"), rb_thread_current());
|
145
|
+
|
146
|
+
/* We've acquired the lock, so ensure we unlock it */
|
147
|
+
return rb_ensure(func, (VALUE)args, NIO_Selector_unlock, self);
|
148
|
+
} else {
|
149
|
+
/* We already hold the selector lock, so no need to unlock it */
|
150
|
+
func(args);
|
151
|
+
}
|
137
152
|
}
|
138
153
|
|
139
154
|
/* Unlock the selector mutex */
|
140
|
-
static VALUE NIO_Selector_unlock(VALUE
|
155
|
+
static VALUE NIO_Selector_unlock(VALUE self)
|
141
156
|
{
|
157
|
+
VALUE lock;
|
158
|
+
|
159
|
+
rb_ivar_set(self, rb_intern("lock_holder"), Qnil);
|
160
|
+
|
161
|
+
lock = rb_ivar_get(self, rb_intern("lock"));
|
142
162
|
rb_funcall(lock, rb_intern("unlock"), 0, 0);
|
143
163
|
}
|
144
164
|
|
@@ -225,11 +245,63 @@ static VALUE NIO_Selector_select(int argc, VALUE *argv, VALUE self)
|
|
225
245
|
return NIO_Selector_synchronize(self, NIO_Selector_select_synchronized, args);
|
226
246
|
}
|
227
247
|
|
248
|
+
/* Select from all registered IO objects */
|
249
|
+
static VALUE NIO_Selector_select_each(int argc, VALUE *argv, VALUE self)
|
250
|
+
{
|
251
|
+
VALUE timeout, array;
|
252
|
+
VALUE args[2];
|
253
|
+
|
254
|
+
if(!rb_block_given_p()) {
|
255
|
+
rb_raise(rb_eArgError, "no block given");
|
256
|
+
}
|
257
|
+
|
258
|
+
rb_scan_args(argc, argv, "01", &timeout);
|
259
|
+
|
260
|
+
args[0] = self;
|
261
|
+
args[1] = timeout;
|
262
|
+
|
263
|
+
return NIO_Selector_synchronize(self, NIO_Selector_select_each_synchronized, args);
|
264
|
+
}
|
265
|
+
|
228
266
|
/* Internal implementation of select with the selector lock held */
|
229
267
|
static VALUE NIO_Selector_select_synchronized(VALUE *args)
|
230
268
|
{
|
231
|
-
VALUE self, timeout, result;
|
232
269
|
struct NIO_Selector *selector;
|
270
|
+
int ready = NIO_Selector_fill_ready_buffer(args);
|
271
|
+
|
272
|
+
Data_Get_Struct(args[0], struct NIO_Selector, selector);
|
273
|
+
|
274
|
+
if(ready > 0) {
|
275
|
+
return rb_ary_new4(ready, selector->ready_buffer);
|
276
|
+
} else {
|
277
|
+
return Qnil;
|
278
|
+
}
|
279
|
+
}
|
280
|
+
|
281
|
+
/* Internal implementation of select with the selector lock held */
|
282
|
+
static VALUE NIO_Selector_select_each_synchronized(VALUE *args)
|
283
|
+
{
|
284
|
+
struct NIO_Selector *selector;
|
285
|
+
int i, ready = NIO_Selector_fill_ready_buffer(args);
|
286
|
+
|
287
|
+
Data_Get_Struct(args[0], struct NIO_Selector, selector);
|
288
|
+
|
289
|
+
if(ready > 0) {
|
290
|
+
for(i = 0; i < ready; i++) {
|
291
|
+
rb_yield(selector->ready_buffer[i]);
|
292
|
+
}
|
293
|
+
|
294
|
+
return INT2NUM(ready);
|
295
|
+
} else {
|
296
|
+
return Qnil;
|
297
|
+
}
|
298
|
+
}
|
299
|
+
|
300
|
+
static int NIO_Selector_fill_ready_buffer(VALUE *args)
|
301
|
+
{
|
302
|
+
VALUE self, timeout;
|
303
|
+
struct NIO_Selector *selector;
|
304
|
+
int result;
|
233
305
|
|
234
306
|
self = args[0];
|
235
307
|
timeout = args[1];
|
@@ -289,11 +361,7 @@ static VALUE NIO_Selector_select_synchronized(VALUE *args)
|
|
289
361
|
}
|
290
362
|
#endif /* defined(HAVE_RB_THREAD_BLOCKING_REGION) */
|
291
363
|
|
292
|
-
|
293
|
-
return Qnil;
|
294
|
-
}
|
295
|
-
|
296
|
-
result = rb_ary_new4(selector->ready_count, selector->ready_buffer);
|
364
|
+
result = selector->ready_count;
|
297
365
|
selector->selecting = selector->ready_count = 0;
|
298
366
|
|
299
367
|
return result;
|
data/lib/nio/jruby/monitor.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module NIO
|
2
2
|
# Monitors watch Channels for specific events
|
3
3
|
class Monitor
|
4
|
-
attr_accessor :value
|
4
|
+
attr_accessor :value, :io
|
5
5
|
|
6
6
|
# :nodoc
|
7
7
|
def initialize(io, selection_key)
|
@@ -15,6 +15,22 @@ module NIO
|
|
15
15
|
Selector.iops2sym @key.interestOps
|
16
16
|
end
|
17
17
|
|
18
|
+
# What is the IO object ready for?
|
19
|
+
def readiness
|
20
|
+
Selector.iops2sym @key.readyOps
|
21
|
+
end
|
22
|
+
|
23
|
+
# Is the IO object readable?
|
24
|
+
def readable?
|
25
|
+
readiness == :r || readiness == :rw
|
26
|
+
end
|
27
|
+
|
28
|
+
# Is the IO object writable?
|
29
|
+
def writable?
|
30
|
+
readiness == :w || readiness == :rw
|
31
|
+
end
|
32
|
+
alias_method :writeable?, :writable?
|
33
|
+
|
18
34
|
# Is this monitor closed?
|
19
35
|
def closed?; @closed; end
|
20
36
|
|
data/lib/nio/jruby/selector.rb
CHANGED
@@ -5,14 +5,22 @@ module NIO
|
|
5
5
|
java_import "java.nio.channels.SelectionKey"
|
6
6
|
|
7
7
|
# Convert nio4r interest symbols to Java NIO interest ops
|
8
|
-
def self.sym2iops(interest)
|
8
|
+
def self.sym2iops(interest, channel)
|
9
9
|
case interest
|
10
10
|
when :r
|
11
|
-
|
11
|
+
if channel.validOps & SelectionKey::OP_ACCEPT != 0
|
12
|
+
SelectionKey::OP_ACCEPT
|
13
|
+
else
|
14
|
+
SelectionKey::OP_READ
|
15
|
+
end
|
12
16
|
when :w
|
13
|
-
|
17
|
+
if channel.respond_to? :connected? and not channel.connected?
|
18
|
+
SelectionKey::OP_CONNECT
|
19
|
+
else
|
20
|
+
SelectionKey::OP_WRITE
|
21
|
+
end
|
14
22
|
when :rw
|
15
|
-
|
23
|
+
super(:r, channel) | super(:w, channel)
|
16
24
|
else raise ArgumentError, "invalid interest type: #{interest}"
|
17
25
|
end
|
18
26
|
end
|
@@ -20,12 +28,13 @@ module NIO
|
|
20
28
|
# Convert Java NIO interest ops to the corresponding Ruby symbols
|
21
29
|
def self.iops2sym(interest_ops)
|
22
30
|
case interest_ops
|
23
|
-
when SelectionKey::OP_READ
|
31
|
+
when SelectionKey::OP_READ, SelectionKey::OP_ACCEPT
|
24
32
|
:r
|
25
|
-
when SelectionKey::OP_WRITE
|
33
|
+
when SelectionKey::OP_WRITE, SelectionKey::OP_CONNECT
|
26
34
|
:w
|
27
35
|
when SelectionKey::OP_READ | SelectionKey::OP_WRITE
|
28
36
|
:rw
|
37
|
+
when 0 then nil
|
29
38
|
else raise ArgumentError, "unknown interest op combination: 0x#{interest_ops.to_s(16)}"
|
30
39
|
end
|
31
40
|
end
|
@@ -44,15 +53,14 @@ module NIO
|
|
44
53
|
def register(io, interest)
|
45
54
|
java_channel = io.to_channel
|
46
55
|
java_channel.configureBlocking(false)
|
47
|
-
|
48
|
-
interest_ops = self.class.sym2iops(interest)
|
56
|
+
interest_ops = self.class.sym2iops(interest, java_channel)
|
49
57
|
|
50
58
|
begin
|
51
59
|
selector_key = java_channel.register @java_selector, interest_ops
|
52
60
|
rescue NativeException => ex
|
53
61
|
case ex.cause
|
54
62
|
when java.lang.IllegalArgumentException
|
55
|
-
raise ArgumentError, "invalid interest type for #{
|
63
|
+
raise ArgumentError, "invalid interest type for #{java_channel}: #{interest}"
|
56
64
|
else raise
|
57
65
|
end
|
58
66
|
end
|
@@ -79,15 +87,32 @@ module NIO
|
|
79
87
|
|
80
88
|
# Select which monitors are ready
|
81
89
|
def select(timeout = nil)
|
90
|
+
selected = []
|
91
|
+
ready = select_each(timeout) { |monitor| selected << monitor }
|
92
|
+
return unless ready
|
93
|
+
|
94
|
+
selected
|
95
|
+
end
|
96
|
+
|
97
|
+
# Iterate across all selectable monitors
|
98
|
+
def select_each(timeout = nil)
|
82
99
|
@select_lock.synchronize do
|
83
|
-
if timeout
|
100
|
+
if timeout == 0
|
101
|
+
# The Java NIO API thinks zero means you want to BLOCK FOREVER o_O
|
102
|
+
# How about we don't block at all instead?
|
103
|
+
ready = @java_selector.selectNow
|
104
|
+
elsif timeout
|
84
105
|
ready = @java_selector.select(timeout * 1000)
|
85
106
|
else
|
86
107
|
ready = @java_selector.select
|
87
108
|
end
|
88
109
|
|
89
110
|
return unless ready > 0 # timeout or wakeup
|
90
|
-
|
111
|
+
|
112
|
+
@java_selector.selectedKeys.each { |key| yield key.attachment }
|
113
|
+
@java_selector.selectedKeys.clear
|
114
|
+
|
115
|
+
ready
|
91
116
|
end
|
92
117
|
end
|
93
118
|
|
data/lib/nio/monitor.rb
CHANGED
@@ -2,7 +2,7 @@ module NIO
|
|
2
2
|
# Monitors watch IO objects for specific events
|
3
3
|
class Monitor
|
4
4
|
attr_reader :io, :interests
|
5
|
-
attr_accessor :value
|
5
|
+
attr_accessor :value, :readiness
|
6
6
|
|
7
7
|
# :nodoc
|
8
8
|
def initialize(io, interests)
|
@@ -10,6 +10,17 @@ module NIO
|
|
10
10
|
@closed = false
|
11
11
|
end
|
12
12
|
|
13
|
+
# Is the IO object readable?
|
14
|
+
def readable?
|
15
|
+
readiness == :r || readiness == :rw
|
16
|
+
end
|
17
|
+
|
18
|
+
# Is the IO object writable?
|
19
|
+
def writable?
|
20
|
+
readiness == :w || readiness == :rw
|
21
|
+
end
|
22
|
+
alias_method :writeable?, :writable?
|
23
|
+
|
13
24
|
# Is this monitor closed?
|
14
25
|
def closed?; @closed; end
|
15
26
|
|
data/lib/nio/selector.rb
CHANGED
@@ -54,10 +54,8 @@ module NIO
|
|
54
54
|
ready_readers, ready_writers = Kernel.select readers, writers, [], timeout
|
55
55
|
return unless ready_readers # timeout or wakeup
|
56
56
|
|
57
|
-
results =
|
58
|
-
|
59
|
-
|
60
|
-
results.map! do |io|
|
57
|
+
results = []
|
58
|
+
ready_readers.each do |io|
|
61
59
|
if io == @wakeup
|
62
60
|
# Clear all wakeup signals we've received by reading them
|
63
61
|
# Wakeups should have level triggered behavior
|
@@ -71,12 +69,35 @@ module NIO
|
|
71
69
|
|
72
70
|
return
|
73
71
|
else
|
74
|
-
@selectables[io]
|
72
|
+
monitor = @selectables[io]
|
73
|
+
monitor.readiness = :r
|
74
|
+
results << monitor
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
ready_readwriters = ready_readers & ready_writers
|
79
|
+
ready_writers = ready_writers - ready_readwriters
|
80
|
+
|
81
|
+
[[ready_writers, :w], [ready_readwriters, :rw]].each do |ios, readiness|
|
82
|
+
ios.each do |io|
|
83
|
+
monitor = @selectables[io]
|
84
|
+
monitor.readiness = readiness
|
85
|
+
results << monitor
|
75
86
|
end
|
76
87
|
end
|
88
|
+
|
89
|
+
results
|
77
90
|
end
|
78
91
|
end
|
79
92
|
|
93
|
+
# Select for ready monitors, successively yielding each one in a block
|
94
|
+
def select_each(timeout = nil, &block)
|
95
|
+
selected = select(timeout)
|
96
|
+
return unless selected
|
97
|
+
selected.each(&block)
|
98
|
+
selected.size
|
99
|
+
end
|
100
|
+
|
80
101
|
# Wake up other threads waiting on this selector
|
81
102
|
def wakeup
|
82
103
|
# Send the selector a signal in the form of writing data to a pipe
|
data/lib/nio/version.rb
CHANGED
data/nio4r.gemspec
CHANGED
@@ -14,9 +14,9 @@ Gem::Specification.new do |gem|
|
|
14
14
|
gem.name = "nio4r"
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = NIO::VERSION
|
17
|
-
gem.extensions = ["ext/nio4r/extconf.rb"]
|
17
|
+
gem.extensions = ["ext/nio4r/extconf.rb"]
|
18
18
|
|
19
19
|
gem.add_development_dependency "rake-compiler", "~> 0.7.9"
|
20
20
|
gem.add_development_dependency "rake"
|
21
|
-
gem.add_development_dependency "rspec", "
|
21
|
+
gem.add_development_dependency "rspec", "~> 2.7.0"
|
22
22
|
end
|
data/spec/nio/monitor_spec.rb
CHANGED
@@ -1,23 +1,18 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe NIO::Monitor do
|
4
|
-
let :
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
let :readable do
|
5
|
+
reader, writer = IO.pipe
|
6
|
+
writer << "have some data"
|
7
|
+
reader
|
8
8
|
end
|
9
9
|
|
10
|
-
# let :unreadable_pipe do
|
11
|
-
# pipe, _ = IO.pipe
|
12
|
-
# pipe
|
13
|
-
# end
|
14
|
-
|
15
10
|
let :selector do
|
16
11
|
NIO::Selector.new
|
17
12
|
end
|
18
13
|
|
19
14
|
# Monitors are created by registering IO objects or channels with a selector
|
20
|
-
subject { selector.register(
|
15
|
+
subject { selector.register(readable, :r) }
|
21
16
|
|
22
17
|
it "knows its interests" do
|
23
18
|
subject.interests.should == :r
|
@@ -28,6 +23,21 @@ describe NIO::Monitor do
|
|
28
23
|
subject.value.should == 42
|
29
24
|
end
|
30
25
|
|
26
|
+
it "knows what IO objects are ready for" do
|
27
|
+
# Perhaps let bindings are just confusing me but they're not producing
|
28
|
+
# what I want. Manually doing the setup here does
|
29
|
+
# FIXME: Hey RSpec wizards! Fix this!
|
30
|
+
reader, writer = IO.pipe
|
31
|
+
writer << "loldata"
|
32
|
+
selector = NIO::Selector.new
|
33
|
+
subject = selector.register(reader, :r)
|
34
|
+
|
35
|
+
# Here's where the spec really begins
|
36
|
+
selector.select(1).should include(subject)
|
37
|
+
subject.readiness.should == :r
|
38
|
+
subject.should be_readable
|
39
|
+
end
|
40
|
+
|
31
41
|
it "closes" do
|
32
42
|
subject.should_not be_closed
|
33
43
|
subject.close
|
data/spec/nio/selector_spec.rb
CHANGED
@@ -67,6 +67,45 @@ describe NIO::Selector do
|
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
|
+
context "select_each" do
|
71
|
+
it "iterates across ready selectables" do
|
72
|
+
readable1, writer = IO.pipe
|
73
|
+
writer << "ohai"
|
74
|
+
|
75
|
+
readable2, writer = IO.pipe
|
76
|
+
writer << "ohai"
|
77
|
+
|
78
|
+
unreadable, _ = IO.pipe
|
79
|
+
|
80
|
+
monitor1 = subject.register(readable1, :r)
|
81
|
+
monitor2 = subject.register(readable2, :r)
|
82
|
+
monitor3 = subject.register(unreadable, :r)
|
83
|
+
|
84
|
+
readables = []
|
85
|
+
subject.select_each { |monitor| readables << monitor }
|
86
|
+
|
87
|
+
readables.should include(monitor1)
|
88
|
+
readables.should include(monitor2)
|
89
|
+
readables.should_not include(monitor3)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "allows new monitors to be registered in the select_each block" do
|
93
|
+
server = TCPServer.new("localhost", 10001)
|
94
|
+
|
95
|
+
monitor = subject.register(server, :r)
|
96
|
+
connector = TCPSocket.open("localhost", 10001)
|
97
|
+
|
98
|
+
block_fired = false
|
99
|
+
subject.select_each do |monitor|
|
100
|
+
block_fired = true
|
101
|
+
socket = server.accept
|
102
|
+
subject.register(socket, :r).should be_a NIO::Monitor
|
103
|
+
end
|
104
|
+
|
105
|
+
block_fired.should be_true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
70
109
|
it "closes" do
|
71
110
|
subject.close
|
72
111
|
subject.should be_closed
|
@@ -139,8 +178,12 @@ describe NIO::Selector do
|
|
139
178
|
end
|
140
179
|
|
141
180
|
let :unreadable_subject do
|
142
|
-
|
143
|
-
|
181
|
+
if defined?(JRUBY_VERSION) and ENV['TRAVIS']
|
182
|
+
pending "This is sporadically showing up readable on JRuby in CI"
|
183
|
+
else
|
184
|
+
TCPServer.new("localhost", tcp_port + 1)
|
185
|
+
TCPSocket.open("localhost", tcp_port + 1)
|
186
|
+
end
|
144
187
|
end
|
145
188
|
|
146
189
|
let :writable_subject do
|
@@ -194,4 +237,33 @@ describe NIO::Selector do
|
|
194
237
|
it_behaves_like "an NIO selectable"
|
195
238
|
end
|
196
239
|
end
|
240
|
+
|
241
|
+
context "acceptables" do
|
242
|
+
shared_context "an NIO acceptable" do
|
243
|
+
it "selects for read readiness" do
|
244
|
+
waiting_monitor = subject.register(unacceptable_subject, :r)
|
245
|
+
ready_monitor = subject.register(acceptable_subject, :r)
|
246
|
+
|
247
|
+
ready_monitors = subject.select
|
248
|
+
ready_monitors.should include ready_monitor
|
249
|
+
ready_monitors.should_not include waiting_monitor
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
context TCPServer do
|
254
|
+
let(:tcp_port) { 23456 }
|
255
|
+
|
256
|
+
let :acceptable_subject do
|
257
|
+
server = TCPServer.new("localhost", tcp_port)
|
258
|
+
TCPSocket.open("localhost", tcp_port)
|
259
|
+
server
|
260
|
+
end
|
261
|
+
|
262
|
+
let :unacceptable_subject do
|
263
|
+
TCPServer.new("localhost", tcp_port + 1)
|
264
|
+
end
|
265
|
+
|
266
|
+
it_behaves_like "an NIO acceptable"
|
267
|
+
end
|
268
|
+
end
|
197
269
|
end
|
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.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2012-01-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake-compiler
|
16
|
-
requirement: &
|
16
|
+
requirement: &70186951376840 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 0.7.9
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70186951376840
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rake
|
27
|
-
requirement: &
|
27
|
+
requirement: &70186951376120 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,18 +32,18 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70186951376120
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rspec
|
38
|
-
requirement: &
|
38
|
+
requirement: &70186951375540 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
|
-
- -
|
41
|
+
- - ~>
|
42
42
|
- !ruby/object:Gem::Version
|
43
43
|
version: 2.7.0
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70186951375540
|
47
47
|
description: New IO for Ruby
|
48
48
|
email:
|
49
49
|
- tony.arcieri@gmail.com
|
@@ -55,10 +55,12 @@ files:
|
|
55
55
|
- .gitignore
|
56
56
|
- .rspec
|
57
57
|
- .travis.yml
|
58
|
+
- CHANGES.md
|
58
59
|
- Gemfile
|
59
60
|
- LICENSE.txt
|
60
61
|
- README.md
|
61
62
|
- Rakefile
|
63
|
+
- examples/echo_server.rb
|
62
64
|
- ext/libev/Changes
|
63
65
|
- ext/libev/LICENSE
|
64
66
|
- ext/libev/README
|
@@ -120,3 +122,4 @@ test_files:
|
|
120
122
|
- spec/nio/monitor_spec.rb
|
121
123
|
- spec/nio/selector_spec.rb
|
122
124
|
- spec/spec_helper.rb
|
125
|
+
has_rdoc:
|