Files
news.gitea.happylizard.me/bin/gems/io-event-1.9.0/ext/io/event/selector/kqueue.c
T
pizza2d1 4cee170d66
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s
Add bin and edit workflow
2026-09-16 13:11:16 -06:00

1078 lines
32 KiB
C

// Released under the MIT License.
// Copyright, 2021-2025, by Samuel Williams.
#include "kqueue.h"
#include "selector.h"
#include "../list.h"
#include "../array.h"
#include <sys/event.h>
#include <sys/ioctl.h>
#include <time.h>
#include <errno.h>
#include <sys/wait.h>
#include <signal.h>
#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);
}