From 31af6e180660feaf0cb3b3379c66875fe97e6d97 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 28 May 2017 23:42:06 -0700 Subject: [PATCH] NIO::Selector: Support for enumerating and configuring backend * Adds NIO::Selector.backends to enumerate supported backends * Adds an optional parameter to #initialize to explicitly choose backend --- ext/nio4r/nio4r_ext.c | 2 + ext/nio4r/org/nio4r/Selector.java | 15 ++++++ ext/nio4r/selector.c | 89 ++++++++++++++++++++++++++++--- lib/nio/selector.rb | 11 +++- spec/nio/selector_spec.rb | 23 ++++++++ 5 files changed, 132 insertions(+), 8 deletions(-) diff --git a/ext/nio4r/nio4r_ext.c b/ext/nio4r/nio4r_ext.c index 7317e94..b2a81b8 100644 --- a/ext/nio4r/nio4r_ext.c +++ b/ext/nio4r/nio4r_ext.c @@ -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(); diff --git a/ext/nio4r/org/nio4r/Selector.java b/ext/nio4r/org/nio4r/Selector.java index bc69498..88e91b0 100644 --- a/ext/nio4r/org/nio4r/Selector.java +++ b/ext/nio4r/org/nio4r/Selector.java @@ -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(); this.wakeupFired = false; diff --git a/ext/nio4r/selector.c b/ext/nio4r/selector.c index 2c85963..1ad747f 100644 --- a/ext/nio4r/selector.c +++ b/ext/nio4r/selector.c @@ -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); @@ -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); @@ -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); @@ -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; @@ -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); diff --git a/lib/nio/selector.rb b/lib/nio/selector.rb index cc46318..552d906 100644 --- a/lib/nio/selector.rb +++ b/lib/nio/selector.rb @@ -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 diff --git a/spec/nio/selector_spec.rb b/spec/nio/selector_spec.rb index 80a81d5..4c6d3cc 100644 --- a/spec/nio/selector_spec.rb +++ b/spec/nio/selector_spec.rb @@ -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