Skip to content

Commit

Permalink
NIO::Selector: Support for enumerating and configuring backend
Browse files Browse the repository at this point in the history
* Adds NIO::Selector.backends to enumerate supported backends
* Adds an optional parameter to #initialize to explicitly choose backend
  • Loading branch information
tarcieri committed May 29, 2017
1 parent 80bc9bd commit 31af6e1
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 8 deletions.
2 changes: 2 additions & 0 deletions ext/nio4r/nio4r_ext.c
Expand Up @@ -12,6 +12,8 @@ void Init_NIO_ByteBuffer();

void Init_nio4r_ext()
{
ev_set_allocator(xrealloc);

Init_NIO_Selector();
Init_NIO_Monitor();
Init_NIO_ByteBuffer();
Expand Down
15 changes: 15 additions & 0 deletions ext/nio4r/org/nio4r/Selector.java
Expand Up @@ -30,8 +30,23 @@ public Selector(final Ruby ruby, RubyClass rubyClass) {
super(ruby, rubyClass);
}

@JRubyMethod(meta = true)
public static IRubyObject backends(ThreadContext context, IRubyObject self) {
return context.runtime.newArray(context.runtime.newSymbol("java"));
}

@JRubyMethod
public IRubyObject initialize(ThreadContext context) {
initialize(context, context.runtime.newSymbol("java"));
return context.nil;
}

@JRubyMethod
public IRubyObject initialize(ThreadContext context, IRubyObject backend) {
if(backend != context.runtime.newSymbol("java")) {
throw context.runtime.newArgumentError(":java is the only supported backend");
}

this.cancelledKeys = new HashMap<SelectableChannel,SelectionKey>();
this.wakeupFired = false;

Expand Down
89 changes: 82 additions & 7 deletions ext/nio4r/selector.c
Expand Up @@ -27,8 +27,11 @@ static void NIO_Selector_mark(struct NIO_Selector *loop);
static void NIO_Selector_shutdown(struct NIO_Selector *selector);
static void NIO_Selector_free(struct NIO_Selector *loop);

/* Methods */
static VALUE NIO_Selector_initialize(VALUE self);
/* Class methods */
static VALUE NIO_Selector_supported_backends(VALUE klass);

/* Instance methods */
static VALUE NIO_Selector_initialize(int argc, VALUE *argv, VALUE self);
static VALUE NIO_Selector_backend(VALUE self);
static VALUE NIO_Selector_register(VALUE self, VALUE selectable, VALUE interest);
static VALUE NIO_Selector_deregister(VALUE self, VALUE io);
Expand Down Expand Up @@ -65,7 +68,8 @@ void Init_NIO_Selector()
cNIO_Selector = rb_define_class_under(mNIO, "Selector", rb_cObject);
rb_define_alloc_func(cNIO_Selector, NIO_Selector_allocate);

rb_define_method(cNIO_Selector, "initialize", NIO_Selector_initialize, 0);
rb_define_singleton_method(cNIO_Selector, "backends", NIO_Selector_supported_backends, 0);
rb_define_method(cNIO_Selector, "initialize", NIO_Selector_initialize, -1);
rb_define_method(cNIO_Selector, "backend", NIO_Selector_backend, 0);
rb_define_method(cNIO_Selector, "register", NIO_Selector_register, 2);
rb_define_method(cNIO_Selector, "deregister", NIO_Selector_deregister, 1);
Expand Down Expand Up @@ -102,7 +106,9 @@ static VALUE NIO_Selector_allocate(VALUE klass)
}

selector = (struct NIO_Selector *)xmalloc(sizeof(struct NIO_Selector));
selector->ev_loop = ev_loop_new(0);

/* Defer initializing the loop to #initialize */
selector->ev_loop = 0;

ev_init(&selector->timer, NIO_Selector_timeout_callback);

Expand All @@ -112,8 +118,6 @@ static VALUE NIO_Selector_allocate(VALUE klass)
ev_io_init(&selector->wakeup, NIO_Selector_wakeup_callback, selector->wakeup_reader, EV_READ);
selector->wakeup.data = (void *)selector;

ev_io_start(selector->ev_loop, &selector->wakeup);

selector->closed = selector->selecting = selector->wakeup_fired = selector->ready_count = 0;
selector->ready_array = Qnil;

Expand Down Expand Up @@ -154,12 +158,83 @@ static void NIO_Selector_free(struct NIO_Selector *selector)
xfree(selector);
}

/* Return an array of symbols for supported backends */
static VALUE NIO_Selector_supported_backends(VALUE klass) {
unsigned int backends = ev_supported_backends();
VALUE result = rb_ary_new();

if(backends & EVBACKEND_EPOLL) {
rb_ary_push(result, ID2SYM(rb_intern("epoll")));
}

if(backends & EVBACKEND_POLL) {
rb_ary_push(result, ID2SYM(rb_intern("poll")));
}

if(backends & EVBACKEND_KQUEUE) {
rb_ary_push(result, ID2SYM(rb_intern("kqueue")));
}

if(backends & EVBACKEND_SELECT) {
rb_ary_push(result, ID2SYM(rb_intern("select")));
}

if(backends & EVBACKEND_PORT) {
rb_ary_push(result, ID2SYM(rb_intern("port")));
}

return result;
}

/* Create a new selector. This is more or less the pure Ruby version
translated into an MRI cext */
static VALUE NIO_Selector_initialize(VALUE self)
static VALUE NIO_Selector_initialize(int argc, VALUE *argv, VALUE self)
{
ID backend_id;
VALUE backend;
VALUE lock;

struct NIO_Selector *selector;
unsigned int flags = 0;

Data_Get_Struct(self, struct NIO_Selector, selector);

rb_scan_args(argc, argv, "01", &backend);

if(backend != Qnil) {
if(!rb_ary_includes(NIO_Selector_supported_backends(CLASS_OF(self)), backend)) {
rb_raise(rb_eArgError, "unsupported backend: %s",
RSTRING_PTR(rb_funcall(backend, rb_intern("inspect"), 0, 0)));
}

backend_id = SYM2ID(backend);

if(backend_id == rb_intern("epoll")) {
flags = EVBACKEND_EPOLL;
} else if(backend_id == rb_intern("poll")) {
flags = EVBACKEND_POLL;
} else if(backend_id == rb_intern("kqueue")) {
flags = EVBACKEND_KQUEUE;
} else if(backend_id == rb_intern("select")) {
flags = EVBACKEND_SELECT;
} else if(backend_id == rb_intern("port")) {
flags = EVBACKEND_PORT;
} else {
rb_raise(rb_eArgError, "unsupported backend: %s",
RSTRING_PTR(rb_funcall(backend, rb_intern("inspect"), 0, 0)));
}
}

/* Ensure the selector loop has not yet been initialized */
assert(!selector->ev_loop);

selector->ev_loop = ev_loop_new(flags);
if(!selector->ev_loop) {
rb_raise(rb_eIOError, "error initializing event loop");
}

ev_io_start(selector->ev_loop, &selector->wakeup);

rb_ivar_set(self, rb_intern("selectables"), rb_hash_new());
rb_ivar_set(self, rb_intern("lock_holder"), Qnil);

Expand Down
11 changes: 10 additions & 1 deletion lib/nio/selector.rb
Expand Up @@ -5,8 +5,17 @@
module NIO
# Selectors monitor IO objects for events of interest
class Selector
# Return supported backends as symbols
#
# See `#backend` method definition for all possible backends
def self.backends
[:ruby]
end

# Create a new NIO::Selector
def initialize
def initialize(backend = :ruby)
raise ArgumentError, "unsupported backend: #{backend}" unless backend == :ruby

@selectables = {}
@lock = Mutex.new

Expand Down
23 changes: 23 additions & 0 deletions spec/nio/selector_spec.rb
Expand Up @@ -13,6 +13,29 @@
let(:reader) { pair.first }
let(:writer) { pair.last }

context ".backends" do
it "knows all supported backends" do
expect(described_class.backends).to be_a Array
expect(described_class.backends.first).to be_a Symbol
end
end

context "#initialize" do
it "allows explicitly specifying a backend" do
backend = described_class.backends.first
selector = described_class.new(backend)
expect(selector.backend).to eq backend
end

it "raises ArgumentError if given an invalid backend" do
expect { described_class.new(:derp) }.to raise_error ArgumentError
end

it "raises TypeError if given a non-Symbol parameter" do
expect { described_class.new(42).to raise_error TypeError }
end
end

context "backend" do
it "knows its backend" do
expect(subject.backend).to be_a Symbol
Expand Down

0 comments on commit 31af6e1

Please sign in to comment.