// Released under the MIT License. // Copyright, 2021-2025, by Samuel Williams. #include "kqueue.h" #include "selector.h" #include "../list.h" #include "../array.h" #include #include #include #include #include #include #include "../interrupt.h" enum { DEBUG = 0, DEBUG_IO_READ = 0, DEBUG_IO_WRITE = 0, DEBUG_IO_WAIT = 0 }; #ifndef EVFILT_USER #define IO_EVENT_SELECTOR_KQUEUE_USE_INTERRUPT #endif enum {KQUEUE_MAX_EVENTS = 64}; // This represents an actual fiber waiting for a specific event. struct IO_Event_Selector_KQueue_Waiting { struct IO_Event_List list; // The events the fiber is waiting for. enum IO_Event events; // The events that are currently ready. enum IO_Event ready; // The fiber value itself. VALUE fiber; }; struct IO_Event_Selector_KQueue { struct IO_Event_Selector backend; int descriptor; int blocked; struct timespec idle_duration; #ifdef IO_EVENT_SELECTOR_KQUEUE_USE_INTERRUPT struct IO_Event_Interrupt interrupt; #endif struct IO_Event_Array descriptors; }; // This represents zero or more fibers waiting for a specific descriptor. struct IO_Event_Selector_KQueue_Descriptor { struct IO_Event_List list; // The union of all events we are waiting for: enum IO_Event waiting_events; // The union of events we are registered for: enum IO_Event registered_events; // The events that are currently ready: enum IO_Event ready_events; }; static void IO_Event_Selector_KQueue_Waiting_mark(struct IO_Event_List *_waiting) { struct IO_Event_Selector_KQueue_Waiting *waiting = (void*)_waiting; if (waiting->fiber) { rb_gc_mark_movable(waiting->fiber); } } static void IO_Event_Selector_KQueue_Descriptor_mark(void *_descriptor) { struct IO_Event_Selector_KQueue_Descriptor *descriptor = _descriptor; IO_Event_List_immutable_each(&descriptor->list, IO_Event_Selector_KQueue_Waiting_mark); } static void IO_Event_Selector_KQueue_Type_mark(void *_selector) { struct IO_Event_Selector_KQueue *selector = _selector; IO_Event_Selector_mark(&selector->backend); IO_Event_Array_each(&selector->descriptors, IO_Event_Selector_KQueue_Descriptor_mark); } static void IO_Event_Selector_KQueue_Waiting_compact(struct IO_Event_List *_waiting) { struct IO_Event_Selector_KQueue_Waiting *waiting = (void*)_waiting; if (waiting->fiber) { waiting->fiber = rb_gc_location(waiting->fiber); } } static void IO_Event_Selector_KQueue_Descriptor_compact(void *_descriptor) { struct IO_Event_Selector_KQueue_Descriptor *descriptor = _descriptor; IO_Event_List_immutable_each(&descriptor->list, IO_Event_Selector_KQueue_Waiting_compact); } static void IO_Event_Selector_KQueue_Type_compact(void *_selector) { struct IO_Event_Selector_KQueue *selector = _selector; IO_Event_Selector_compact(&selector->backend); IO_Event_Array_each(&selector->descriptors, IO_Event_Selector_KQueue_Descriptor_compact); } static void close_internal(struct IO_Event_Selector_KQueue *selector) { if (selector->descriptor >= 0) { close(selector->descriptor); selector->descriptor = -1; } } static void IO_Event_Selector_KQueue_Type_free(void *_selector) { struct IO_Event_Selector_KQueue *selector = _selector; close_internal(selector); IO_Event_Array_free(&selector->descriptors); free(selector); } static size_t IO_Event_Selector_KQueue_Type_size(const void *_selector) { const struct IO_Event_Selector_KQueue *selector = _selector; return sizeof(struct IO_Event_Selector_KQueue) + IO_Event_Array_memory_size(&selector->descriptors) ; } static const rb_data_type_t IO_Event_Selector_KQueue_Type = { .wrap_struct_name = "IO::Event::Backend::KQueue", .function = { .dmark = IO_Event_Selector_KQueue_Type_mark, .dcompact = IO_Event_Selector_KQueue_Type_compact, .dfree = IO_Event_Selector_KQueue_Type_free, .dsize = IO_Event_Selector_KQueue_Type_size, }, .data = NULL, .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; inline static struct IO_Event_Selector_KQueue_Descriptor * IO_Event_Selector_KQueue_Descriptor_lookup(struct IO_Event_Selector_KQueue *selector, uintptr_t descriptor) { struct IO_Event_Selector_KQueue_Descriptor *kqueue_descriptor = IO_Event_Array_lookup(&selector->descriptors, descriptor); if (!kqueue_descriptor) { rb_sys_fail("IO_Event_Selector_KQueue_Descriptor_lookup:IO_Event_Array_lookup"); } return kqueue_descriptor; } inline static enum IO_Event events_from_kevent_filter(int filter) { switch (filter) { case EVFILT_READ: return IO_EVENT_READABLE; case EVFILT_WRITE: return IO_EVENT_WRITABLE; case EVFILT_PROC: return IO_EVENT_EXIT; default: return 0; } } inline static int IO_Event_Selector_KQueue_Descriptor_update(struct IO_Event_Selector_KQueue *selector, uintptr_t identifier, struct IO_Event_Selector_KQueue_Descriptor *kqueue_descriptor) { int count = 0; struct kevent kevents[3] = {0}; if (kqueue_descriptor->waiting_events & IO_EVENT_READABLE) { kevents[count].ident = identifier; kevents[count].filter = EVFILT_READ; kevents[count].flags = EV_ADD | EV_ONESHOT; kevents[count].udata = (void *)kqueue_descriptor; // #ifdef EV_OOBAND // if (events & IO_EVENT_PRIORITY) { // kevents[count].flags |= EV_OOBAND; // } // #endif count++; } if (kqueue_descriptor->waiting_events & IO_EVENT_WRITABLE) { kevents[count].ident = identifier; kevents[count].filter = EVFILT_WRITE; kevents[count].flags = EV_ADD | EV_ONESHOT; kevents[count].udata = (void *)kqueue_descriptor; count++; } if (kqueue_descriptor->waiting_events & IO_EVENT_EXIT) { kevents[count].ident = identifier; kevents[count].filter = EVFILT_PROC; kevents[count].flags = EV_ADD | EV_ONESHOT; kevents[count].fflags = NOTE_EXIT; kevents[count].udata = (void *)kqueue_descriptor; count++; } if (count == 0) { return 0; } int result = kevent(selector->descriptor, kevents, count, NULL, 0, NULL); if (result == -1) { return result; } kqueue_descriptor->registered_events = kqueue_descriptor->waiting_events; return result; } inline static int IO_Event_Selector_KQueue_Waiting_register(struct IO_Event_Selector_KQueue *selector, uintptr_t identifier, struct IO_Event_Selector_KQueue_Waiting *waiting) { struct IO_Event_Selector_KQueue_Descriptor *kqueue_descriptor = IO_Event_Selector_KQueue_Descriptor_lookup(selector, identifier); // We are waiting for these events: kqueue_descriptor->waiting_events |= waiting->events; int result = IO_Event_Selector_KQueue_Descriptor_update(selector, identifier, kqueue_descriptor); if (result == -1) return -1; IO_Event_List_prepend(&kqueue_descriptor->list, &waiting->list); return result; } inline static void IO_Event_Selector_KQueue_Waiting_cancel(struct IO_Event_Selector_KQueue_Waiting *waiting) { IO_Event_List_pop(&waiting->list); waiting->fiber = 0; } void IO_Event_Selector_KQueue_Descriptor_initialize(void *element) { struct IO_Event_Selector_KQueue_Descriptor *kqueue_descriptor = element; IO_Event_List_initialize(&kqueue_descriptor->list); kqueue_descriptor->waiting_events = 0; kqueue_descriptor->registered_events = 0; kqueue_descriptor->ready_events = 0; } void IO_Event_Selector_KQueue_Descriptor_free(void *element) { struct IO_Event_Selector_KQueue_Descriptor *kqueue_descriptor = element; IO_Event_List_free(&kqueue_descriptor->list); } VALUE IO_Event_Selector_KQueue_allocate(VALUE self) { struct IO_Event_Selector_KQueue *selector = NULL; VALUE instance = TypedData_Make_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); IO_Event_Selector_initialize(&selector->backend, self, Qnil); selector->descriptor = -1; selector->blocked = 0; selector->descriptors.element_initialize = IO_Event_Selector_KQueue_Descriptor_initialize; selector->descriptors.element_free = IO_Event_Selector_KQueue_Descriptor_free; int result = IO_Event_Array_initialize(&selector->descriptors, IO_EVENT_ARRAY_DEFAULT_COUNT, sizeof(struct IO_Event_Selector_KQueue_Descriptor)); if (result < 0) { rb_sys_fail("IO_Event_Selector_KQueue_allocate:IO_Event_Array_initialize"); } return instance; } #ifdef IO_EVENT_SELECTOR_KQUEUE_USE_INTERRUPT void IO_Event_Interrupt_add(struct IO_Event_Interrupt *interrupt, struct IO_Event_Selector_KQueue *selector) { int descriptor = IO_Event_Interrupt_descriptor(interrupt); struct kevent kev = { .filter = EVFILT_READ, .ident = descriptor, .flags = EV_ADD | EV_CLEAR, }; int result = kevent(selector->descriptor, &kev, 1, NULL, 0, NULL); if (result == -1) { rb_sys_fail("IO_Event_Interrupt_add:kevent"); } } #endif VALUE IO_Event_Selector_KQueue_initialize(VALUE self, VALUE loop) { struct IO_Event_Selector_KQueue *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); IO_Event_Selector_initialize(&selector->backend, self, loop); int result = kqueue(); if (result == -1) { rb_sys_fail("IO_Event_Selector_KQueue_initialize:kqueue"); } else { // Make sure the descriptor is closed on exec. ioctl(result, FIOCLEX); selector->descriptor = result; rb_update_max_fd(selector->descriptor); } #ifdef IO_EVENT_SELECTOR_KQUEUE_USE_INTERRUPT IO_Event_Interrupt_open(&selector->interrupt); IO_Event_Interrupt_add(&selector->interrupt, selector); #endif return self; } VALUE IO_Event_Selector_KQueue_loop(VALUE self) { struct IO_Event_Selector_KQueue *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); return selector->backend.loop; } VALUE IO_Event_Selector_KQueue_idle_duration(VALUE self) { struct IO_Event_Selector_KQueue *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); double duration = selector->idle_duration.tv_sec + (selector->idle_duration.tv_nsec / 1000000000.0); return DBL2NUM(duration); } VALUE IO_Event_Selector_KQueue_close(VALUE self) { struct IO_Event_Selector_KQueue *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); close_internal(selector); #ifdef IO_EVENT_SELECTOR_KQUEUE_USE_INTERRUPT IO_Event_Interrupt_close(&selector->interrupt); #endif return Qnil; } VALUE IO_Event_Selector_KQueue_transfer(VALUE self) { struct IO_Event_Selector_KQueue *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); return IO_Event_Selector_loop_yield(&selector->backend); } VALUE IO_Event_Selector_KQueue_resume(int argc, VALUE *argv, VALUE self) { struct IO_Event_Selector_KQueue *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); return IO_Event_Selector_resume(&selector->backend, argc, argv); } VALUE IO_Event_Selector_KQueue_yield(VALUE self) { struct IO_Event_Selector_KQueue *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); return IO_Event_Selector_yield(&selector->backend); } VALUE IO_Event_Selector_KQueue_push(VALUE self, VALUE fiber) { struct IO_Event_Selector_KQueue *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); IO_Event_Selector_ready_push(&selector->backend, fiber); return Qnil; } VALUE IO_Event_Selector_KQueue_raise(int argc, VALUE *argv, VALUE self) { struct IO_Event_Selector_KQueue *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); return IO_Event_Selector_raise(&selector->backend, argc, argv); } VALUE IO_Event_Selector_KQueue_ready_p(VALUE self) { struct IO_Event_Selector_KQueue *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); return selector->backend.ready ? Qtrue : Qfalse; } struct process_wait_arguments { struct IO_Event_Selector_KQueue *selector; struct IO_Event_Selector_KQueue_Waiting *waiting; pid_t pid; int flags; }; static void process_prewait(pid_t pid) { #if defined(WNOWAIT) // FreeBSD seems to have an issue where kevent() can return an EVFILT_PROC/NOTE_EXIT event for a process even though a wait with WNOHANG on it immediately after will not return it (but it does after a small delay). Similarly, OpenBSD/NetBSD seem to sometimes fail the kevent() call with ESRCH (indicating the process has already terminated) even though a WNOHANG may not return it immediately after. // To deal with this, do a hanging WNOWAIT wait on the process to make sure it is "terminated enough" for future WNOHANG waits to return it. // Using waitid() for this because OpenBSD only supports WNOWAIT with waitid(). int result; do { siginfo_t info; result = waitid(P_PID, pid, &info, WEXITED | WNOWAIT); // This can sometimes get interrupted by SIGCHLD. } while (result == -1 && errno == EINTR); if (result == -1) { rb_sys_fail("process_prewait:waitid"); } #endif } static VALUE process_wait_transfer(VALUE _arguments) { struct process_wait_arguments *arguments = (struct process_wait_arguments *)_arguments; IO_Event_Selector_loop_yield(&arguments->selector->backend); if (arguments->waiting->ready) { process_prewait(arguments->pid); return IO_Event_Selector_process_status_wait(arguments->pid, arguments->flags); } else { return Qfalse; } } static VALUE process_wait_ensure(VALUE _arguments) { struct process_wait_arguments *arguments = (struct process_wait_arguments *)_arguments; IO_Event_Selector_KQueue_Waiting_cancel(arguments->waiting); return Qnil; } struct IO_Event_List_Type IO_Event_Selector_KQueue_process_wait_list_type = {}; VALUE IO_Event_Selector_KQueue_process_wait(VALUE self, VALUE fiber, VALUE _pid, VALUE _flags) { struct IO_Event_Selector_KQueue *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); pid_t pid = NUM2PIDT(_pid); int flags = NUM2INT(_flags); struct IO_Event_Selector_KQueue_Waiting waiting = { .list = {.type = &IO_Event_Selector_KQueue_process_wait_list_type}, .fiber = fiber, .events = IO_EVENT_EXIT, }; RB_OBJ_WRITTEN(self, Qundef, fiber); struct process_wait_arguments process_wait_arguments = { .selector = selector, .waiting = &waiting, .pid = pid, .flags = flags, }; int result = IO_Event_Selector_KQueue_Waiting_register(selector, pid, &waiting); if (result == -1) { // OpenBSD/NetBSD return ESRCH when attempting to register an EVFILT_PROC event for a zombie process. if (errno == ESRCH) { process_prewait(pid); return IO_Event_Selector_process_status_wait(pid, flags); } rb_sys_fail("IO_Event_Selector_KQueue_process_wait:IO_Event_Selector_KQueue_Waiting_register"); } return rb_ensure(process_wait_transfer, (VALUE)&process_wait_arguments, process_wait_ensure, (VALUE)&process_wait_arguments); } struct io_wait_arguments { struct IO_Event_Selector_KQueue *selector; struct IO_Event_Selector_KQueue_Waiting *waiting; }; static VALUE io_wait_ensure(VALUE _arguments) { struct io_wait_arguments *arguments = (struct io_wait_arguments *)_arguments; IO_Event_Selector_KQueue_Waiting_cancel(arguments->waiting); return Qnil; } static VALUE io_wait_transfer(VALUE _arguments) { struct io_wait_arguments *arguments = (struct io_wait_arguments *)_arguments; IO_Event_Selector_loop_yield(&arguments->selector->backend); if (arguments->waiting->ready) { return RB_INT2NUM(arguments->waiting->ready); } else { return Qfalse; } } struct IO_Event_List_Type IO_Event_Selector_KQueue_io_wait_list_type = {}; VALUE IO_Event_Selector_KQueue_io_wait(VALUE self, VALUE fiber, VALUE io, VALUE events) { struct IO_Event_Selector_KQueue *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); int descriptor = IO_Event_Selector_io_descriptor(io); struct IO_Event_Selector_KQueue_Waiting waiting = { .list = {.type = &IO_Event_Selector_KQueue_io_wait_list_type}, .fiber = fiber, .events = RB_NUM2INT(events), }; RB_OBJ_WRITTEN(self, Qundef, fiber); int result = IO_Event_Selector_KQueue_Waiting_register(selector, descriptor, &waiting); if (result == -1) { rb_sys_fail("IO_Event_Selector_KQueue_io_wait:IO_Event_Selector_KQueue_Waiting_register"); } struct io_wait_arguments io_wait_arguments = { .selector = selector, .waiting = &waiting, }; if (DEBUG_IO_WAIT) fprintf(stderr, "IO_Event_Selector_KQueue_io_wait descriptor=%d\n", descriptor); return rb_ensure(io_wait_transfer, (VALUE)&io_wait_arguments, io_wait_ensure, (VALUE)&io_wait_arguments); } #ifdef HAVE_RUBY_IO_BUFFER_H struct io_read_arguments { VALUE self; VALUE fiber; VALUE io; int flags; int descriptor; VALUE buffer; size_t length; size_t offset; }; static VALUE io_read_loop(VALUE _arguments) { struct io_read_arguments *arguments = (struct io_read_arguments *)_arguments; void *base; size_t size; rb_io_buffer_get_bytes_for_writing(arguments->buffer, &base, &size); size_t length = arguments->length; size_t offset = arguments->offset; size_t total = 0; if (DEBUG_IO_READ) fprintf(stderr, "io_read_loop(fd=%d, length=%zu)\n", arguments->descriptor, length); size_t maximum_size = size - offset; while (maximum_size) { if (DEBUG_IO_READ) fprintf(stderr, "read(%d, +%ld, %ld)\n", arguments->descriptor, offset, maximum_size); ssize_t result = read(arguments->descriptor, (char*)base+offset, maximum_size); if (DEBUG_IO_READ) fprintf(stderr, "read(%d, +%ld, %ld) -> %zd\n", arguments->descriptor, offset, maximum_size, result); if (result > 0) { total += result; offset += result; if ((size_t)result >= length) break; length -= result; } else if (result == 0) { break; } else if (length > 0 && IO_Event_try_again(errno)) { if (DEBUG_IO_READ) fprintf(stderr, "IO_Event_Selector_KQueue_io_wait(fd=%d, length=%zu)\n", arguments->descriptor, length); IO_Event_Selector_KQueue_io_wait(arguments->self, arguments->fiber, arguments->io, RB_INT2NUM(IO_EVENT_READABLE)); } else { if (DEBUG_IO_READ) fprintf(stderr, "io_read_loop(fd=%d, length=%zu) -> errno=%d\n", arguments->descriptor, length, errno); return rb_fiber_scheduler_io_result(-1, errno); } maximum_size = size - offset; } if (DEBUG_IO_READ) fprintf(stderr, "io_read_loop(fd=%d, length=%zu) -> %zu\n", arguments->descriptor, length, offset); return rb_fiber_scheduler_io_result(total, 0); } static VALUE io_read_ensure(VALUE _arguments) { struct io_read_arguments *arguments = (struct io_read_arguments *)_arguments; IO_Event_Selector_nonblock_restore(arguments->descriptor, arguments->flags); return Qnil; } VALUE IO_Event_Selector_KQueue_io_read(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _length, VALUE _offset) { struct IO_Event_Selector_KQueue *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); int descriptor = IO_Event_Selector_io_descriptor(io); size_t length = NUM2SIZET(_length); size_t offset = NUM2SIZET(_offset); struct io_read_arguments io_read_arguments = { .self = self, .fiber = fiber, .io = io, .flags = IO_Event_Selector_nonblock_set(descriptor), .descriptor = descriptor, .buffer = buffer, .length = length, .offset = offset, }; RB_OBJ_WRITTEN(self, Qundef, fiber); return rb_ensure(io_read_loop, (VALUE)&io_read_arguments, io_read_ensure, (VALUE)&io_read_arguments); } static VALUE IO_Event_Selector_KQueue_io_read_compatible(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 4, 5); VALUE _offset = SIZET2NUM(0); if (argc == 5) { _offset = argv[4]; } return IO_Event_Selector_KQueue_io_read(self, argv[0], argv[1], argv[2], argv[3], _offset); } struct io_write_arguments { VALUE self; VALUE fiber; VALUE io; int flags; int descriptor; VALUE buffer; size_t length; size_t offset; }; static VALUE io_write_loop(VALUE _arguments) { struct io_write_arguments *arguments = (struct io_write_arguments *)_arguments; const void *base; size_t size; rb_io_buffer_get_bytes_for_reading(arguments->buffer, &base, &size); size_t length = arguments->length; size_t offset = arguments->offset; size_t total = 0; if (length > size) { rb_raise(rb_eRuntimeError, "Length exceeds size of buffer!"); } if (DEBUG_IO_WRITE) fprintf(stderr, "io_write_loop(fd=%d, length=%zu)\n", arguments->descriptor, length); size_t maximum_size = size - offset; while (maximum_size) { if (DEBUG_IO_WRITE) fprintf(stderr, "write(%d, +%ld, %ld, length=%zu)\n", arguments->descriptor, offset, maximum_size, length); ssize_t result = write(arguments->descriptor, (char*)base+offset, maximum_size); if (DEBUG_IO_WRITE) fprintf(stderr, "write(%d, +%ld, %ld) -> %zd\n", arguments->descriptor, offset, maximum_size, result); if (result > 0) { total += result; offset += result; if ((size_t)result >= length) break; length -= result; } else if (result == 0) { break; } else if (length > 0 && IO_Event_try_again(errno)) { if (DEBUG_IO_WRITE) fprintf(stderr, "IO_Event_Selector_KQueue_io_wait(fd=%d, length=%zu)\n", arguments->descriptor, length); IO_Event_Selector_KQueue_io_wait(arguments->self, arguments->fiber, arguments->io, RB_INT2NUM(IO_EVENT_READABLE)); } else { if (DEBUG_IO_WRITE) fprintf(stderr, "io_write_loop(fd=%d, length=%zu) -> errno=%d\n", arguments->descriptor, length, errno); return rb_fiber_scheduler_io_result(-1, errno); } maximum_size = size - offset; } if (DEBUG_IO_READ) fprintf(stderr, "io_write_loop(fd=%d, length=%zu) -> %zu\n", arguments->descriptor, length, offset); return rb_fiber_scheduler_io_result(total, 0); }; static VALUE io_write_ensure(VALUE _arguments) { struct io_write_arguments *arguments = (struct io_write_arguments *)_arguments; IO_Event_Selector_nonblock_restore(arguments->descriptor, arguments->flags); return Qnil; }; VALUE IO_Event_Selector_KQueue_io_write(VALUE self, VALUE fiber, VALUE io, VALUE buffer, VALUE _length, VALUE _offset) { struct IO_Event_Selector_KQueue *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); int descriptor = IO_Event_Selector_io_descriptor(io); size_t length = NUM2SIZET(_length); size_t offset = NUM2SIZET(_offset); struct io_write_arguments io_write_arguments = { .self = self, .fiber = fiber, .io = io, .flags = IO_Event_Selector_nonblock_set(descriptor), .descriptor = descriptor, .buffer = buffer, .length = length, .offset = offset, }; RB_OBJ_WRITTEN(self, Qundef, fiber); return rb_ensure(io_write_loop, (VALUE)&io_write_arguments, io_write_ensure, (VALUE)&io_write_arguments); } static VALUE IO_Event_Selector_KQueue_io_write_compatible(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 4, 5); VALUE _offset = SIZET2NUM(0); if (argc == 5) { _offset = argv[4]; } return IO_Event_Selector_KQueue_io_write(self, argv[0], argv[1], argv[2], argv[3], _offset); } #endif static struct timespec * make_timeout(VALUE duration, struct timespec * storage) { if (duration == Qnil) { return NULL; } if (RB_INTEGER_TYPE_P(duration)) { storage->tv_sec = NUM2TIMET(duration); storage->tv_nsec = 0; return storage; } duration = rb_to_float(duration); double value = RFLOAT_VALUE(duration); time_t seconds = value; storage->tv_sec = seconds; storage->tv_nsec = (value - seconds) * 1000000000L; return storage; } static int timeout_nonblocking(struct timespec * timespec) { return timespec && timespec->tv_sec == 0 && timespec->tv_nsec == 0; } struct select_arguments { struct IO_Event_Selector_KQueue *selector; int count; struct kevent events[KQUEUE_MAX_EVENTS]; struct timespec storage; struct timespec *timeout; struct IO_Event_List saved; }; static void * select_internal(void *_arguments) { struct select_arguments * arguments = (struct select_arguments *)_arguments; arguments->count = kevent(arguments->selector->descriptor, NULL, 0, arguments->events, arguments->count, arguments->timeout); return NULL; } static void select_internal_without_gvl(struct select_arguments *arguments) { arguments->selector->blocked = 1; rb_thread_call_without_gvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0); arguments->selector->blocked = 0; if (arguments->count == -1) { if (errno != EINTR) { rb_sys_fail("select_internal_without_gvl:kevent"); } else { arguments->count = 0; } } } static void select_internal_with_gvl(struct select_arguments *arguments) { select_internal((void *)arguments); if (arguments->count == -1) { if (errno != EINTR) { rb_sys_fail("select_internal_with_gvl:kevent"); } else { arguments->count = 0; } } } static int IO_Event_Selector_KQueue_handle(struct IO_Event_Selector_KQueue *selector, uintptr_t identifier, struct IO_Event_Selector_KQueue_Descriptor *kqueue_descriptor, struct IO_Event_List *saved) { // This is the mask of all events that occured for the given descriptor: enum IO_Event ready_events = kqueue_descriptor->ready_events; if (ready_events) { kqueue_descriptor->ready_events = 0; // Since we use one-shot semantics, we need to re-arm the events that are ready if needed: kqueue_descriptor->registered_events &= ~ready_events; } else { return 0; } struct IO_Event_List *list = &kqueue_descriptor->list; struct IO_Event_List *node = list->tail; // Reset the events back to 0 so that we can re-arm if necessary: kqueue_descriptor->waiting_events = 0; // It's possible (but unlikely) that the address of list will changing during iteration. while (node != list) { struct IO_Event_Selector_KQueue_Waiting *waiting = (struct IO_Event_Selector_KQueue_Waiting *)node; enum IO_Event matching_events = waiting->events & ready_events; if (DEBUG) fprintf(stderr, "IO_Event_Selector_KQueue_handle: identifier=%lu, ready_events=%d, matching_events=%d\n", identifier, ready_events, matching_events); if (matching_events) { IO_Event_List_append(node, saved); waiting->ready = matching_events; IO_Event_Selector_loop_resume(&selector->backend, waiting->fiber, 0, NULL); node = saved->tail; IO_Event_List_pop(saved); } else { kqueue_descriptor->waiting_events |= waiting->events; node = node->tail; } } return IO_Event_Selector_KQueue_Descriptor_update(selector, identifier, kqueue_descriptor); } static VALUE select_handle_events(VALUE _arguments) { struct select_arguments *arguments = (struct select_arguments *)_arguments; struct IO_Event_Selector_KQueue *selector = arguments->selector; for (int i = 0; i < arguments->count; i += 1) { if (arguments->events[i].udata) { struct IO_Event_Selector_KQueue_Descriptor *kqueue_descriptor = arguments->events[i].udata; kqueue_descriptor->ready_events |= events_from_kevent_filter(arguments->events[i].filter); } } for (int i = 0; i < arguments->count; i += 1) { if (arguments->events[i].udata) { struct IO_Event_Selector_KQueue_Descriptor *kqueue_descriptor = arguments->events[i].udata; IO_Event_Selector_KQueue_handle(selector, arguments->events[i].ident, kqueue_descriptor, &arguments->saved); } else { #ifdef IO_EVENT_SELECTOR_KQUEUE_USE_INTERRUPT IO_Event_Interrupt_clear(&selector->interrupt); #endif } } return RB_INT2NUM(arguments->count); } static VALUE select_handle_events_ensure(VALUE _arguments) { struct select_arguments *arguments = (struct select_arguments *)_arguments; IO_Event_List_free(&arguments->saved); return Qnil; } VALUE IO_Event_Selector_KQueue_select(VALUE self, VALUE duration) { struct IO_Event_Selector_KQueue *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); selector->idle_duration.tv_sec = 0; selector->idle_duration.tv_nsec = 0; int ready = IO_Event_Selector_ready_flush(&selector->backend); struct select_arguments arguments = { .selector = selector, .count = KQUEUE_MAX_EVENTS, .storage = { .tv_sec = 0, .tv_nsec = 0 }, .saved = {}, }; arguments.timeout = &arguments.storage; // We break this implementation into two parts. // (1) count = kevent(..., timeout = 0) // (2) without gvl: kevent(..., timeout = 0) if count == 0 and timeout != 0 // This allows us to avoid releasing and reacquiring the GVL. // Non-comprehensive testing shows this gives a 1.5x speedup. // First do the syscall with no timeout to get any immediately available events: if (DEBUG) fprintf(stderr, "\r\nselect_internal_with_gvl timeout=" IO_EVENT_TIME_PRINTF_TIMESPEC "\r\n", IO_EVENT_TIME_PRINTF_TIMESPEC_ARGUMENTS(arguments.storage)); select_internal_with_gvl(&arguments); if (DEBUG) fprintf(stderr, "\r\nselect_internal_with_gvl done\r\n"); // If we: // 1. Didn't process any ready fibers, and // 2. Didn't process any events from non-blocking select (above), and // 3. There are no items in the ready list, // then we can perform a blocking select. if (!ready && !arguments.count && !selector->backend.ready) { arguments.timeout = make_timeout(duration, &arguments.storage); if (!timeout_nonblocking(arguments.timeout)) { arguments.count = KQUEUE_MAX_EVENTS; struct timespec start_time; IO_Event_Time_current(&start_time); if (DEBUG) fprintf(stderr, "IO_Event_Selector_KQueue_select timeout=" IO_EVENT_TIME_PRINTF_TIMESPEC "\n", IO_EVENT_TIME_PRINTF_TIMESPEC_ARGUMENTS(arguments.storage)); select_internal_without_gvl(&arguments); struct timespec end_time; IO_Event_Time_current(&end_time); IO_Event_Time_elapsed(&start_time, &end_time, &selector->idle_duration); } } if (arguments.count) { return rb_ensure(select_handle_events, (VALUE)&arguments, select_handle_events_ensure, (VALUE)&arguments); } else { return RB_INT2NUM(0); } } VALUE IO_Event_Selector_KQueue_wakeup(VALUE self) { struct IO_Event_Selector_KQueue *selector = NULL; TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector); if (selector->blocked) { #ifdef IO_EVENT_SELECTOR_KQUEUE_USE_INTERRUPT IO_Event_Interrupt_signal(&selector->interrupt); #else struct kevent trigger = {0}; trigger.filter = EVFILT_USER; trigger.flags = EV_ADD | EV_CLEAR; int result = kevent(selector->descriptor, &trigger, 1, NULL, 0, NULL); if (result == -1) { rb_sys_fail("IO_Event_Selector_KQueue_wakeup:kevent"); } // FreeBSD apparently only works if the NOTE_TRIGGER is done as a separate call. trigger.flags = 0; trigger.fflags = NOTE_TRIGGER; result = kevent(selector->descriptor, &trigger, 1, NULL, 0, NULL); if (result == -1) { rb_sys_fail("IO_Event_Selector_KQueue_wakeup:kevent"); } #endif return Qtrue; } return Qfalse; } void Init_IO_Event_Selector_KQueue(VALUE IO_Event_Selector) { VALUE IO_Event_Selector_KQueue = rb_define_class_under(IO_Event_Selector, "KQueue", rb_cObject); rb_define_alloc_func(IO_Event_Selector_KQueue, IO_Event_Selector_KQueue_allocate); rb_define_method(IO_Event_Selector_KQueue, "initialize", IO_Event_Selector_KQueue_initialize, 1); rb_define_method(IO_Event_Selector_KQueue, "loop", IO_Event_Selector_KQueue_loop, 0); rb_define_method(IO_Event_Selector_KQueue, "idle_duration", IO_Event_Selector_KQueue_idle_duration, 0); rb_define_method(IO_Event_Selector_KQueue, "transfer", IO_Event_Selector_KQueue_transfer, 0); rb_define_method(IO_Event_Selector_KQueue, "resume", IO_Event_Selector_KQueue_resume, -1); rb_define_method(IO_Event_Selector_KQueue, "yield", IO_Event_Selector_KQueue_yield, 0); rb_define_method(IO_Event_Selector_KQueue, "push", IO_Event_Selector_KQueue_push, 1); rb_define_method(IO_Event_Selector_KQueue, "raise", IO_Event_Selector_KQueue_raise, -1); rb_define_method(IO_Event_Selector_KQueue, "ready?", IO_Event_Selector_KQueue_ready_p, 0); rb_define_method(IO_Event_Selector_KQueue, "select", IO_Event_Selector_KQueue_select, 1); rb_define_method(IO_Event_Selector_KQueue, "wakeup", IO_Event_Selector_KQueue_wakeup, 0); rb_define_method(IO_Event_Selector_KQueue, "close", IO_Event_Selector_KQueue_close, 0); rb_define_method(IO_Event_Selector_KQueue, "io_wait", IO_Event_Selector_KQueue_io_wait, 3); #ifdef HAVE_RUBY_IO_BUFFER_H rb_define_method(IO_Event_Selector_KQueue, "io_read", IO_Event_Selector_KQueue_io_read_compatible, -1); rb_define_method(IO_Event_Selector_KQueue, "io_write", IO_Event_Selector_KQueue_io_write_compatible, -1); #endif rb_define_method(IO_Event_Selector_KQueue, "process_wait", IO_Event_Selector_KQueue_process_wait, 3); }