nio4r 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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:
|