Releases
Unreleased
- Add
IO::Event::Futexon Linux with Ruby 4.1+, including atomic value operations and blocking and scheduler-aware single and vector waits over shared memory. Futexes retain a counted allocation lock until explicitly closed or finalized; pending waits prevent closing until completion or cancellation has finished.
v1.22.1
- Fix an infinite loop in the ready queue flush when a queued fiber is resumed out of band, e.g. by a stale
unblockracing a timeout, while another fiber re-queues itself on every iteration. - Preserve each ready queue flush's boundary with its own placeholder so newly queued fibers are deferred even when earlier entries are removed. Nested native flushes skip other placeholders, processing work queued before their own boundary. Native selectors ignore placeholders when checking readiness and remove them if a fiber raises.
v1.22.0
- Reject non-finite timer times (
NaNand positive or negative infinity), preventing invalid heap ordering and permanently retained timer handles.
v1.21.1
- Fix the
URingcompletion free-list empty check so its sole entry can be reused instead of unnecessarily allocating a new completion. - Keep cancelled
URingcompletion records allocated until both the operation and cancellation completions have been processed.
v1.20.0
- Add compatibility with Ruby 4.1's fiber scheduler interface version 4. Buffered IO operations now use
(offset, length), perform a single transfer of at mostlengthbytes, return short transfers directly, and report-EAGAINwithout waiting. Earlier Ruby versions retain the existing minimum-progress behavior.
v1.19.5
- Preserve the original exception or non-local control flow when
IO::Event::WorkerPoolcancellation interrupts a blocked fiber, while still cancelling and draining the in-flight blocking operation before returning control to Ruby.
v1.19.4
- Capture
errnoimmediately afterepoll_wait/kevent, preventing stale or subsequently clobbered values from raising a spuriousErrno::*when a native selector wait is interrupted or skipped.
v1.19.3
- Prevent
URing,EPoll, andKQueuefrom entering a native wait when a signal exception becomes pending during the GVL transition. On Ruby versions withoutRB_NOGVL_PENDING_INTR_FAIL, the fallback now refreshes pending-interrupt state immediately before releasing the GVL while preserving the caller's exception-delivery timing.
v1.19.2
- Use
rb_process_status_forwhen available to constructURingprocess_waitresults directly fromwaitid, avoiding the extra reap syscall previously needed to build aProcess::Status.
v1.19.1
- Fix
Process.waitall/Process.detachunder theURingselector: whenio_uring'swaitidreported an error (e.g.ECHILDwhen there are no more children), theprocess_waithook raised instead of returning the error as aProcess::Status, so callers that expectwaitpidto report "no more children" rather than raise would fail.
v1.19.0
- Use
io_uring_prep_waitidforprocess_waitin theURingselector (Linux 6.7+), waiting for child exit directly in the ring instead of polling on apidfd. The child is reaped viarb_process_status_wait(usingWEXITED | WNOWAIT) to construct a correctProcess::Status, andprocess_wait(-1, ...)/process_wait(0, ...)are now supported. - Support waiting for any child or a process group (
pid <= 0) on all selectors. TheEPoll(pidfd_open) andKQueue(EVFILT_PROC) selectors can only watch a specific process, so these cases now fall back to a blocking wait on a dedicated thread; joining it is fiber-scheduler aware, so the reactor keeps running.
v1.18.0
- Fixed: Avoid entering a blocking native selector wait when an interrupt is already pending for the current thread.
v1.17.0
- Report inherited selector objects as closed after fork, and avoid closing descriptors they no longer own.
v1.16.4
- Correctly implement
Interrupt#signalso that it is robust enough to be called byScheduler#unblock.
v1.16.3
- Handle
IOErrorraised while shutting down the pure Ruby interrupt pipe, soIO::Event::Interrupt#closedoes not leak expected shutdown errors from the interrupt fiber.
v1.16.2
- Improve timer heap performance by batching scheduled timer insertion, compacting cancelled timers during flush, and avoiding unnecessary heap rebuilds for small incremental inserts.
v1.16.1
- Ensure the pure Ruby
Selectselector returnsfalse, notnil, whenio_waitresumes without any ready events.
v1.16.0
- Use
eventfdforURingcross-thread wakeup, and enableIORING_SETUP_SINGLE_ISSUER,IORING_SETUP_DEFER_TASKRUN, andIORING_SETUP_TASKRUN_FLAG. The waking thread now signals viaeventfdrather than submitting aNOPSQE, which unlocks the single-issuer optimisation, defers task work to the application thread, and letsselect()skip theio_uring_get_events()syscall when no task work is pending. - Add support for the
io_closefiber-scheduler hook (Ruby 4.0+). TheURingselector performs the close asynchronously via the ring; theDebug::SelectorandTestSchedulerwrappers forward to the underlying selector when supported. - Improve
WorkerPoolGC compaction support and add proper write barriers, fixing potential use-after-free under compacting GC. - Keep blocked scheduler fibers alive during GC by registering them as roots in
TestScheduler#block, preventing premature collection and the resulting use-after-free crash on resume. - Use Ruby's
xmalloc/xcalloc/xrealloc2/xfreefor all internal selector allocations (the per-fiber ready-queue entries inIO_Event_Selector_ready_push, and both the backing array and per-element allocations inIO_Event_Array). Previously a rawmallocpaired with a debug-build-onlyassert(...)would silently dereferenceNULLand crash in release builds under memory pressure; the Ruby allocators trigger a GC sweep on pressure and raiseNoMemoryError/RangeErroron real failure, so the-1return-code paths throughIO_Event_Array_initialize/_resize/_lookupand their callers inepoll.c/kqueue.c/uring.care removed in favour of straight exception propagation. - Correctly handle short
io_uring_submit()results in theURingselector.io_uring_submit()returns the number of SQEs actually accepted by the kernel and can be short (SQE prep errors,ENOMEM, transientEAGAIN); the old accounting resetpending = 0on any success and silently lost track of unsubmitted SQEs. - Enable
IORING_SETUP_SUBMIT_ALL(kernel 5.18+) on theURingselector so the kernel keeps processing the rest of an SQE batch past individual errors, reducing the frequency of short submits in practice.
v1.15.1
- Simplify closed-IO handling in the
Selectselector: rely on Ruby 4'srb_thread_io_close_interruptto wake fibers waiting on a descriptor that's been closed, removing a custom error-recovery path that could mis-attributeIOError/Errno::EBADFto the wrong waiter.
v1.15.0
- Add bounds checks, in the unlikely event of a user providing an invalid offset that exceeds the buffer size. This prevents potential memory corruption and ensures safe operation when using buffered IO methods.
v1.14.4
- Allow
epoll_pwait2to be disabled via--disable-epoll_pwait2.
v1.14.3
- Fix several implementation bugs that could cause deadlocks on blocking writes.
v1.14.0
Enhanced IO::Event::PriorityHeap with deletion and bulk insertion methods
The class IO::Event::PriorityHeap now supports efficient element removal and bulk insertion:
delete(element): Remove a specific element from the heap in O(n) timedelete_if(&block): Remove elements matching a condition with O(n) amortized bulk deletionconcat(elements): Add multiple elements efficiently in O(n) time
heap = IO::Event::PriorityHeap.new
# Efficient bulk insertion - O(n) instead of O(n log n)
heap.concat([5, 2, 8, 1, 9, 3])
# Remove specific element
removed = heap.delete(5) # Returns 5, heap maintains order
# Bulk removal with condition
count = heap.delete_if{|x| x.even?} # Removes 2, 8 efficiently
The delete_if and concat methods are particularly efficient for bulk operations, using bottom-up heapification to maintain the heap property in O(n) time. This provides significant performance improvements:
- Bulk insertion: O(n log n) → O(n) for adding multiple elements
- Bulk deletion: O(k×n) → O(n) for removing k elements
Both methods maintain the heap invariant and include comprehensive test coverage with edge case validation.
v1.11.2
- Fix Windows build.
v1.11.1
- Fix
read_nonblockwhen using theURingselector, which was not handling zero-length reads correctly. This allows reading available data without blocking.
v1.11.0
Introduce IO::Event::WorkerPool for off-loading blocking operations.
The IO::Event::WorkerPool provides a mechanism for executing blocking operations on separate OS threads while properly integrating with Ruby's fiber scheduler and GVL (Global VM Lock) management. This enables true parallelism for CPU-intensive or blocking operations that would otherwise block the event loop.
# Fiber scheduler integration via blocking_operation_wait hook
class MyScheduler
def initialize
@worker_pool = IO::Event::WorkerPool.new
end
def blocking_operation_wait(operation)
@worker_pool.call(operation)
end
end
# Usage with automatic offloading
Fiber.set_scheduler(MyScheduler.new)
# Automatically offload `rb_nogvl(..., RB_NOGVL_OFFLOAD_SAFE)` to a background thread:
result = some_blocking_operation()
The implementation uses one or more background threads and a list of pending blocking operations. Those operations either execute through to completion or may be cancelled, which executes the "unblock function" provided to rb_nogvl.
v1.10.2
- Improved consistency of handling closed IO when invoking
#select.
v1.10.0
IO::Event::Profileris moved to dedicated gem: fiber-profiler.- Perform runtime checks for native selectors to ensure they are supported in the current environment. While compile-time checks determine availability, restrictions like seccomp and SELinux may still prevent them from working.
v1.9.0
- Improved
IO::Event::Profilerfor detecting stalls.
v1.8.0
- Detecting fibers that are stalling the event loop.
v1.7.5
- Fix
process_waitrace condition on EPoll that could cause a hang.