nio4r 1.1.0 → 1.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +61 -0
- data/.rubocop_todo.yml +35 -0
- data/.travis.yml +11 -1
- data/CHANGES.md +13 -0
- data/Gemfile +2 -2
- data/Rakefile +1 -1
- data/examples/echo_server.rb +6 -7
- data/ext/libev/Changes +16 -1
- data/ext/libev/ev.c +339 -141
- data/ext/libev/ev.h +14 -7
- data/ext/libev/ev_epoll.c +2 -2
- data/ext/libev/ev_win32.c +2 -3
- data/ext/nio4r/extconf.rb +23 -34
- data/ext/nio4r/monitor.c +38 -2
- data/ext/nio4r/org/nio4r/Nio4r.java +35 -0
- data/ext/nio4r/selector.c +11 -0
- data/lib/nio/monitor.rb +16 -4
- data/lib/nio/selector.rb +14 -15
- data/lib/nio/version.rb +1 -1
- data/lib/nio.rb +15 -13
- data/nio4r.gemspec +4 -3
- data/spec/nio/acceptables_spec.rb +3 -3
- data/spec/nio/monitor_spec.rb +52 -8
- data/spec/nio/selectables/pipe_spec.rb +5 -5
- data/spec/nio/selectables/ssl_socket_spec.rb +12 -12
- data/spec/nio/selectables/tcp_socket_spec.rb +8 -8
- data/spec/nio/selectables/udp_socket_spec.rb +6 -6
- data/spec/nio/selector_spec.rb +10 -4
- data/spec/spec_helper.rb +7 -5
- data/spec/support/selectable_examples.rb +5 -5
- data/tasks/extension.rake +7 -7
- data/tasks/rspec.rake +2 -2
- data/tasks/rubocop.rake +3 -0
- metadata +22 -6
data/ext/libev/ev.h
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* libev native API header
|
|
3
3
|
*
|
|
4
|
-
* Copyright (c) 2007,2008,2009,2010,2011,2012 Marc Alexander Lehmann <libev@schmorp.de>
|
|
4
|
+
* Copyright (c) 2007,2008,2009,2010,2011,2012,2015 Marc Alexander Lehmann <libev@schmorp.de>
|
|
5
5
|
* All rights reserved.
|
|
6
6
|
*
|
|
7
7
|
* Redistribution and use in source and binary forms, with or without modifica-
|
|
@@ -42,12 +42,16 @@
|
|
|
42
42
|
|
|
43
43
|
#ifdef __cplusplus
|
|
44
44
|
# define EV_CPP(x) x
|
|
45
|
+
# if __cplusplus >= 201103L
|
|
46
|
+
# define EV_THROW noexcept
|
|
47
|
+
# else
|
|
48
|
+
# define EV_THROW throw ()
|
|
49
|
+
# endif
|
|
45
50
|
#else
|
|
46
51
|
# define EV_CPP(x)
|
|
52
|
+
# define EV_THROW
|
|
47
53
|
#endif
|
|
48
54
|
|
|
49
|
-
#define EV_THROW EV_CPP(throw())
|
|
50
|
-
|
|
51
55
|
EV_CPP(extern "C" {)
|
|
52
56
|
|
|
53
57
|
/*****************************************************************************/
|
|
@@ -148,6 +152,8 @@ EV_CPP(extern "C" {)
|
|
|
148
152
|
|
|
149
153
|
typedef double ev_tstamp;
|
|
150
154
|
|
|
155
|
+
#include <string.h> /* for memmove */
|
|
156
|
+
|
|
151
157
|
#ifndef EV_ATOMIC_T
|
|
152
158
|
# include <signal.h>
|
|
153
159
|
# define EV_ATOMIC_T sig_atomic_t volatile
|
|
@@ -205,7 +211,7 @@ struct ev_loop;
|
|
|
205
211
|
/*****************************************************************************/
|
|
206
212
|
|
|
207
213
|
#define EV_VERSION_MAJOR 4
|
|
208
|
-
#define EV_VERSION_MINOR
|
|
214
|
+
#define EV_VERSION_MINOR 22
|
|
209
215
|
|
|
210
216
|
/* eventmask, revents, events... */
|
|
211
217
|
enum {
|
|
@@ -660,7 +666,7 @@ EV_API_DECL void ev_set_userdata (EV_P_ void *data) EV_THROW;
|
|
|
660
666
|
EV_API_DECL void *ev_userdata (EV_P) EV_THROW;
|
|
661
667
|
typedef void (*ev_loop_callback)(EV_P);
|
|
662
668
|
EV_API_DECL void ev_set_invoke_pending_cb (EV_P_ ev_loop_callback invoke_pending_cb) EV_THROW;
|
|
663
|
-
/* C++ doesn't allow the use of the ev_loop_callback typedef here, so we need to spell it out*/
|
|
669
|
+
/* C++ doesn't allow the use of the ev_loop_callback typedef here, so we need to spell it out */
|
|
664
670
|
EV_API_DECL void ev_set_loop_release_cb (EV_P_ void (*release)(EV_P) EV_THROW, void (*acquire)(EV_P) EV_THROW) EV_THROW;
|
|
665
671
|
|
|
666
672
|
EV_API_DECL unsigned int ev_pending_count (EV_P) EV_THROW; /* number of pending events, if any */
|
|
@@ -715,7 +721,8 @@ EV_API_DECL void ev_resume (EV_P) EV_THROW;
|
|
|
715
721
|
#define ev_is_pending(ev) (0 + ((ev_watcher *)(void *)(ev))->pending) /* ro, true when watcher is waiting for callback invocation */
|
|
716
722
|
#define ev_is_active(ev) (0 + ((ev_watcher *)(void *)(ev))->active) /* ro, true when the watcher has been started */
|
|
717
723
|
|
|
718
|
-
#define
|
|
724
|
+
#define ev_cb_(ev) (ev)->cb /* rw */
|
|
725
|
+
#define ev_cb(ev) (memmove (&ev_cb_ (ev), &((ev_watcher *)(ev))->cb, sizeof (ev_cb_ (ev))), (ev)->cb)
|
|
719
726
|
|
|
720
727
|
#if EV_MINPRI == EV_MAXPRI
|
|
721
728
|
# define ev_priority(ev) ((ev), EV_MINPRI)
|
|
@@ -728,7 +735,7 @@ EV_API_DECL void ev_resume (EV_P) EV_THROW;
|
|
|
728
735
|
#define ev_periodic_at(ev) (+((ev_watcher_time *)(ev))->at)
|
|
729
736
|
|
|
730
737
|
#ifndef ev_set_cb
|
|
731
|
-
# define ev_set_cb(ev,cb_)
|
|
738
|
+
# define ev_set_cb(ev,cb_) (ev_cb_ (ev) = (cb_), memmove (&((ev_watcher *)(ev))->cb, &ev_cb_ (ev), sizeof (ev_cb_ (ev))))
|
|
732
739
|
#endif
|
|
733
740
|
|
|
734
741
|
/* stopping (enabling, adding) a watcher does nothing if it is already running */
|
data/ext/libev/ev_epoll.c
CHANGED
|
@@ -179,7 +179,7 @@ epoll_poll (EV_P_ ev_tstamp timeout)
|
|
|
179
179
|
if (expect_false ((uint32_t)anfds [fd].egen != (uint32_t)(ev->data.u64 >> 32)))
|
|
180
180
|
{
|
|
181
181
|
/* recreate kernel state */
|
|
182
|
-
postfork
|
|
182
|
+
postfork |= 2;
|
|
183
183
|
continue;
|
|
184
184
|
}
|
|
185
185
|
|
|
@@ -203,7 +203,7 @@ epoll_poll (EV_P_ ev_tstamp timeout)
|
|
|
203
203
|
/* which is fortunately easy to do for us. */
|
|
204
204
|
if (epoll_ctl (backend_fd, want ? EPOLL_CTL_MOD : EPOLL_CTL_DEL, fd, ev))
|
|
205
205
|
{
|
|
206
|
-
postfork
|
|
206
|
+
postfork |= 2; /* an error occurred, recreate kernel state */
|
|
207
207
|
continue;
|
|
208
208
|
}
|
|
209
209
|
}
|
data/ext/libev/ev_win32.c
CHANGED
|
@@ -39,9 +39,6 @@
|
|
|
39
39
|
|
|
40
40
|
#ifdef _WIN32
|
|
41
41
|
|
|
42
|
-
/* timeb.h is actually xsi legacy functionality */
|
|
43
|
-
#include <sys/timeb.h>
|
|
44
|
-
|
|
45
42
|
/* note: the comment below could not be substantiated, but what would I care */
|
|
46
43
|
/* MSDN says this is required to handle SIGFPE */
|
|
47
44
|
/* my wild guess would be that using something floating-pointy is required */
|
|
@@ -91,6 +88,8 @@ ev_pipe (int filedes [2])
|
|
|
91
88
|
if (connect (sock [0], (struct sockaddr *)&addr, addr_size))
|
|
92
89
|
goto fail;
|
|
93
90
|
|
|
91
|
+
/* TODO: returns INVALID_SOCKET on winsock accept, not < 0. fix it */
|
|
92
|
+
/* when convenient, probably by just removing error checking altogether? */
|
|
94
93
|
if ((sock [1] = accept (listener, 0, 0)) < 0)
|
|
95
94
|
goto fail;
|
|
96
95
|
|
data/ext/nio4r/extconf.rb
CHANGED
|
@@ -1,52 +1,41 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "mkmf"
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
$defs << '-DHAVE_RB_THREAD_BLOCKING_REGION'
|
|
5
|
-
end
|
|
3
|
+
have_header("unistd.h")
|
|
6
4
|
|
|
7
|
-
if have_func(
|
|
8
|
-
$defs <<
|
|
5
|
+
if have_func("rb_thread_blocking_region")
|
|
6
|
+
$defs << "-DHAVE_RB_THREAD_BLOCKING_REGION"
|
|
9
7
|
end
|
|
10
8
|
|
|
11
|
-
if
|
|
12
|
-
$defs <<
|
|
9
|
+
if have_func("rb_thread_call_without_gvl")
|
|
10
|
+
$defs << "-DHAVE_RB_THEREAD_CALL_WITHOUT_GVL"
|
|
13
11
|
end
|
|
14
12
|
|
|
15
|
-
if have_header(
|
|
16
|
-
$defs << '-DEV_USE_POLL'
|
|
17
|
-
end
|
|
13
|
+
$defs << "-DEV_USE_SELECT" if have_header("sys/select.h")
|
|
18
14
|
|
|
19
|
-
if have_header(
|
|
20
|
-
$defs << '-DEV_USE_EPOLL'
|
|
21
|
-
end
|
|
15
|
+
$defs << "-DEV_USE_POLL" if have_header("poll.h")
|
|
22
16
|
|
|
23
|
-
|
|
24
|
-
$defs << '-DEV_USE_KQUEUE'
|
|
25
|
-
end
|
|
17
|
+
$defs << "-DEV_USE_EPOLL" if have_header("sys/epoll.h")
|
|
26
18
|
|
|
27
|
-
if have_header(
|
|
28
|
-
$defs <<
|
|
19
|
+
if have_header("sys/event.h") && have_header("sys/queue.h")
|
|
20
|
+
$defs << "-DEV_USE_KQUEUE"
|
|
29
21
|
end
|
|
30
22
|
|
|
31
|
-
if have_header(
|
|
32
|
-
$defs << '-DHAVE_SYS_RESOURCE_H'
|
|
33
|
-
end
|
|
23
|
+
$defs << "-DEV_USE_PORT" if have_header("port.h")
|
|
34
24
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
25
|
+
$defs << "-DHAVE_SYS_RESOURCE_H" if have_header("sys/resource.h")
|
|
26
|
+
|
|
27
|
+
$defs << "-DHAVE_RUBYSIG_H" if RUBY_VERSION.to_f < 1.9
|
|
38
28
|
|
|
39
|
-
dir_config
|
|
40
|
-
create_makefile
|
|
29
|
+
dir_config "nio4r_ext"
|
|
30
|
+
create_makefile "nio4r_ext"
|
|
41
31
|
|
|
42
|
-
# win32 needs to link in "just the right order" for some reason or
|
|
43
|
-
#
|
|
32
|
+
# win32 needs to link in "just the right order" for some reason or
|
|
33
|
+
# ioctlsocket will be mapped to an [inverted] ruby specific version.
|
|
44
34
|
if RUBY_PLATFORM =~ /mingw|win32/
|
|
45
|
-
makefile_contents = File.read
|
|
35
|
+
makefile_contents = File.read "Makefile"
|
|
46
36
|
|
|
47
|
-
|
|
48
|
-
makefile_contents.gsub! 'DLDFLAGS = ', 'DLDFLAGS = -export-all '
|
|
37
|
+
makefile_contents.gsub! "DLDFLAGS = ", "DLDFLAGS = -export-all "
|
|
49
38
|
|
|
50
|
-
makefile_contents.gsub!
|
|
51
|
-
File.open(
|
|
39
|
+
makefile_contents.gsub! "LIBS = $(LIBRUBYARG_SHARED)", "LIBS = -lws2_32 $(LIBRUBYARG_SHARED)"
|
|
40
|
+
File.open("Makefile", "w") { |f| f.write makefile_contents }
|
|
52
41
|
end
|
data/ext/nio4r/monitor.c
CHANGED
|
@@ -15,6 +15,8 @@ static void NIO_Monitor_free(struct NIO_Monitor *monitor);
|
|
|
15
15
|
|
|
16
16
|
/* Methods */
|
|
17
17
|
static VALUE NIO_Monitor_initialize(VALUE self, VALUE selector, VALUE io, VALUE interests);
|
|
18
|
+
static VALUE NIO_Monitor_set_interests(VALUE self, VALUE interests);
|
|
19
|
+
|
|
18
20
|
static VALUE NIO_Monitor_close(int argc, VALUE *argv, VALUE self);
|
|
19
21
|
static VALUE NIO_Monitor_is_closed(VALUE self);
|
|
20
22
|
static VALUE NIO_Monitor_io(VALUE self);
|
|
@@ -40,6 +42,7 @@ void Init_NIO_Monitor()
|
|
|
40
42
|
rb_define_alloc_func(cNIO_Monitor, NIO_Monitor_allocate);
|
|
41
43
|
|
|
42
44
|
rb_define_method(cNIO_Monitor, "initialize", NIO_Monitor_initialize, 3);
|
|
45
|
+
rb_define_method(cNIO_Monitor, "interests=", NIO_Monitor_set_interests, 1);
|
|
43
46
|
rb_define_method(cNIO_Monitor, "close", NIO_Monitor_close, -1);
|
|
44
47
|
rb_define_method(cNIO_Monitor, "closed?", NIO_Monitor_is_closed, 0);
|
|
45
48
|
rb_define_method(cNIO_Monitor, "io", NIO_Monitor_io, 0);
|
|
@@ -117,6 +120,38 @@ static VALUE NIO_Monitor_initialize(VALUE self, VALUE io, VALUE interests, VALUE
|
|
|
117
120
|
return Qnil;
|
|
118
121
|
}
|
|
119
122
|
|
|
123
|
+
static VALUE NIO_Monitor_set_interests(VALUE self, VALUE interests)
|
|
124
|
+
{
|
|
125
|
+
struct NIO_Monitor *monitor;
|
|
126
|
+
ID interests_id;
|
|
127
|
+
|
|
128
|
+
if(NIO_Monitor_is_closed(self) == Qtrue) {
|
|
129
|
+
rb_raise(rb_eTypeError, "monitor is already closed");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
interests_id = SYM2ID(interests);
|
|
133
|
+
Data_Get_Struct(self, struct NIO_Monitor, monitor);
|
|
134
|
+
|
|
135
|
+
if(interests_id == rb_intern("r")) {
|
|
136
|
+
monitor->interests = EV_READ;
|
|
137
|
+
} else if(interests_id == rb_intern("w")) {
|
|
138
|
+
monitor->interests = EV_WRITE;
|
|
139
|
+
} else if(interests_id == rb_intern("rw")) {
|
|
140
|
+
monitor->interests = EV_READ | EV_WRITE;
|
|
141
|
+
} else {
|
|
142
|
+
rb_raise(rb_eArgError, "invalid interest type %s (must be :r, :w, or :rw)",
|
|
143
|
+
RSTRING_PTR(rb_funcall(interests, rb_intern("inspect"), 0, 0)));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
ev_io_stop(monitor->selector->ev_loop, &monitor->ev_io);
|
|
147
|
+
ev_io_set(&monitor->ev_io, monitor->ev_io.fd, monitor->interests);
|
|
148
|
+
ev_io_start(monitor->selector->ev_loop, &monitor->ev_io);
|
|
149
|
+
|
|
150
|
+
rb_ivar_set(self, rb_intern("interests"), interests);
|
|
151
|
+
|
|
152
|
+
return interests;
|
|
153
|
+
}
|
|
154
|
+
|
|
120
155
|
static VALUE NIO_Monitor_close(int argc, VALUE *argv, VALUE self)
|
|
121
156
|
{
|
|
122
157
|
VALUE deregister, selector;
|
|
@@ -127,10 +162,11 @@ static VALUE NIO_Monitor_close(int argc, VALUE *argv, VALUE self)
|
|
|
127
162
|
selector = rb_ivar_get(self, rb_intern("selector"));
|
|
128
163
|
|
|
129
164
|
if(selector != Qnil) {
|
|
130
|
-
/* if ev_loop is 0, it means that the loop has been stopped already (see NIO_Selector_shutdown)*/
|
|
165
|
+
/* if ev_loop is 0, it means that the loop has been stopped already (see NIO_Selector_shutdown) */
|
|
131
166
|
if(monitor->selector->ev_loop != 0) {
|
|
132
167
|
ev_io_stop(monitor->selector->ev_loop, &monitor->ev_io);
|
|
133
168
|
}
|
|
169
|
+
|
|
134
170
|
monitor->selector = 0;
|
|
135
171
|
rb_ivar_set(self, rb_intern("selector"), Qnil);
|
|
136
172
|
|
|
@@ -148,7 +184,7 @@ static VALUE NIO_Monitor_is_closed(VALUE self)
|
|
|
148
184
|
struct NIO_Monitor *monitor;
|
|
149
185
|
Data_Get_Struct(self, struct NIO_Monitor, monitor);
|
|
150
186
|
|
|
151
|
-
return
|
|
187
|
+
return monitor->selector == 0 ? Qtrue : Qfalse;
|
|
152
188
|
}
|
|
153
189
|
|
|
154
190
|
static VALUE NIO_Monitor_io(VALUE self)
|
|
@@ -235,6 +235,11 @@ public class Nio4r implements Library {
|
|
|
235
235
|
@JRubyMethod
|
|
236
236
|
public synchronized IRubyObject select(ThreadContext context, IRubyObject timeout, Block block) {
|
|
237
237
|
Ruby runtime = context.getRuntime();
|
|
238
|
+
|
|
239
|
+
if(!this.selector.isOpen()) {
|
|
240
|
+
throw context.getRuntime().newIOError("selector is closed");
|
|
241
|
+
}
|
|
242
|
+
|
|
238
243
|
int ready = doSelect(runtime, context, timeout);
|
|
239
244
|
|
|
240
245
|
/* Timeout or wakeup */
|
|
@@ -353,6 +358,36 @@ public class Nio4r implements Library {
|
|
|
353
358
|
key.attach(this);
|
|
354
359
|
}
|
|
355
360
|
|
|
361
|
+
@JRubyMethod(name = "interests=")
|
|
362
|
+
public IRubyObject setInterests(ThreadContext context, IRubyObject interests) {
|
|
363
|
+
if(this.closed == context.getRuntime().getTrue()) {
|
|
364
|
+
throw context.getRuntime().newTypeError("monitor is already closed");
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
int interestOps = 0;
|
|
368
|
+
Ruby runtime = context.getRuntime();
|
|
369
|
+
Channel rawChannel = io.getChannel();
|
|
370
|
+
SelectableChannel channel = (SelectableChannel)rawChannel;
|
|
371
|
+
|
|
372
|
+
this.interests = interests;
|
|
373
|
+
|
|
374
|
+
if(interests == ruby.newSymbol("r")) {
|
|
375
|
+
interestOps = SelectionKey.OP_READ;
|
|
376
|
+
} else if(interests == ruby.newSymbol("w")) {
|
|
377
|
+
interestOps = SelectionKey.OP_WRITE;
|
|
378
|
+
} else if(interests == ruby.newSymbol("rw")) {
|
|
379
|
+
interestOps = SelectionKey.OP_READ|SelectionKey.OP_WRITE;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if((interestOps & ~(channel.validOps())) == 0) {
|
|
383
|
+
key.interestOps(interestOps);
|
|
384
|
+
} else {
|
|
385
|
+
throw context.getRuntime().newArgumentError("given interests not supported for this IO object");
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return this.interests;
|
|
389
|
+
}
|
|
390
|
+
|
|
356
391
|
@JRubyMethod
|
|
357
392
|
public IRubyObject io(ThreadContext context) {
|
|
358
393
|
return io;
|
data/ext/nio4r/selector.c
CHANGED
|
@@ -7,7 +7,13 @@
|
|
|
7
7
|
#ifdef HAVE_RUBYSIG_H
|
|
8
8
|
# include "rubysig.h"
|
|
9
9
|
#endif
|
|
10
|
+
|
|
11
|
+
#ifdef HAVE_UNISTD_H
|
|
10
12
|
#include <unistd.h>
|
|
13
|
+
#else
|
|
14
|
+
#include <io.h>
|
|
15
|
+
#endif
|
|
16
|
+
|
|
11
17
|
#include <fcntl.h>
|
|
12
18
|
#include <assert.h>
|
|
13
19
|
|
|
@@ -291,6 +297,11 @@ static VALUE NIO_Selector_select_synchronized(VALUE *args)
|
|
|
291
297
|
struct NIO_Selector *selector;
|
|
292
298
|
|
|
293
299
|
Data_Get_Struct(args[0], struct NIO_Selector, selector);
|
|
300
|
+
|
|
301
|
+
if(selector->closed) {
|
|
302
|
+
rb_raise(rb_eIOError, "selector is closed");
|
|
303
|
+
}
|
|
304
|
+
|
|
294
305
|
if(!rb_block_given_p()) {
|
|
295
306
|
selector->ready_array = rb_ary_new();
|
|
296
307
|
}
|
data/lib/nio/monitor.rb
CHANGED
|
@@ -13,11 +13,21 @@ module NIO
|
|
|
13
13
|
io = io.to_io
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
fail TypeError, "can't convert #{io.class} into IO" unless io.is_a? IO
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
@io
|
|
20
|
-
@
|
|
19
|
+
@io = io
|
|
20
|
+
@interests = interests
|
|
21
|
+
@selector = selector
|
|
22
|
+
@closed = false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# set the interests set
|
|
26
|
+
def interests=(interests)
|
|
27
|
+
fail TypeError, "monitor is already closed" if closed?
|
|
28
|
+
fail ArgumentError, "bad interests: #{interests}" unless [:r, :w, :rw].include?(interests)
|
|
29
|
+
|
|
30
|
+
@interests = interests
|
|
21
31
|
end
|
|
22
32
|
|
|
23
33
|
# Is the IO object readable?
|
|
@@ -32,7 +42,9 @@ module NIO
|
|
|
32
42
|
alias_method :writeable?, :writable?
|
|
33
43
|
|
|
34
44
|
# Is this monitor closed?
|
|
35
|
-
def closed
|
|
45
|
+
def closed?
|
|
46
|
+
@closed
|
|
47
|
+
end
|
|
36
48
|
|
|
37
49
|
# Deactivate this monitor
|
|
38
50
|
def close(deregister = true)
|
data/lib/nio/selector.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "set"
|
|
2
2
|
|
|
3
3
|
module NIO
|
|
4
4
|
# Selectors monitor IO objects for events of interest
|
|
@@ -20,13 +20,10 @@ module NIO
|
|
|
20
20
|
# * :rw - is the IO either readable or writeable?
|
|
21
21
|
def register(io, interest)
|
|
22
22
|
@lock.synchronize do
|
|
23
|
-
if closed?
|
|
24
|
-
raise IOError, "selector is closed"
|
|
25
|
-
end
|
|
23
|
+
fail IOError, "selector is closed" if closed?
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
end
|
|
25
|
+
monitor = @selectables[io]
|
|
26
|
+
fail ArgumentError, "already registered as #{monitor.interests.inspect}" if monitor
|
|
30
27
|
|
|
31
28
|
monitor = Monitor.new(io, interest, self)
|
|
32
29
|
@selectables[monitor.io] = monitor
|
|
@@ -39,20 +36,21 @@ module NIO
|
|
|
39
36
|
def deregister(io)
|
|
40
37
|
@lock.synchronize do
|
|
41
38
|
monitor = @selectables.delete io
|
|
42
|
-
monitor.close(false) if monitor
|
|
39
|
+
monitor.close(false) if monitor && !monitor.closed?
|
|
43
40
|
monitor
|
|
44
41
|
end
|
|
45
42
|
end
|
|
46
43
|
|
|
47
44
|
# Is the given IO object registered with the selector?
|
|
48
45
|
def registered?(io)
|
|
49
|
-
@lock.synchronize { @selectables.
|
|
46
|
+
@lock.synchronize { @selectables.key? io }
|
|
50
47
|
end
|
|
51
48
|
|
|
52
49
|
# Select which monitors are ready
|
|
53
50
|
def select(timeout = nil)
|
|
54
51
|
@lock.synchronize do
|
|
55
|
-
readers
|
|
52
|
+
readers = [@wakeup]
|
|
53
|
+
writers = []
|
|
56
54
|
|
|
57
55
|
@selectables.each do |io, monitor|
|
|
58
56
|
readers << io if monitor.interests == :r || monitor.interests == :rw
|
|
@@ -80,11 +78,10 @@ module NIO
|
|
|
80
78
|
|
|
81
79
|
ready_writers.each do |io|
|
|
82
80
|
monitor = @selectables[io]
|
|
83
|
-
monitor.readiness
|
|
84
|
-
|
|
85
|
-
:rw
|
|
81
|
+
if monitor.readiness == :r
|
|
82
|
+
monitor.readiness = :rw
|
|
86
83
|
else
|
|
87
|
-
:w
|
|
84
|
+
monitor.readiness = :w
|
|
88
85
|
end
|
|
89
86
|
selected_monitors << monitor
|
|
90
87
|
end
|
|
@@ -124,7 +121,9 @@ module NIO
|
|
|
124
121
|
end
|
|
125
122
|
|
|
126
123
|
# Is this selector closed?
|
|
127
|
-
def closed
|
|
124
|
+
def closed?
|
|
125
|
+
@closed
|
|
126
|
+
end
|
|
128
127
|
|
|
129
128
|
def empty?
|
|
130
129
|
@selectables.empty?
|
data/lib/nio/version.rb
CHANGED
data/lib/nio.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require
|
|
1
|
+
require "thread"
|
|
2
|
+
require "socket"
|
|
3
|
+
require "nio/version"
|
|
4
4
|
|
|
5
5
|
# New I/O for Ruby
|
|
6
6
|
module NIO
|
|
@@ -8,22 +8,24 @@ module NIO
|
|
|
8
8
|
# * select: in pure Ruby using Kernel.select
|
|
9
9
|
# * libev: as a C extension using libev
|
|
10
10
|
# * java: using Java NIO
|
|
11
|
-
def self.engine
|
|
11
|
+
def self.engine
|
|
12
|
+
ENGINE
|
|
13
|
+
end
|
|
12
14
|
end
|
|
13
15
|
|
|
14
|
-
if ENV["NIO4R_PURE"] || (Gem.win_platform? && !defined?(JRUBY_VERSION))
|
|
15
|
-
require
|
|
16
|
-
require
|
|
17
|
-
NIO::ENGINE =
|
|
16
|
+
if ENV["NIO4R_PURE"] == "true" || (Gem.win_platform? && !defined?(JRUBY_VERSION))
|
|
17
|
+
require "nio/monitor"
|
|
18
|
+
require "nio/selector"
|
|
19
|
+
NIO::ENGINE = "ruby"
|
|
18
20
|
else
|
|
19
|
-
require
|
|
21
|
+
require "nio4r_ext"
|
|
20
22
|
|
|
21
23
|
if defined?(JRUBY_VERSION)
|
|
22
|
-
require
|
|
23
|
-
require
|
|
24
|
+
require "java"
|
|
25
|
+
require "jruby"
|
|
24
26
|
org.nio4r.Nio4r.new.load(JRuby.runtime, false)
|
|
25
|
-
NIO::ENGINE =
|
|
27
|
+
NIO::ENGINE = "java"
|
|
26
28
|
else
|
|
27
|
-
NIO::ENGINE =
|
|
29
|
+
NIO::ENGINE = "libev"
|
|
28
30
|
end
|
|
29
31
|
end
|
data/nio4r.gemspec
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
require File.expand_path(
|
|
2
|
+
require File.expand_path("../lib/nio/version", __FILE__)
|
|
3
3
|
|
|
4
4
|
Gem::Specification.new do |gem|
|
|
5
5
|
gem.authors = ["Tony Arcieri"]
|
|
@@ -9,7 +9,7 @@ Gem::Specification.new do |gem|
|
|
|
9
9
|
gem.homepage = "https://github.com/celluloid/nio4r"
|
|
10
10
|
gem.license = "MIT"
|
|
11
11
|
|
|
12
|
-
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
12
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
|
13
13
|
gem.files = `git ls-files`.split("\n")
|
|
14
14
|
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
15
15
|
gem.name = "nio4r"
|
|
@@ -25,5 +25,6 @@ Gem::Specification.new do |gem|
|
|
|
25
25
|
|
|
26
26
|
gem.add_development_dependency "rake-compiler"
|
|
27
27
|
gem.add_development_dependency "rake"
|
|
28
|
-
gem.add_development_dependency "rspec", "~> 3.0
|
|
28
|
+
gem.add_development_dependency "rspec", "~> 3.0"
|
|
29
|
+
gem.add_development_dependency "rubocop"
|
|
29
30
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "spec_helper"
|
|
2
2
|
|
|
3
|
-
describe "NIO acceptables" do
|
|
3
|
+
RSpec.describe "NIO acceptables" do
|
|
4
4
|
shared_context "an NIO acceptable" do
|
|
5
5
|
let(:selector) { NIO::Selector.new }
|
|
6
6
|
|
|
@@ -15,7 +15,7 @@ describe "NIO acceptables" do
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
describe TCPServer do
|
|
18
|
-
let(:tcp_port) {
|
|
18
|
+
let(:tcp_port) { 23_456 }
|
|
19
19
|
|
|
20
20
|
let :acceptable_subject do
|
|
21
21
|
server = TCPServer.new("localhost", tcp_port)
|
data/spec/nio/monitor_spec.rb
CHANGED
|
@@ -1,20 +1,49 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "socket"
|
|
3
|
+
|
|
4
|
+
RSpec.describe NIO::Monitor do
|
|
5
|
+
let(:example_peers) do
|
|
6
|
+
address = "127.0.0.1"
|
|
7
|
+
base_port = 12_345
|
|
8
|
+
tries = 10
|
|
9
|
+
|
|
10
|
+
server = tries.times do |n|
|
|
11
|
+
begin
|
|
12
|
+
break TCPServer.new(address, base_port + n)
|
|
13
|
+
rescue Errno::EADDRINUSE
|
|
14
|
+
retry
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
fail Errno::EADDRINUSE, "couldn't find an open port" unless server
|
|
19
|
+
client = TCPSocket.new(address, server.addr[1])
|
|
20
|
+
[server, client]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
let(:reader) { example_peers.first }
|
|
24
|
+
let(:writer) { example_peers.last }
|
|
2
25
|
|
|
3
|
-
describe NIO::Monitor do
|
|
4
|
-
let(:pipes) { IO.pipe }
|
|
5
|
-
let(:reader) { pipes.first }
|
|
6
|
-
let(:writer) { pipes.last }
|
|
7
26
|
let(:selector) { NIO::Selector.new }
|
|
8
27
|
|
|
9
28
|
subject { selector.register(reader, :r) }
|
|
10
29
|
let(:peer) { selector.register(writer, :rw) }
|
|
11
30
|
after { selector.close }
|
|
12
31
|
|
|
32
|
+
before { example_peers } # open server and client automatically
|
|
33
|
+
after { reader.close }
|
|
34
|
+
after { writer.close }
|
|
35
|
+
|
|
13
36
|
it "knows its interests" do
|
|
14
37
|
expect(subject.interests).to eq(:r)
|
|
15
38
|
expect(peer.interests).to eq(:rw)
|
|
16
39
|
end
|
|
17
40
|
|
|
41
|
+
it "changes the interest set" do
|
|
42
|
+
expect(peer.interests).not_to eq(:w)
|
|
43
|
+
peer.interests = :w
|
|
44
|
+
expect(peer.interests).to eq(:w)
|
|
45
|
+
end
|
|
46
|
+
|
|
18
47
|
it "knows its IO object" do
|
|
19
48
|
expect(subject.io).to eq(reader)
|
|
20
49
|
end
|
|
@@ -30,17 +59,17 @@ describe NIO::Monitor do
|
|
|
30
59
|
|
|
31
60
|
it "knows what operations IO objects are ready for" do
|
|
32
61
|
# For whatever odd reason this breaks unless we eagerly evaluate subject
|
|
33
|
-
reader_monitor
|
|
62
|
+
reader_monitor = subject
|
|
63
|
+
writer_monitor = peer
|
|
34
64
|
|
|
35
65
|
selected = selector.select(0)
|
|
36
|
-
expect(selected).not_to include(reader_monitor)
|
|
37
66
|
expect(selected).to include(writer_monitor)
|
|
38
67
|
|
|
39
68
|
expect(writer_monitor.readiness).to eq(:w)
|
|
40
69
|
expect(writer_monitor).not_to be_readable
|
|
41
70
|
expect(writer_monitor).to be_writable
|
|
42
71
|
|
|
43
|
-
writer << "
|
|
72
|
+
writer << "testing 1 2 3"
|
|
44
73
|
|
|
45
74
|
selected = selector.select(0)
|
|
46
75
|
expect(selected).to include(reader_monitor)
|
|
@@ -50,6 +79,14 @@ describe NIO::Monitor do
|
|
|
50
79
|
expect(reader_monitor).not_to be_writable
|
|
51
80
|
end
|
|
52
81
|
|
|
82
|
+
it "changes current interests with #interests=" do
|
|
83
|
+
client = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
|
84
|
+
monitor = selector.register(client, :r)
|
|
85
|
+
expect(monitor.interests).to eq(:r)
|
|
86
|
+
monitor.interests = :w
|
|
87
|
+
expect(monitor.interests).to eq(:w)
|
|
88
|
+
end
|
|
89
|
+
|
|
53
90
|
it "closes" do
|
|
54
91
|
expect(subject).not_to be_closed
|
|
55
92
|
expect(selector.registered?(reader)).to be_truthy
|
|
@@ -66,4 +103,11 @@ describe NIO::Monitor do
|
|
|
66
103
|
subject.close
|
|
67
104
|
expect(subject).to be_closed
|
|
68
105
|
end
|
|
106
|
+
|
|
107
|
+
it "changes the interest set after monitor closed" do
|
|
108
|
+
# check for changing the interests on the go after closed expected to fail
|
|
109
|
+
expect(subject.interests).not_to eq(:rw)
|
|
110
|
+
subject.close # forced shutdown
|
|
111
|
+
expect { subject.interests = :rw }.to raise_error(TypeError)
|
|
112
|
+
end
|
|
69
113
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "spec_helper"
|
|
2
2
|
|
|
3
|
-
describe "IO.pipe" do
|
|
3
|
+
RSpec.describe "IO.pipe" do
|
|
4
4
|
let(:pair) { IO.pipe }
|
|
5
5
|
|
|
6
6
|
let :unreadable_subject do
|
|
@@ -18,17 +18,17 @@ describe "IO.pipe" do
|
|
|
18
18
|
let :unwritable_subject do
|
|
19
19
|
reader, pipe = IO.pipe
|
|
20
20
|
|
|
21
|
-
#HACK: On OS X 10.8, this str must be larger than PIPE_BUF. Otherwise,
|
|
21
|
+
# HACK: On OS X 10.8, this str must be larger than PIPE_BUF. Otherwise,
|
|
22
22
|
# the write is atomic and select() will return writable but write()
|
|
23
23
|
# will throw EAGAIN if there is too little space to write the string
|
|
24
24
|
# TODO: Use FFI to lookup the platform-specific size of PIPE_BUF
|
|
25
|
-
str = "JUNK IN THE TUBES" *
|
|
25
|
+
str = "JUNK IN THE TUBES" * 10_000
|
|
26
26
|
begin
|
|
27
27
|
pipe.write_nonblock str
|
|
28
28
|
_, writers = select [], [pipe], [], 0
|
|
29
29
|
rescue Errno::EPIPE
|
|
30
30
|
break
|
|
31
|
-
end while writers
|
|
31
|
+
end while writers && writers.include?(pipe)
|
|
32
32
|
|
|
33
33
|
pipe
|
|
34
34
|
end
|