carbon_fiber 0.1.0-aarch64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b2168303a8c978a7e889d8767d9df9e1380d58777ab15e07bdb1964a9635ed09
4
+ data.tar.gz: f693ad61709cc53fee8f9b302eb5869d5253edd76d035457c089422e065f9b60
5
+ SHA512:
6
+ metadata.gz: f8b12b3107a2fee454ccd5d421484b28d7ee916796aab57c9428d3c4a912fc95718f83b5d3409dbdd386f0b3686900178947ce66481aaa1c8ef441d2b923aefe
7
+ data.tar.gz: fd9ad628d5545082aa9e2eb6700952c4fc9cd0f8a7bbc7250e83055813a0c56200a219d0d7f235f449d006e59c20a928a9a86074c1e798a943b52cf9c8bcf39a
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yaroslav Markin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,320 @@
1
+ # Carbon Fiber
2
+
3
+ **High performance Ruby Fiber Scheduler powered by Zig and libxev.**
4
+
5
+ <div align="center">
6
+ <img src="https://raw.githubusercontent.com/yaroslav/carbon_fiber/refs/heads/main/assets/images/carbon_fiber.jpg" width="600" height="400" alt="Carbon Fiber">
7
+ </div>
8
+
9
+ [![GitHub Release](https://img.shields.io/github/v/release/yaroslav/carbon_fiber)](https://github.com/yaroslav/carbon_fiber/releases)
10
+ [![Docs](https://img.shields.io/badge/yard-docs-blue.svg)](https://rubydoc.info/gems/carbon_fiber)
11
+
12
+ **Carbon Fiber is a Ruby Fiber Scheduler** with a native event loop (io_uring on Linux, kqueue on macOS). Install it, and your `Net::HTTP`, `TCPSocket`, `Mutex`, `Queue`, `Process.spawn` code becomes concurrent without any changes. Carbon Fiber also **supports the Async framework** ([async gem](https://github.com/socketry/async)) working as a plug and play backend.
13
+
14
+ By my benchmarks (follows), Carbon Fiber ends up being the **fastest pure Ruby Fiber Scheduler** currently available.
15
+
16
+ Carbon Fiber is implemented using the **Zig** programming language and is powered by **[libxev](https://github.com/mitchellh/libxev)** by Mitchell Hashimoto, used in his **Ghostty** terminal emulator. It is one of the first Ruby native extensions written in Zig.
17
+
18
+ ## Features
19
+
20
+ - **Very fast.** My benchmarks place it as overall the fastest pure Ruby Fiber Scheduler. Uses **io_uring on Linux** and **kqueue on macOS**.
21
+ - **Plug and play with plain Ruby**, thanks to the Fiber Scheduler API—`Net::HTTP`, `TCPSocket`, `Process.spawn`, `Mutex`, `Queue`, DNS—all transparently concurrent under the scheduler; no gem-specific wrappers required.
22
+ - **Async (gem async) support** as a swappable backend. One call swaps the [Async](https://github.com/socketry/async) event loop for ours.
23
+ - **Pure-Ruby fallback**—when the native extension isn't available (Windows, for instance), drops to pure Ruby 4.0+ code behind the same API.
24
+
25
+ ## Example
26
+
27
+ ```ruby
28
+ require "carbon_fiber"
29
+ Fiber.set_scheduler(CarbonFiber::Scheduler.new)
30
+
31
+ urls.each do |url|
32
+ Fiber.schedule { Net::HTTP.get(URI(url)) } # plain Net::HTTP, runs concurrently
33
+ end
34
+ ```
35
+
36
+ ---
37
+
38
+ ## Contents
39
+
40
+ - [Performance](#performance)
41
+ - [Requirements](#requirements)
42
+ - [Installation](#installation)
43
+ - [Usage](#usage)
44
+ - [How It Works](#how-it-works)
45
+ - [Benchmarks](#benchmarks)
46
+ - [Acknowledgements](#acknowledgements)
47
+ - [License](#license)
48
+
49
+ ---
50
+
51
+ ## Performance
52
+
53
+ AWS EC2 c7a.2xlarge, 8 dedicated vCPUs, Ubuntu 24.04 LTS, kernel 6.17, Ruby 4.0.2 + YJIT, io_uring. 5-run median.
54
+
55
+ Some benchmarks:
56
+
57
+ | Workload | Carbon Fiber | Async | Itsi | Carbon Fiber vs. Async |
58
+ |---|---|---|---|---|
59
+ | `http_server` | **48.175k req/s** | 37.409k req/s | 29.708k req/s | +29% |
60
+ | `http_client_api` | **15.528k req/s** | 13.373k req/s | timeout | +16% |
61
+ | `http_client_download` | **6.747k dl/s** | 5.920k dl/s | timeout | +14% |
62
+ | `tcp_echo` | **50.392k ops/s** | 38.907k ops/s | 30.660k ops/s | +29% |
63
+ | `cascading_timeout` | **4.659k ops/s** | 4.488k ops/s | error | +4% |
64
+ | `connection_pool` | **4.989k co/s** | 4.912k co/s | 4.968k co/s | +2% |
65
+
66
+ On my workloads, wins almost all workloads across Async, Itsi, fiber_scheduler, io-event, and libev. [See detailed benchmarks →](#benchmarks)
67
+
68
+ ---
69
+
70
+ ## Requirements
71
+
72
+ - Ruby 3.4 or 4.0.
73
+ - Linux (amd64 or arm64), macOS (Apple Silicon, x64 uses fallback*), Windows (fallback*).
74
+
75
+ Distributed as aleady compiled and ready to use: Zig compiler _not_ needed.
76
+
77
+ _\* Fallback means pure Ruby code, worse performance._
78
+
79
+ ## Installation
80
+
81
+ ```
82
+ bundle add carbon_fiber
83
+ ```
84
+
85
+ ## Usage
86
+
87
+ ### As a standalone fiber scheduler
88
+
89
+ Ruby's fiber scheduler protocol is built into the standard library. Install Carbon Fiber as the thread's scheduler and every blocking I/O call (`sleep`, `Net::HTTP.get`, `TCPSocket.new`, `Process::Status.wait`) yields automatically—no callbacks, no special wrappers.
90
+
91
+ See also: [Ruby Fiber Scheduler protocol](https://ruby-doc.org/core/Fiber/Scheduler.html).
92
+
93
+ #### Example: Parallel file downloads
94
+
95
+ Fetch a batch of files concurrently. Each fiber yields to the event loop while waiting for data; the scheduler runs all of them on a single thread.
96
+
97
+ ```ruby
98
+ require "carbon_fiber"
99
+
100
+ require "net/http"
101
+ require "uri"
102
+
103
+ urls = %w[
104
+ http://files.example.net/archive-1.tar.gz
105
+ http://files.example.net/archive-2.tar.gz
106
+ http://files.example.net/archive-3.tar.gz
107
+ ]
108
+
109
+ scheduler = CarbonFiber::Scheduler.new
110
+ Fiber.set_scheduler(scheduler)
111
+
112
+ results = {}
113
+ urls.each do |url|
114
+ Fiber.schedule do
115
+ results[url] = Net::HTTP.get(URI(url))
116
+ end
117
+ end
118
+
119
+ scheduler.run
120
+ Fiber.set_scheduler(nil)
121
+
122
+ puts "Downloaded #{results.size} files (#{results.values.sum(&:bytesize)} bytes total)"
123
+ ```
124
+
125
+ The `Net::HTTP` code is unchanged from a sequential version. The scheduler intercepts every socket read and write, parks the fiber, and resumes it when the kernel signals readiness—all three downloads proceed in parallel.
126
+
127
+ #### Example: Parallel subprocess processing
128
+
129
+ Transcode a batch of video files using multiple `ffmpeg` processes, all monitored concurrently. `Process::Status.wait` yields to the scheduler while the child runs; no polling required.
130
+
131
+ ```ruby
132
+ require "carbon_fiber"
133
+
134
+ jobs = {
135
+ "output-1080p.mp4" => ["ffmpeg", "-i", "input.mov", "-vf", "scale=1920:1080", "output-1080p.mp4"],
136
+ "output-720p.mp4" => ["ffmpeg", "-i", "input.mov", "-vf", "scale=1280:720", "output-720p.mp4"],
137
+ "output-480p.mp4" => ["ffmpeg", "-i", "input.mov", "-vf", "scale=854:480", "output-480p.mp4"],
138
+ }
139
+
140
+ scheduler = CarbonFiber::Scheduler.new
141
+ Fiber.set_scheduler(scheduler)
142
+
143
+ statuses = {}
144
+ jobs.each do |label, cmd|
145
+ Fiber.schedule do
146
+ pid = Process.spawn(*cmd, out: File::NULL, err: File::NULL)
147
+ statuses[label] = Process::Status.wait(pid)
148
+ end
149
+ end
150
+
151
+ scheduler.run
152
+ Fiber.set_scheduler(nil)
153
+
154
+ statuses.each do |label, status|
155
+ puts "#{label}: #{status.success? ? "ok" : "exit #{status.exitstatus}"}"
156
+ end
157
+ ```
158
+
159
+ All three `ffmpeg` processes run in parallel. The scheduler uses a background thread per `process_wait` call and parks the fiber until the process exits.
160
+
161
+ ---
162
+
163
+ ### With the Async framework
164
+
165
+ Carbon Fiber implements the `IO::Event::Selector` interface, so it can replace Async's built-in event loop. Call `CarbonFiber::Async.default!` once at startup; every subsequent `Async { }` block uses our backend.
166
+
167
+ ```ruby
168
+ require "async"
169
+ require "carbon_fiber/async"
170
+ CarbonFiber::Async.default!
171
+ ```
172
+
173
+ Alternatively, set the environment variable: `IO_EVENT_SELECTOR=CarbonFiberSelector ruby app.rb`.
174
+
175
+ #### Example: Fan-out API calls
176
+
177
+ Call multiple upstream endpoints in parallel using `Async::Barrier`, then wait for all results.
178
+
179
+ ```ruby
180
+ require "async"
181
+ require "async/barrier"
182
+ require "carbon_fiber/async"
183
+
184
+ require "net/http"
185
+ require "json"
186
+
187
+ CarbonFiber::Async.default!
188
+
189
+ ENDPOINTS = %w[/users /orders /products /inventory /settings]
190
+
191
+ Async do
192
+ barrier = Async::Barrier.new
193
+ results = {}
194
+
195
+ ENDPOINTS.each do |path|
196
+ barrier.async do
197
+ response = Net::HTTP.get_response(URI("http://api.example.com#{path}"))
198
+ results[path] = JSON.parse(response.body)
199
+ end
200
+ end
201
+
202
+ barrier.wait
203
+
204
+ puts "Loaded #{results.size} resources"
205
+ results
206
+ end
207
+ ```
208
+
209
+ #### Example: Rate-limited concurrent crawl
210
+
211
+ Fetch many pages concurrently while keeping no more than 10 connections open at once. `Async::Semaphore` limits in-flight requests; `Async::Barrier` tracks completion.
212
+
213
+ ```ruby
214
+ require "async"
215
+ require "async/barrier"
216
+ require "async/semaphore"
217
+ require "carbon_fiber/async"
218
+
219
+ require "net/http"
220
+
221
+ CarbonFiber::Async.default!
222
+
223
+ pages = (1..200).map { |i| "http://data.example.com/records?page=#{i}" }
224
+
225
+ Async do
226
+ barrier = Async::Barrier.new
227
+ semaphore = Async::Semaphore.new(10, parent: barrier)
228
+ results = []
229
+
230
+ pages.each do |url|
231
+ semaphore.async do
232
+ results << Net::HTTP.get(URI(url))
233
+ end
234
+ end
235
+
236
+ barrier.wait
237
+ puts "Fetched #{results.size} pages"
238
+ end
239
+ ```
240
+
241
+ ---
242
+
243
+ ## How It Works
244
+
245
+ The scheduler has two layers:
246
+
247
+ 1. **Zig native core** (`ext/carbon_fiber_native/`) owns the event loop (libxev), ready queue, timer heap, and fiber chaining. Handles `io_wait`, `io_read`, `io_write`, `block`/`unblock`, timer-based sleep, and `timeout_after` directly in native code.
248
+
249
+ 2. **Ruby shell** (`lib/carbon_fiber/scheduler.rb`) implements the Ruby Fiber Scheduler protocol, delegates to the native Selector, and falls back to a background thread for operations the native core doesn't cover (DNS, `process_wait`).
250
+
251
+ **Fiber chaining:** when a fiber parks, the native event loop calls `rb_fiber_transfer` directly to the next ready fiber, skipping a round-trip through the root fiber. This removes one context switch per scheduling decision on every `sleep`, `read`, and `write`.
252
+
253
+ If the native extension can't be loaded (on Windows, for example), a pure-Ruby fallback (`lib/carbon_fiber/native/fallback.rb`) provides the same Selector API using threads and condition variables.
254
+
255
+ ---
256
+
257
+ ## Benchmarks
258
+
259
+ AWS EC2 c7a.2xlarge, 8 dedicated vCPUs,Ubuntu 24.04 LTS, kernel 6.17, Ruby 4.0.2 + YJIT, io_uring. 5-run median.
260
+
261
+ ### Ruby Fiber Schedulers (leading ones): Carbon Fiber vs. Async vs. Itsi
262
+
263
+ Measuring pure Ruby Fiber Scheduler performance (`Fiber.set_scheduler`).
264
+
265
+ | Workload | Unit | Carbon Fiber | Async | Itsi | Carbon Fiber vs. Async |
266
+ |---|---|---|---|---|---|
267
+ | `http_client_api` | req/s | **15,528** | 13,373 | timeout | +16% |
268
+ | `http_client_download` | dl/s | **6,747** | 5,920 | timeout | +14% |
269
+ | `http_server` | req/s | **48,175** | 37,409 | 29,708 | +29% |
270
+ | `tcp_echo` | ops/s | **50,392** | 38,907 | 30,660 | +29% |
271
+ | `connection_pool` | co/s | **4,989** | 4,912 | 4,968 | +2% |
272
+ | `fan_out_gather` | cyc/s | 2,024 | 2,046 | **2,104** | −1% |
273
+ | `db_query_mix` | qry/s | 1,660 | 1,652 | **1,662** | +0.5% |
274
+ | `cascading_timeout` | ops/s | **4,659** | 4,488 | error | +4% |
275
+
276
+ Enabling YJIT turned out to be very beneficial for Async as well—numbers here are with `--yjit` on both sides.
277
+
278
+ ### Async framework: stock Async vs. Async + Carbon Fiber backend
279
+
280
+ Swapped the io-event selector for Carbon Fiber's native backend. Same Async code.
281
+
282
+ | Workload | Unit | Stock Async | Carbon Fiber | Delta |
283
+ |---|---|---|---|---|
284
+ | `http_client_api` | req/s | 13,375 | **14,331** | +7.1% |
285
+ | `http_client_download` | dl/s | 3,893 | **3,956** | +1.6% |
286
+ | `task_churn` | task/s | **87,883** | 85,027 | −3.3% |
287
+ | `condition_signal` | sig/s | 337,282 | **361,089** | +7.1% |
288
+ | `cascading_timeout` | ops/s | 4,497 | **4,511** | +0.3% |
289
+ | `tcp_throughput` | ops/s | 42,292 | **51,930** | +22.8% |
290
+
291
+ ### Examples of how to run benchmarks
292
+
293
+ See [README](benchmarks/README.md) in the `benchmarks/` directory.
294
+
295
+ ```bash
296
+ # macOS smoke test
297
+ benchmarks/bench -t carbon,async -w http_client_api,tcp_echo
298
+
299
+ # Linux via Docker (io_uring)
300
+ benchmarks/core_docker -t carbon,async,itsi -r 5
301
+
302
+ # Async framework comparison
303
+ benchmarks/async_docker -r 5
304
+ ```
305
+
306
+ Note that your results will differ depending on the operating system (io_uring or kqueue), system load and hardware, and there will be a lot of variance with certain workloads anyway. Try to benchmark on dedicated hardware, with setup (operating system, kernel) close to your production environment.
307
+
308
+ ---
309
+
310
+ ## Acknowledgements
311
+
312
+ Thanks to Mitchell Hashimoto for [libxev](https://github.com/mitchellh/libxev) (and Ghostty).
313
+
314
+ Thanks to furunkel for [zig.rb](https://github.com/furunkel/zig.rb).
315
+
316
+ Thanks to Samuel Williams for the [Async](https://github.com/socketry/async) framework and [ecosystem](https://github.com/socketry).
317
+
318
+ ## License
319
+
320
+ MIT
@@ -0,0 +1,256 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Please note that this code is heavily AI-assisted.
4
+
5
+ require_relative "native"
6
+
7
+ module CarbonFiber
8
+ module Async
9
+ # IO::Event::Selector-compatible adapter backed by our native Zig selector.
10
+ # Subclasses Native::Selector so hot-path methods (transfer, yield, wakeup)
11
+ # dispatch directly to native code without an extra Ruby method frame.
12
+ #
13
+ # Registration:
14
+ # require "async"
15
+ # require "carbon_fiber/async"
16
+ # CarbonFiber::Async.default!
17
+ #
18
+ # Or via environment:
19
+ # IO_EVENT_SELECTOR=CarbonFiberSelector ruby app.rb
20
+ #
21
+ class Selector < CarbonFiber::Native::Selector
22
+ # @return [Float] seconds spent idle in the last {#select} call
23
+ attr_reader :idle_duration
24
+
25
+ # @return [Fiber] the event loop fiber
26
+ attr_reader :loop
27
+
28
+ # @param loop [Fiber] the Async event loop fiber
29
+ def initialize(loop)
30
+ super
31
+ @loop = loop
32
+ @idle_duration = 0.0
33
+
34
+ # Auxiliary ready queue for non-Fiber pushables (e.g. FiberInterrupt)
35
+ # and cross-thread pushes of non-Fiber objects. Thread-safe via mutex.
36
+ @auxiliary = []
37
+ @auxiliary_mutex = Mutex.new
38
+ end
39
+
40
+ # transfer: inherited from native (no override needed)
41
+ # yield: inherited from native (no override needed)
42
+ # wakeup: inherited from native (no override needed)
43
+
44
+ # Release native resources.
45
+ def close
46
+ destroy
47
+ end
48
+
49
+ # Enqueue a fiber or fiber-like object into the ready queue.
50
+ # @param fiber [Fiber, Object]
51
+ def push(fiber)
52
+ if fiber.is_a?(Fiber)
53
+ super
54
+ else
55
+ @auxiliary_mutex.synchronize { @auxiliary << fiber }
56
+ end
57
+ end
58
+
59
+ # Re-enqueue the current fiber and transfer to +fiber+ with arguments.
60
+ # @param fiber [Fiber]
61
+ # @param arguments [Array]
62
+ def resume(fiber, *arguments)
63
+ current = Fiber.current
64
+ native_push(current) unless current.equal?(@loop)
65
+ fiber.transfer(*arguments)
66
+ end
67
+
68
+ # Re-enqueue the current fiber and raise on +fiber+.
69
+ # @param fiber [Fiber]
70
+ def raise(fiber, *arguments, **options)
71
+ current = Fiber.current
72
+ native_push(current) unless current.equal?(@loop)
73
+ fiber.raise(*arguments, **options)
74
+ end
75
+
76
+ # @return [Boolean] whether there is pending work
77
+ def ready?
78
+ !@auxiliary.empty? || pending?
79
+ end
80
+
81
+ # --- Event loop ---
82
+
83
+ # Run one event loop iteration, draining the auxiliary queue before
84
+ # and after the native select.
85
+ #
86
+ # Note: +idle_duration+ is not actually measured—it stays at 0.0.
87
+ # Async uses it for load stats only (not correctness), and the two
88
+ # +Process.clock_gettime+ calls plus Float allocation cost ~1-2%
89
+ # on select-heavy workloads.
90
+ # @param duration [Float, nil] maximum seconds to wait
91
+ def select(duration = nil)
92
+ drain_auxiliary
93
+ super
94
+ drain_auxiliary
95
+ end
96
+
97
+ # --- IO operations ---
98
+
99
+ # Wait for I/O readiness. Falls back to IO.select on a background
100
+ # thread when the native path returns nil (kqueue WRITE bypass,
101
+ # closed fd, duplicate waiter).
102
+ # @param fiber [Fiber]
103
+ # @param io [IO]
104
+ # @param events [Integer] bitmask of +IO::READABLE+, +IO::WRITABLE+
105
+ # @return [Integer, false] readiness bitmask, or false on timeout
106
+ def io_wait(fiber, io, events)
107
+ result = native_io_wait(fiber, io.fileno, events)
108
+ return result unless result.nil?
109
+
110
+ fallback_io_wait(io, events)
111
+ end
112
+
113
+ # Native Zig io_read/io_write use recv/send with kernel buffer
114
+ # draining. Falls back to Ruby-level nonblock+io_wait for non-socket
115
+ # fds (pipes, files) where native returns nil.
116
+
117
+ EAGAIN = -Errno::EAGAIN::Errno
118
+ EWOULDBLOCK = -Errno::EWOULDBLOCK::Errno
119
+
120
+ # @param fiber [Fiber]
121
+ # @param io [IO]
122
+ # @param buffer [IO::Buffer]
123
+ # @param length [Integer]
124
+ # @param offset [Integer]
125
+ # @return [Integer] bytes read, or negative errno
126
+ def io_read(fiber, io, buffer, length, offset = 0)
127
+ result = native_io_read(io.fileno, buffer, length, offset)
128
+ return result unless result.nil?
129
+
130
+ ruby_io_read(fiber, io, buffer, length, offset)
131
+ end
132
+
133
+ # @param fiber [Fiber]
134
+ # @param io [IO]
135
+ # @param buffer [IO::Buffer]
136
+ # @param length [Integer]
137
+ # @param offset [Integer]
138
+ # @return [Integer] bytes written, or negative errno
139
+ def io_write(fiber, io, buffer, length, offset = 0)
140
+ result = native_io_write(io.fileno, buffer, length, offset)
141
+ return result unless result.nil?
142
+
143
+ ruby_io_write(fiber, io, buffer, length, offset)
144
+ end
145
+
146
+ # Cancel pending waiters and close the descriptor.
147
+ # @param io [IO]
148
+ def io_close(io)
149
+ fd = io.respond_to?(:fileno) ? io.fileno : io.to_i
150
+ super(fd, IOError.new("stream closed while waiting"))
151
+ end
152
+
153
+ # Wait for a child process on a background thread.
154
+ # @param fiber [Fiber]
155
+ # @param pid [Integer]
156
+ # @param flags [Integer]
157
+ # @return [Process::Status]
158
+ def process_wait(fiber, pid, flags)
159
+ Thread.new do
160
+ Thread.current.report_on_exception = false
161
+ Process::Status.wait(pid, flags)
162
+ end.value
163
+ end
164
+
165
+ private
166
+
167
+ def drain_auxiliary
168
+ return if @auxiliary.empty?
169
+
170
+ items = @auxiliary_mutex.synchronize do
171
+ batch = @auxiliary.dup
172
+ @auxiliary.clear
173
+ batch
174
+ end
175
+
176
+ items.each { |item| item.transfer if item.alive? }
177
+ end
178
+
179
+ # Ruby-level io_read/io_write for non-socket fds (pipes, files).
180
+ # Mirrors io-event's Select fallback selector.
181
+ def ruby_io_read(fiber, io, buffer, length, offset = 0)
182
+ total = 0
183
+
184
+ IO::Event::Selector.nonblock(io) do
185
+ while true
186
+ result = Fiber.blocking { buffer.read(io, 0, offset) }
187
+
188
+ if result < 0
189
+ if result == EAGAIN || result == EWOULDBLOCK
190
+ io_wait(fiber, io, IO::READABLE)
191
+ else
192
+ return result
193
+ end
194
+ elsif result == 0
195
+ break
196
+ else
197
+ total += result
198
+ break if total >= length
199
+ offset += result
200
+ end
201
+ end
202
+ end
203
+
204
+ total
205
+ end
206
+
207
+ def ruby_io_write(fiber, io, buffer, length, offset = 0)
208
+ total = 0
209
+
210
+ IO::Event::Selector.nonblock(io) do
211
+ while true
212
+ result = Fiber.blocking { buffer.write(io, 0, offset) }
213
+
214
+ if result < 0
215
+ if result == EAGAIN || result == EWOULDBLOCK
216
+ io_wait(fiber, io, IO::WRITABLE)
217
+ else
218
+ return result
219
+ end
220
+ elsif result == 0
221
+ break
222
+ else
223
+ total += result
224
+ break if total >= length
225
+ offset += result
226
+ end
227
+ end
228
+ end
229
+
230
+ total
231
+ end
232
+
233
+ def fallback_io_wait(io, events)
234
+ Thread.new do
235
+ Thread.current.report_on_exception = false
236
+ readers = (events & IO::READABLE).zero? ? nil : [io]
237
+ writers = (events & IO::WRITABLE).zero? ? nil : [io]
238
+ ready = ::IO.select(readers, writers, nil, nil)
239
+ return false unless ready
240
+
241
+ readiness = 0
242
+ readiness |= IO::READABLE if ready[0]&.include?(io)
243
+ readiness |= IO::WRITABLE if ready[1]&.include?(io)
244
+ readiness.zero? ? false : readiness
245
+ end.value
246
+ end
247
+ end
248
+
249
+ # Register as the default IO::Event selector for Async.
250
+ # Call after +require "async"+ so IO::Event::Selector is available.
251
+ def self.default!
252
+ IO::Event::Selector.const_set(:CarbonFiberSelector, CarbonFiber::Async::Selector) unless IO::Event::Selector.const_defined?(:CarbonFiberSelector, false)
253
+ ENV["IO_EVENT_SELECTOR"] = "CarbonFiberSelector"
254
+ end
255
+ end
256
+ end