Class: CarbonFiber::Async::Selector
- Inherits:
-
Native::Selector
- Object
- Native::Selector
- CarbonFiber::Async::Selector
- Defined in:
- lib/carbon_fiber/async.rb
Overview
IO::Event::Selector-compatible adapter backed by our native Zig selector. Subclasses Native::Selector so hot-path methods (transfer, yield, wakeup) dispatch directly to native code without an extra Ruby method frame.
Registration:
require "async"
require "carbon_fiber/async"
CarbonFiber::Async.default!
Or via environment:
IO_EVENT_SELECTOR=CarbonFiberSelector ruby app.rb
Constant Summary collapse
- EAGAIN =
Native Zig io_read/io_write use recv/send with kernel buffer draining. Falls back to Ruby-level nonblock+io_wait for non-socket fds (pipes, files) where native returns nil.
-Errno::EAGAIN::Errno
- EWOULDBLOCK =
-Errno::EWOULDBLOCK::Errno
Instance Attribute Summary collapse
-
#idle_duration ⇒ Float
readonly
Seconds spent idle in the last #select call.
-
#loop ⇒ Fiber
readonly
The event loop fiber.
Instance Method Summary collapse
-
#close ⇒ Object
Release native resources.
-
#initialize(loop, io_contract_v4: CarbonFiber.io_contract_v4?) ⇒ Selector
constructor
A new instance of Selector.
-
#io_close(descriptor) ⇒ Object
Cancel pending waiters on the descriptor.
-
#io_read_v3(fiber, io, buffer, length, offset = 0) ⇒ Integer
(also: #io_read)
Legacy contract (Ruby 3.4 through 4.0): length before offset, minimum-progress semantics.
-
#io_read_v4(fiber, io, buffer, offset, length) ⇒ Integer
Ruby 4.1+ contract: offset before length, single transfer.
-
#io_wait(fiber, io, events) ⇒ Integer, false
Wait for I/O readiness.
-
#io_write_v3(fiber, io, buffer, length, offset = 0) ⇒ Integer
(also: #io_write)
Legacy contract (Ruby 3.4 through 4.0): length before offset, minimum-progress semantics.
-
#io_write_v4(fiber, io, buffer, offset, length) ⇒ Integer
Ruby 4.1+ contract: offset before length, single transfer.
-
#process_wait(fiber, pid, flags) ⇒ Process::Status
Wait for a child process on a background thread.
-
#push(fiber) ⇒ Object
Enqueue a fiber or fiber-like object into the ready queue.
-
#raise(fiber, *arguments, **options) ⇒ Object
Re-enqueue the current fiber and raise on
fiber. -
#ready? ⇒ Boolean
Whether there is pending work.
-
#resume(fiber, *arguments) ⇒ Object
Re-enqueue the current fiber and transfer to
fiberwith arguments. -
#select(duration = nil) ⇒ Object
Run one event loop iteration, draining the auxiliary queue before and after the native select.
Constructor Details
#initialize(loop, io_contract_v4: CarbonFiber.io_contract_v4?) ⇒ Selector
Returns a new instance of Selector.
33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/carbon_fiber/async.rb', line 33 def initialize(loop, io_contract_v4: CarbonFiber.io_contract_v4?) super(loop) self.io_contract_v4 = io_contract_v4 @loop = loop @idle_duration = 0.0 # Auxiliary ready queue for non-Fiber pushables (e.g. FiberInterrupt) # and cross-thread pushes of non-Fiber objects. Thread-safe via mutex. @auxiliary = [] @auxiliary_mutex = Mutex.new end |
Instance Attribute Details
#idle_duration ⇒ Float (readonly)
Returns seconds spent idle in the last #select call.
24 25 26 |
# File 'lib/carbon_fiber/async.rb', line 24 def idle_duration @idle_duration end |
#loop ⇒ Fiber (readonly)
Returns the event loop fiber.
27 28 29 |
# File 'lib/carbon_fiber/async.rb', line 27 def loop @loop end |
Instance Method Details
#close ⇒ Object
Release native resources.
50 51 52 |
# File 'lib/carbon_fiber/async.rb', line 50 def close destroy end |
#io_close(descriptor) ⇒ Object
Cancel pending waiters on the descriptor.
201 202 203 |
# File 'lib/carbon_fiber/async.rb', line 201 def io_close(descriptor) super(descriptor, IOError.new("stream closed while waiting")) end |
#io_read_v3(fiber, io, buffer, length, offset = 0) ⇒ Integer Also known as: io_read
Legacy contract (Ruby 3.4 through 4.0): length before offset, minimum-progress semantics.
133 134 135 136 137 138 |
# File 'lib/carbon_fiber/async.rb', line 133 def io_read_v3(fiber, io, buffer, length, offset = 0) result = native_io_read(io.fileno, buffer, length, offset) return result unless result.nil? ruby_io_read(fiber, io, buffer, length, offset) end |
#io_read_v4(fiber, io, buffer, offset, length) ⇒ Integer
Ruby 4.1+ contract: offset before length, single transfer. One nonblocking attempt; short results and -EAGAIN are returned directly, and the caller composes retries via io_wait.
149 150 151 152 153 154 155 156 |
# File 'lib/carbon_fiber/async.rb', line 149 def io_read_v4(fiber, io, buffer, offset, length) return 0 if length.zero? result = native_io_read(io.fileno, buffer, length, offset) return result unless result.nil? ruby_io_read_v4(io, buffer, offset, length) end |
#io_wait(fiber, io, events) ⇒ Integer, false
Wait for I/O readiness. Falls back to IO.select on a background thread when the native path returns nil (kqueue WRITE bypass, closed fd, duplicate waiter).
111 112 113 114 115 116 |
# File 'lib/carbon_fiber/async.rb', line 111 def io_wait(fiber, io, events) result = native_io_wait(fiber, io.fileno, events) return result unless result.nil? fallback_io_wait(io, events) end |
#io_write_v3(fiber, io, buffer, length, offset = 0) ⇒ Integer Also known as: io_write
Legacy contract (Ruby 3.4 through 4.0): length before offset, minimum-progress semantics.
166 167 168 169 170 171 |
# File 'lib/carbon_fiber/async.rb', line 166 def io_write_v3(fiber, io, buffer, length, offset = 0) result = native_io_write(io.fileno, buffer, length, offset) return result unless result.nil? ruby_io_write(fiber, io, buffer, length, offset) end |
#io_write_v4(fiber, io, buffer, offset, length) ⇒ Integer
Ruby 4.1+ contract: offset before length, single transfer.
180 181 182 183 184 185 186 187 |
# File 'lib/carbon_fiber/async.rb', line 180 def io_write_v4(fiber, io, buffer, offset, length) return 0 if length.zero? result = native_io_write(io.fileno, buffer, length, offset) return result unless result.nil? ruby_io_write_v4(io, buffer, offset, length) end |
#process_wait(fiber, pid, flags) ⇒ Process::Status
Wait for a child process on a background thread.
210 211 212 213 214 215 |
# File 'lib/carbon_fiber/async.rb', line 210 def process_wait(fiber, pid, flags) Thread.new do Thread.current.report_on_exception = false Process::Status.wait(pid, flags) end.value end |
#push(fiber) ⇒ Object
Enqueue a fiber or fiber-like object into the ready queue.
56 57 58 59 60 61 62 |
# File 'lib/carbon_fiber/async.rb', line 56 def push(fiber) if fiber.is_a?(Fiber) super else @auxiliary_mutex.synchronize { @auxiliary << fiber } end end |
#raise(fiber, *arguments, **options) ⇒ Object
Re-enqueue the current fiber and raise on fiber.
75 76 77 78 79 |
# File 'lib/carbon_fiber/async.rb', line 75 def raise(fiber, *arguments, **) current = Fiber.current native_push(current) unless current.equal?(@loop) fiber.raise(*arguments, **) end |
#ready? ⇒ Boolean
Returns whether there is pending work.
82 83 84 |
# File 'lib/carbon_fiber/async.rb', line 82 def ready? !@auxiliary.empty? || pending? end |
#resume(fiber, *arguments) ⇒ Object
Re-enqueue the current fiber and transfer to fiber with arguments.
67 68 69 70 71 |
# File 'lib/carbon_fiber/async.rb', line 67 def resume(fiber, *arguments) current = Fiber.current native_push(current) unless current.equal?(@loop) fiber.transfer(*arguments) end |
#select(duration = nil) ⇒ Object
Run one event loop iteration, draining the auxiliary queue before and after the native select.
Note: idle_duration is not actually measured—it stays at 0.0.
Async uses it for load stats only (not correctness), and the two
Process.clock_gettime calls plus Float allocation cost ~1-2%
on select-heavy workloads.
96 97 98 99 100 |
# File 'lib/carbon_fiber/async.rb', line 96 def select(duration = nil) drain_auxiliary super drain_auxiliary end |