Class: Hyperion::IOUring::Ring
- Inherits:
-
Object
- Object
- Hyperion::IOUring::Ring
- Defined in:
- lib/hyperion/io_uring.rb
Overview
Per-Ring instance. Wraps the opaque pointer returned by ‘hyperion_io_uring_ring_new` and exposes the accept / read primitives over Fiddle.
Constant Summary collapse
- DEFAULT_QUEUE_DEPTH =
256
Instance Method Summary collapse
-
#accept(listener_fd) ⇒ Object
Accept one connection on ‘listener_fd`.
-
#close ⇒ Object
Close the ring + free its SQ/CQ memory.
- #closed? ⇒ Boolean
-
#initialize(queue_depth: DEFAULT_QUEUE_DEPTH) ⇒ Ring
constructor
A new instance of Ring.
-
#read(fd, max: 4096) ⇒ Object
Read up to ‘max` bytes from `fd` into a fresh ASCII-8BIT String.
Constructor Details
#initialize(queue_depth: DEFAULT_QUEUE_DEPTH) ⇒ Ring
Returns a new instance of Ring.
69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/hyperion/io_uring.rb', line 69 def initialize(queue_depth: DEFAULT_QUEUE_DEPTH) raise Unsupported, 'io_uring not supported on this platform' unless IOUring.supported? @ptr = IOUring.ring_new(queue_depth.to_i) raise Unsupported, 'io_uring_setup failed at ring allocation' if @ptr.nil? || @ptr.null? # `errno` scratch — reused across calls. Fiddle::Pointer to a # 4-byte buffer that the C side writes into on error. Saves # one Pointer allocation per accept. @errno_buf = Fiddle::Pointer.malloc(4, Fiddle::RUBY_FREE) @closed = false end |
Instance Method Details
#accept(listener_fd) ⇒ Object
Accept one connection on ‘listener_fd`. Returns the integer client fd, or `:wouldblock` on EAGAIN. Raises on hard errors.
The ring’s submit_and_wait drives io_uring_enter with min_complete=1, so this fiber parks here until the kernel delivers the matching CQE. Under Async, the Ruby side calls this from a Fiber — the fiber is logically blocked but the OS thread keeps running other fibers via the scheduler ONLY if ‘submit_and_wait` itself yields. It does not yield (it’s a syscall under FFI), so the accept fiber must be the only fiber with work-pending on its OS thread. In Hyperion’s default 1-accept-fiber-per-worker shape that’s always true.
94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/hyperion/io_uring.rb', line 94 def accept(listener_fd) raise IOError, 'ring closed' if @closed rc = IOUring.ring_accept(@ptr, listener_fd.to_i, @errno_buf) return rc if rc.positive? || rc.zero? return :wouldblock if rc == -1 errno = @errno_buf.to_str(4).unpack1('l<') # ECANCELED / EBADF / EINTR → caller treats as wouldblock and # loops. Anything else is a hard error. return :wouldblock if [4, 9, 103, 125].include?(errno) # EINTR / EBADF / ECONNABORTED / ECANCELED raise SystemCallError.new('io_uring accept failed', errno) end |
#close ⇒ Object
Close the ring + free its SQ/CQ memory. Idempotent — calling twice is a no-op (we null-out @ptr after the first free). Must be called from the same fiber that opened the ring.
129 130 131 132 133 134 135 |
# File 'lib/hyperion/io_uring.rb', line 129 def close return if @closed @closed = true IOUring.ring_free(@ptr) if @ptr && !@ptr.null? @ptr = nil end |
#closed? ⇒ Boolean
137 138 139 |
# File 'lib/hyperion/io_uring.rb', line 137 def closed? @closed end |
#read(fd, max: 4096) ⇒ Object
Read up to ‘max` bytes from `fd` into a fresh ASCII-8BIT String. 2.3-A ships this for the accept-only path’s sibling use (per-connection short reads); the connection layer keeps using regular ‘read_nonblock` until a future 2.3-x round wires io_uring reads into the request-line + header parse.
114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/hyperion/io_uring.rb', line 114 def read(fd, max: 4096) raise IOError, 'ring closed' if @closed buf = Fiddle::Pointer.malloc(max, Fiddle::RUBY_FREE) rc = IOUring.ring_read(@ptr, fd.to_i, buf, max.to_i, @errno_buf) return buf.to_str(rc) if rc >= 0 return :wouldblock if rc == -1 errno = @errno_buf.to_str(4).unpack1('l<') raise SystemCallError.new('io_uring read failed', errno) end |