diff --git a/ext/io/event/selector/epoll.c b/ext/io/event/selector/epoll.c index 0f50757..9bc687e 100644 --- a/ext/io/event/selector/epoll.c +++ b/ext/io/event/selector/epoll.c @@ -870,6 +870,8 @@ void * select_internal(void *_arguments) { static void select_internal_without_gvl(struct select_arguments *arguments) { + IO_Event_Selector_before_waiting(arguments->selector); + arguments->selector->blocked = 1; rb_thread_call_without_gvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0); arguments->selector->blocked = 0; diff --git a/ext/io/event/selector/kqueue.c b/ext/io/event/selector/kqueue.c index 9cab2c2..9318f49 100644 --- a/ext/io/event/selector/kqueue.c +++ b/ext/io/event/selector/kqueue.c @@ -842,6 +842,8 @@ void * select_internal(void *_arguments) { static void select_internal_without_gvl(struct select_arguments *arguments) { + IO_Event_Selector_before_waiting(arguments->selector); + arguments->selector->blocked = 1; rb_thread_call_without_gvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0); diff --git a/ext/io/event/selector/selector.c b/ext/io/event/selector/selector.c index c012421..f3b9e99 100644 --- a/ext/io/event/selector/selector.c +++ b/ext/io/event/selector/selector.c @@ -78,6 +78,8 @@ static VALUE IO_Event_Selector_nonblock(VALUE class, VALUE io) return rb_ensure(rb_yield, io, IO_Event_Selector_nonblock_ensure, (VALUE)&arguments); } +static float IO_Event_Selector_GARBAGE_COLLECTION = -1; + void Init_IO_Event_Selector(VALUE IO_Event_Selector) { #ifndef HAVE_RB_IO_DESCRIPTOR id_fileno = rb_intern("fileno"); @@ -89,6 +91,11 @@ void Init_IO_Event_Selector(VALUE IO_Event_Selector) { rb_gc_register_mark_object(rb_Process_Status); #endif + char *garbage_collection_env = getenv("IO_EVENT_SELECTOR_GARBAGE_COLLECTION"); + if (garbage_collection_env) { + IO_Event_Selector_GARBAGE_COLLECTION = atof(garbage_collection_env); + } + rb_define_singleton_method(IO_Event_Selector, "nonblock", IO_Event_Selector_nonblock, 1); } @@ -98,6 +105,9 @@ void IO_Event_Selector_initialize(struct IO_Event_Selector *backend, VALUE self, backend->waiting = NULL; backend->ready = NULL; + + backend->garbage_collection = IO_Event_Selector_GARBAGE_COLLECTION; + backend->last_garbage_collection = (struct timespec){0, 0}; } VALUE IO_Event_Selector_loop_resume(struct IO_Event_Selector *backend, VALUE fiber, int argc, VALUE *argv) { diff --git a/ext/io/event/selector/selector.h b/ext/io/event/selector/selector.h index 137616f..4b0f377 100644 --- a/ext/io/event/selector/selector.h +++ b/ext/io/event/selector/selector.h @@ -81,10 +81,27 @@ struct IO_Event_Selector { struct IO_Event_Selector_Queue *waiting; // Process from ready (back/tail of queue). struct IO_Event_Selector_Queue *ready; + + // Minimum interval in seconds between out-of-band garbage collections, or < 0 to disable: + float garbage_collection; + struct timespec last_garbage_collection; }; void IO_Event_Selector_initialize(struct IO_Event_Selector *backend, VALUE self, VALUE loop); +static inline IO_Event_Selector_before_waiting(struct IO_Event_Selector *backend) { + // Experimental support for out-of-band garbage collection. This allows the selector to perform garbage collection at regular intervals while waiting for events, which can help reduce overall latency. + if (backend->garbage_collection >= 0) { + struct timespec now; + IO_Event_Time_current(&now); + + if (IO_Event_Time_delta(&backend->last_garbage_collection, &now) >= backend->garbage_collection) { + rb_gc(); + backend->last_garbage_collection = now; + } + } +} + static inline void IO_Event_Selector_mark(struct IO_Event_Selector *backend) { rb_gc_mark_movable(backend->self); diff --git a/ext/io/event/selector/uring.c b/ext/io/event/selector/uring.c index ae4d1fc..f7eebba 100644 --- a/ext/io/event/selector/uring.c +++ b/ext/io/event/selector/uring.c @@ -1053,6 +1053,8 @@ static int select_internal_without_gvl(struct select_arguments *arguments) { io_uring_submit_flush(arguments->selector); + IO_Event_Selector_before_waiting(arguments->selector); + arguments->selector->blocked = 1; rb_thread_call_without_gvl(select_internal, (void *)arguments, RUBY_UBF_IO, 0); arguments->selector->blocked = 0; diff --git a/releases.md b/releases.md index 394cb90..06b398b 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,9 @@ # Releases +## Unreleased + + - **Experimental** Add support for out-of-band garbage collection. When enabled via the `IO_EVENT_SELECTOR_GARBAGE_COLLECTION` environment variable, the selector will run garbage collection at a minimum interval (in seconds) while the event loop is blocking, reducing GC pressure during active I/O. A negative value (default) disables the feature; `0` runs GC on every block. + ## v1.14.4 - Allow `epoll_pwait2` to be disabled via `--disable-epoll_pwait2`.