123456789_123456789_123456789_123456789_123456789_

Class: NIO::Selector

Relationships & Source Files
Inherits: Object
Defined in: lib/nio/selector.rb,
ext/nio4r/selector.c

Overview

Selectors monitor IO objects for events of interest

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(*args)

Create a new selector. This is more or less the pure Ruby version translated into an MRI cext

[ GitHub ]

  
# File 'ext/nio4r/selector.c', line 224

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;

    TypedData_Get_Struct(self, struct NIO_Selector, &NIO_Selector_type, 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)));
        }

        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 if (backend_id == rb_intern("linuxaio")) {
            flags = EVBACKEND_LINUXAIO;
        } else if (backend_id == rb_intern("io_uring")) {
            flags = EVBACKEND_IOURING;
        } else {
            rb_raise(rb_eArgError, "unsupported backend: %s", RSTRING_PTR(rb_funcall(backend, rb_intern("inspect"), 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);

    lock = rb_class_new_instance(0, 0, rb_const_get(rb_cObject, rb_intern("Mutex")));
    rb_ivar_set(self, rb_intern("lock"), lock);
    rb_ivar_set(self, rb_intern("lock_holder"), Qnil);

    return Qnil;
}

#initialize(backend = :ruby) ⇒ Selector

Create a new Selector

Raises:

  • (ArgumentError)
[ GitHub ]

  
# File 'lib/nio/selector.rb', line 31

def initialize(backend = :ruby)
  raise ArgumentError, "unsupported backend: #{backend}" unless [:ruby, nil].include?(backend)

  @selectables = {}
  @lock = Mutex.new

  # Other threads can wake up a selector
  @wakeup, @waker = IO.pipe
  @closed = false
end

Class Method Details

.backends

Return supported backends as symbols

See #backend method definition for all possible backends

[ GitHub ]

  
# File 'ext/nio4r/selector.c', line 186

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")));
    }

    if (backends & EVBACKEND_LINUXAIO) {
        rb_ary_push(result, ID2SYM(rb_intern("linuxaio")));
    }

    if (backends & EVBACKEND_IOURING) {
        rb_ary_push(result, ID2SYM(rb_intern("io_uring")));
    }

    return result;
}

Instance Attribute Details

#closed?Boolean (readonly)

Is this selector closed?

[ GitHub ]

  
# File 'ext/nio4r/selector.c', line 548

static VALUE NIO_Selector_closed(VALUE self)
{
    return NIO_Selector_synchronize(self, NIO_Selector_closed_synchronized, self);
}

#empty?Boolean (readonly)

True if there are monitors on the loop

[ GitHub ]

  
# File 'ext/nio4r/selector.c', line 563

static VALUE NIO_Selector_is_empty(VALUE self)
{
    VALUE selectables = rb_ivar_get(self, rb_intern("selectables"));

    return rb_funcall(selectables, rb_intern("empty?"), 0) == Qtrue ? Qtrue : Qfalse;
}

Instance Method Details

#backend

Return a symbol representing the backend I/O multiplexing mechanism used. Supported backends are:

  • :ruby - pure Ruby (i.e IO.select)
  • :java - Java NIO on JRuby
  • :epoll - libev w\ Linux epoll
  • :poll - libev w\ POSIX poll
  • :kqueue - libev w\ BSD kqueue
  • :select - libev w\ SysV select
  • :port - libev w\ I/O completion ports
  • :linuxaio - libev w\ Linux AIO io_submit (experimental)
  • :io_uring - libev w\ Linux io_uring (experimental)
  • :unknown - libev w\ unknown backend
[ GitHub ]

  
# File 'ext/nio4r/selector.c', line 283

static VALUE NIO_Selector_backend(VALUE self)
{
    struct NIO_Selector *selector;

    TypedData_Get_Struct(self, struct NIO_Selector, &NIO_Selector_type, selector);
    if (selector->closed) {
        rb_raise(rb_eIOError, "selector is closed");
    }

    switch (ev_backend(selector->ev_loop)) {
        case EVBACKEND_EPOLL:
            return ID2SYM(rb_intern("epoll"));
        case EVBACKEND_POLL:
            return ID2SYM(rb_intern("poll"));
        case EVBACKEND_KQUEUE:
            return ID2SYM(rb_intern("kqueue"));
        case EVBACKEND_SELECT:
            return ID2SYM(rb_intern("select"));
        case EVBACKEND_PORT:
            return ID2SYM(rb_intern("port"));
        case EVBACKEND_LINUXAIO:
            return ID2SYM(rb_intern("linuxaio"));
        case EVBACKEND_IOURING:
            return ID2SYM(rb_intern("io_uring"));
    }

    return ID2SYM(rb_intern("unknown"));
}

#close

Close the selector and free system resources

[ GitHub ]

  
# File 'ext/nio4r/selector.c', line 531

static VALUE NIO_Selector_close(VALUE self)
{
    return NIO_Selector_synchronize(self, NIO_Selector_close_synchronized, self);
}

#deregister(io)

Deregister the given IO object from the selector

[ GitHub ]

  
# File 'ext/nio4r/selector.c', line 388

static VALUE NIO_Selector_deregister(VALUE self, VALUE io)
{
    VALUE args[2] = {self, io};
    return NIO_Selector_synchronize(self, NIO_Selector_deregister_synchronized, (VALUE)args);
}

#register(io, interest)

Register interest in an IO object with the selector for the given types of events. Valid event types for interest are:

  • :r - is the IO readable?
  • :w - is the IO writeable?
  • :rw - is the IO either readable or writeable?
[ GitHub ]

  
# File 'ext/nio4r/selector.c', line 347

static VALUE NIO_Selector_register(VALUE self, VALUE io, VALUE interests)
{
    VALUE args[3] = {self, io, interests};
    return NIO_Selector_synchronize(self, NIO_Selector_register_synchronized, (VALUE)args);
}

#registered?(io) ⇒ Boolean

Is the given IO object registered with the selector?

[ GitHub ]

  
# File 'ext/nio4r/selector.c', line 414

static VALUE NIO_Selector_is_registered(VALUE self, VALUE io)
{
    VALUE selectables = rb_ivar_get(self, rb_intern("selectables"));

    /* Perhaps this should be holding the mutex? */
    return rb_funcall(selectables, rb_intern("has_key?"), 1, io);
}

#select(timeout = nil)

Select from all registered IO objects

[ GitHub ]

  
# File 'ext/nio4r/selector.c', line 423

static VALUE NIO_Selector_select(int argc, VALUE *argv, VALUE self)
{
    VALUE timeout;

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

    if (timeout != Qnil && NUM2DBL(timeout) < 0) {
        rb_raise(rb_eArgError, "time interval must be positive");
    }

    VALUE args[2] = {self, timeout};
    return NIO_Selector_synchronize(self, NIO_Selector_select_synchronized, (VALUE)args);
}

#wakeup

Wake up a thread that's in the middle of selecting on this selector, if any such thread exists.

Invoking this method more than once between two successive select calls has the same effect as invoking it just once. In other words, it provides level-triggered behavior.

[ GitHub ]

  
# File 'ext/nio4r/selector.c', line 515

static VALUE NIO_Selector_wakeup(VALUE self)
{
    struct NIO_Selector *selector;
    TypedData_Get_Struct(self, struct NIO_Selector, &NIO_Selector_type, selector);

    if (selector->closed) {
        rb_raise(rb_eIOError, "selector is closed");
    }

    selector->wakeup_fired = 1;
    write(selector->wakeup_writer, "\0", 1);

    return Qnil;
}