uringmachine 0.20.0 → 0.21.0
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 +4 -4
- data/.github/workflows/test.yml +3 -4
- data/CHANGELOG.md +20 -0
- data/TODO.md +0 -38
- data/examples/bm_queue.rb +2 -1
- data/examples/bm_write.rb +4 -1
- data/ext/um/extconf.rb +1 -1
- data/ext/um/um.c +269 -49
- data/ext/um/um.h +48 -21
- data/ext/um/um_async_op.c +1 -1
- data/ext/um/um_class.c +89 -13
- data/ext/um/um_op.c +37 -0
- data/ext/um/um_sync.c +8 -14
- data/grant-2025/journal.md +125 -1
- data/grant-2025/tasks.md +102 -33
- data/lib/uringmachine/fiber_scheduler.rb +191 -64
- data/lib/uringmachine/version.rb +1 -1
- data/test/test_fiber_scheduler.rb +519 -17
- data/test/test_um.rb +298 -23
- data/uringmachine.gemspec +5 -5
- data/vendor/liburing/src/include/liburing/io_uring.h +1 -0
- data/vendor/liburing/src/include/liburing.h +13 -0
- data/vendor/liburing/src/liburing-ffi.map +1 -0
- data/vendor/liburing/test/bind-listen.c +175 -13
- data/vendor/liburing/test/read-write.c +4 -4
- data/vendor/liburing/test/ringbuf-read.c +4 -4
- data/vendor/liburing/test/send_recv.c +8 -7
- metadata +15 -15
data/grant-2025/tasks.md
CHANGED
|
@@ -1,38 +1,83 @@
|
|
|
1
1
|
- [v] io-event
|
|
2
|
-
|
|
3
2
|
- [v] Make PR to use io_uring_prep_waitid for kernel version >= 6.7
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
- [ ] UringMachine
|
|
9
|
-
- [v] Add support for IO::Buffer in UM API. (How can we detect an IO::Buffer object?)
|
|
10
|
-
https://docs.ruby-lang.org/capi/en/master/d8/d36/group__object.html#gab1b70414d07e7de585f47ee50a64a86c
|
|
11
|
-
|
|
4
|
+
- [ ] UringMachine low-level API
|
|
5
|
+
- [v] Add support for IO::Buffer in UM API.
|
|
12
6
|
- [v] Add `UM::Error` class to be used instead of RuntimeError
|
|
13
|
-
|
|
14
|
-
- [ ] Do batch allocation for `struct um_op`, so they'll be adjacent
|
|
15
|
-
- [ ] Add optional buffer depth argument to `UM.new` (for example, a the
|
|
7
|
+
- [v] Add optional ring size argument to `UM.new` (for example, a the
|
|
16
8
|
worker thread for the scheduler `blocking_operation_wait` hook does not need
|
|
17
9
|
a lot of depth, so you can basically do `UM.new(4)`)
|
|
18
|
-
|
|
19
|
-
- [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
10
|
+
- [v] Add debugging code suggested by Samuel
|
|
11
|
+
- [v] Add support for SQPOLL
|
|
12
|
+
https://unixism.net/loti/tutorial/sq_poll.html
|
|
13
|
+
|
|
14
|
+
- [ ] Add support for using IO::Buffer in association with io_uring registered
|
|
15
|
+
buffers / buffer rings
|
|
16
|
+
- [ ] Set `IOSQE_CQE_SKIP_SUCCESS` flag for `#close_async` and `#write_async`
|
|
17
|
+
- [ ] In `UM#spin` always start fibers as non-blocking.
|
|
18
|
+
- [ ] Add some way to measure fiber CPU time.
|
|
19
|
+
https://github.com/socketry/async/issues/428
|
|
20
|
+
|
|
21
|
+
- [ ] UringMachine Fiber::Scheduler implementation
|
|
23
22
|
- [v] Check how scheduler interacts with `fork`.
|
|
24
23
|
- [v] Implement `process_wait` (with `rb_process_status_new`)
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- [
|
|
30
|
-
|
|
31
|
-
- [
|
|
32
|
-
- [
|
|
33
|
-
- [
|
|
34
|
-
- [
|
|
35
|
-
|
|
24
|
+
- [v] Implement `fiber_interrupt` hook
|
|
25
|
+
- [v] Add `#address_resolve` hook with same impl as Async:
|
|
26
|
+
https://github.com/socketry/async/blob/ea8b0725042b63667ea781d4d011786ca3658256/lib/async/scheduler.rb#L285-L296
|
|
27
|
+
- [v] Implement other hooks:
|
|
28
|
+
- [v] `#timeout_after`
|
|
29
|
+
https://github.com/socketry/async/blob/ea8b0725042b63667ea781d4d011786ca3658256/lib/async/scheduler.rb#L631-L644
|
|
30
|
+
- [v] `#io_pread`
|
|
31
|
+
- [v] `#io_pwrite`
|
|
32
|
+
- [v] `#io_select`
|
|
33
|
+
- [v] Add timeout handling in different I/O hooks
|
|
34
|
+
- [v] Experiment more with fork:
|
|
35
|
+
- [v] what happens to schedulers on other threads (those that don't make it post-fork)
|
|
36
|
+
- do they get GC'd?
|
|
37
|
+
- do they get closed (`#scheduler_close` called)?
|
|
38
|
+
- are they freed cleanly (at least for UM)?
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
class S
|
|
42
|
+
def respond_to?(sym) = true
|
|
43
|
+
end
|
|
44
|
+
o = S.new
|
|
45
|
+
ObjectSpace.define_finalizer(o, ->(*){ puts 'scheduler finalized' })
|
|
46
|
+
t1 = Thread.new { Fiber.set_scheduler(o); sleep }
|
|
47
|
+
t2 = Thread.new {
|
|
48
|
+
fork { p(t1:, t2:) }
|
|
49
|
+
GC.start
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# output:
|
|
53
|
+
# scheduler finalized
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
So, apparently there's no problem!
|
|
57
|
+
- [v] Implement multi-thread worker pool for `blocking_operation_wait`
|
|
58
|
+
Single thread pool at class level, shared by all schedulers
|
|
59
|
+
With worker count according to CPU count
|
|
60
|
+
- [v] Test working with non-blocking files, it should be fine, and we shouldn't need to reset `O_NONBLOCK`.
|
|
61
|
+
- [v] Implement timeouts (how do timeouts interact with blocking ops?)
|
|
62
|
+
- [ ] Implement `#yield` hook (https://github.com/ruby/ruby/pull/14700)
|
|
63
|
+
- [ ] Finish documentation for the `FiberScheduler` class.
|
|
64
|
+
|
|
65
|
+
- [v] tests:
|
|
66
|
+
- [v] Wrap the scheduler interface such that we can verify that specific
|
|
67
|
+
hooks were called. Add asserts for called hooks for all tests.
|
|
68
|
+
- [v] Sockets (only io_wait)
|
|
69
|
+
- [v] Files
|
|
70
|
+
- [v] Mutex / Queue
|
|
71
|
+
- [v] Thread.join
|
|
72
|
+
- [v] Process.wait
|
|
73
|
+
- [v] fork
|
|
74
|
+
- [v] system / exec / etc.
|
|
75
|
+
- [v] popen
|
|
76
|
+
- [ ] "Integration tests"
|
|
77
|
+
- [ ] queue: multiple concurrent readers / writers
|
|
78
|
+
- [ ] net/http test: ad-hoc HTTP/1.1 server + `Net::HTTP` client
|
|
79
|
+
- [ ] sockets: echo server + many clients
|
|
80
|
+
- [ ] IO - all methods!
|
|
36
81
|
|
|
37
82
|
- [ ] Benchmarks
|
|
38
83
|
- [ ] UM queue / Ruby queue (threads) / Ruby queue with UM fiber scheduler
|
|
@@ -52,15 +97,39 @@
|
|
|
52
97
|
- my hunch is we'll be able to show with io_uring real_time is less,
|
|
53
98
|
while cpu_time is more. But it's just a hunch.
|
|
54
99
|
|
|
55
|
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
- https://github.com/socketry/async/blob/main/lib/async/scheduler.rb#L28
|
|
100
|
+
- [ ] Ruby Fiber::Scheduler interface
|
|
101
|
+
- [ ] Make a PR for resetting the scheduler and resetting the fiber non-blocking flag.
|
|
102
|
+
- [ ] Missing hook for close
|
|
103
|
+
- [ ] Missing hooks for send/recv/sendmsg/recvmsg
|
|
104
|
+
- [ ] Writes to a file (including `IO.write`) do not invoke `#io_write` (because writes to files cannot be non-blocking?) Instead, `blocking_operation_wait` is invoked.
|
|
61
105
|
|
|
62
106
|
- [ ] SSL
|
|
63
107
|
- [ ] openssl gem: custom BIO?
|
|
64
108
|
|
|
65
109
|
- curl: https://github.com/curl/curl/blob/5f4cd4c689c822ce957bb415076f0c78e5f474b5/lib/vtls/openssl.c#L786-L803
|
|
66
110
|
|
|
111
|
+
- [ ] UringMachine website
|
|
112
|
+
- [ ] domain: uringmachine.dev
|
|
113
|
+
- [ ] logo: ???
|
|
114
|
+
- [ ] docs (similar to papercraft docs)
|
|
115
|
+
|
|
116
|
+
- [ ] Uma - web server
|
|
117
|
+
- [ ] child process workers
|
|
118
|
+
- [ ] reforking (following https://github.com/Shopify/pitchfork)
|
|
119
|
+
see also: https://byroot.github.io/ruby/performance/2025/03/04/the-pitchfork-story.html
|
|
120
|
+
- Monitor worker memory usage - how much is shared
|
|
121
|
+
- Choose worker with most served request count as "mold" for next generation
|
|
122
|
+
- Perform GC out of band, preferably when there are no active requests
|
|
123
|
+
https://railsatscale.com/2024-10-23-next-generation-oob-gc/
|
|
124
|
+
- When a worker is promoted to "mold", it:
|
|
125
|
+
- Stops `accept`ing requests
|
|
126
|
+
- When finally idle, calls `Process.warmup`
|
|
127
|
+
- Starts replacing sibling workers with forked workers
|
|
128
|
+
see also: https://www.youtube.com/watch?v=kAW5O2dkSU8
|
|
129
|
+
- [ ] Each worker is single-threaded (except for auxiliary threads)
|
|
130
|
+
- [ ] Rack 3.0-compatible
|
|
131
|
+
see: https://github.com/socketry/protocol-rack
|
|
132
|
+
- [ ] Rails integration (Railtie)
|
|
133
|
+
see: https://github.com/socketry/falcon
|
|
134
|
+
- [ ] Benchmarks
|
|
135
|
+
- [ ] Add to the TechEmpower bencchmarks
|
|
@@ -1,10 +1,65 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'resolv'
|
|
4
|
+
require 'etc'
|
|
5
|
+
|
|
3
6
|
class UringMachine
|
|
7
|
+
# Implements a thread pool for running blocking operations.
|
|
8
|
+
class BlockingOperationThreadPool
|
|
9
|
+
def initialize
|
|
10
|
+
@blocking_op_queue = UM::Queue.new
|
|
11
|
+
@pending_count = 0
|
|
12
|
+
@worker_count = 0
|
|
13
|
+
@max_workers = Etc.nprocessors
|
|
14
|
+
@worker_mutex = UM::Mutex.new
|
|
15
|
+
@workers = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def process(machine, job)
|
|
19
|
+
queue = UM::Queue.new
|
|
20
|
+
|
|
21
|
+
if @worker_count == 0 || (@pending_count > 0 && @worker_count < @max_workers)
|
|
22
|
+
start_worker(machine)
|
|
23
|
+
end
|
|
24
|
+
machine.push(@blocking_op_queue, [queue, job])
|
|
25
|
+
machine.shift(queue)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def start_worker(machine)
|
|
31
|
+
machine.synchronize(@worker_mutex) do
|
|
32
|
+
return if @worker_count == @max_workers
|
|
33
|
+
|
|
34
|
+
@workers << Thread.new { run_worker_thread }
|
|
35
|
+
@worker_count += 1
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def run_worker_thread
|
|
40
|
+
machine = UM.new(4).mark(1)
|
|
41
|
+
loop do
|
|
42
|
+
q, op = machine.shift(@blocking_op_queue)
|
|
43
|
+
@pending_count += 1
|
|
44
|
+
res = begin
|
|
45
|
+
op.()
|
|
46
|
+
rescue Exception => e
|
|
47
|
+
e
|
|
48
|
+
end
|
|
49
|
+
@pending_count -= 1
|
|
50
|
+
machine.push(q, res)
|
|
51
|
+
rescue => e
|
|
52
|
+
UM.debug("worker e: #{e.inspect}")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
4
57
|
# UringMachine::FiberScheduler implements the Fiber::Scheduler interface for
|
|
5
58
|
# creating fiber-based concurrent applications in Ruby, in tight integration
|
|
6
59
|
# with the standard Ruby I/O and locking APIs.
|
|
7
60
|
class FiberScheduler
|
|
61
|
+
@@blocking_operation_thread_pool = BlockingOperationThreadPool.new
|
|
62
|
+
|
|
8
63
|
attr_reader :machine, :fiber_map
|
|
9
64
|
|
|
10
65
|
# Instantiates a scheduler with the given UringMachine instance.
|
|
@@ -17,7 +72,6 @@ class UringMachine
|
|
|
17
72
|
# @return [void]
|
|
18
73
|
def initialize(machine = nil)
|
|
19
74
|
@machine = machine || UM.new
|
|
20
|
-
@ios = ObjectSpace::WeakMap.new
|
|
21
75
|
@fiber_map = ObjectSpace::WeakMap.new
|
|
22
76
|
end
|
|
23
77
|
|
|
@@ -29,9 +83,8 @@ class UringMachine
|
|
|
29
83
|
# automatically after a fork).
|
|
30
84
|
#
|
|
31
85
|
# @return [self]
|
|
32
|
-
def
|
|
86
|
+
def process_fork
|
|
33
87
|
@machine = UM.new
|
|
34
|
-
@ios = ObjectSpace::WeakMap.new
|
|
35
88
|
@fiber_map = ObjectSpace::WeakMap.new
|
|
36
89
|
self
|
|
37
90
|
end
|
|
@@ -51,11 +104,6 @@ class UringMachine
|
|
|
51
104
|
join()
|
|
52
105
|
end
|
|
53
106
|
|
|
54
|
-
# fiber_interrupt hook: to be implemented.
|
|
55
|
-
def fiber_interrupt(fiber, exception)
|
|
56
|
-
raise NotImplementedError, "Implement me!"
|
|
57
|
-
end
|
|
58
|
-
|
|
59
107
|
# For debugging purposes
|
|
60
108
|
def p(o) = UM.debug(o.inspect)
|
|
61
109
|
|
|
@@ -79,11 +127,18 @@ class UringMachine
|
|
|
79
127
|
# @param blocking_operation [callable] blocking operation
|
|
80
128
|
# @return [void]
|
|
81
129
|
def blocking_operation_wait(blocking_operation)
|
|
82
|
-
|
|
130
|
+
# naive_blocking_peration_wait(blocking_operation)
|
|
131
|
+
@@blocking_operation_thread_pool.process(@machine, blocking_operation)
|
|
132
|
+
end
|
|
83
133
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
134
|
+
def naive_blocking_peration_wait(blocking_operation)
|
|
135
|
+
res = nil
|
|
136
|
+
Thread.new do
|
|
137
|
+
res = blocking_operation.()
|
|
138
|
+
rescue => e
|
|
139
|
+
res = e
|
|
140
|
+
end.join
|
|
141
|
+
res
|
|
87
142
|
end
|
|
88
143
|
|
|
89
144
|
# block hook: blocks the current fiber by yielding to the machine. This hook
|
|
@@ -92,11 +147,17 @@ class UringMachine
|
|
|
92
147
|
#
|
|
93
148
|
# @param blocker [any] blocker object
|
|
94
149
|
# @param timeout [Number, nil] optional
|
|
95
|
-
# timeout @return [
|
|
150
|
+
# timeout @return [bool] was the operation successful
|
|
96
151
|
def block(blocker, timeout = nil)
|
|
97
|
-
|
|
152
|
+
if timeout
|
|
153
|
+
@machine.timeout(timeout, Timeout::Error) { @machine.yield }
|
|
154
|
+
else
|
|
155
|
+
@machine.yield
|
|
156
|
+
end
|
|
98
157
|
|
|
99
|
-
|
|
158
|
+
true
|
|
159
|
+
rescue Timeout::Error
|
|
160
|
+
false
|
|
100
161
|
end
|
|
101
162
|
|
|
102
163
|
# unblock hook: unblocks the given fiber by scheduling it. This hook is
|
|
@@ -108,6 +169,7 @@ class UringMachine
|
|
|
108
169
|
# @return [void]
|
|
109
170
|
def unblock(blocker, fiber)
|
|
110
171
|
@machine.schedule(fiber, nil)
|
|
172
|
+
@machine.wakeup
|
|
111
173
|
end
|
|
112
174
|
|
|
113
175
|
# kernel_sleep hook: sleeps for the given duration.
|
|
@@ -132,15 +194,28 @@ class UringMachine
|
|
|
132
194
|
timeout ||= io.timeout
|
|
133
195
|
if timeout
|
|
134
196
|
@machine.timeout(timeout, Timeout::Error) {
|
|
135
|
-
@machine.poll(io.fileno, events)
|
|
197
|
+
@machine.poll(io.fileno, events)
|
|
136
198
|
}
|
|
137
199
|
else
|
|
138
|
-
@machine.poll(io.fileno, events)
|
|
200
|
+
@machine.poll(io.fileno, events)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def io_select(rios, wios, eios, timeout = nil)
|
|
205
|
+
map_r = map_io_fds(rios)
|
|
206
|
+
map_w = map_io_fds(wios)
|
|
207
|
+
map_e = map_io_fds(eios)
|
|
139
208
|
|
|
209
|
+
r, w, e = nil
|
|
210
|
+
if timeout
|
|
211
|
+
@machine.timeout(timeout, Timeout::Error) {
|
|
212
|
+
r, w, e = @machine.select(map_r.keys, map_w.keys, map_e.keys)
|
|
213
|
+
}
|
|
214
|
+
else
|
|
215
|
+
r, w, e = @machine.select(map_r.keys, map_w.keys, map_e.keys)
|
|
140
216
|
end
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
raise
|
|
217
|
+
|
|
218
|
+
[unmap_fds(r, map_r), unmap_fds(w, map_w), unmap_fds(e, map_e)]
|
|
144
219
|
end
|
|
145
220
|
|
|
146
221
|
# fiber hook: creates a new fiber with the given block. The created fiber is
|
|
@@ -156,6 +231,53 @@ class UringMachine
|
|
|
156
231
|
fiber
|
|
157
232
|
end
|
|
158
233
|
|
|
234
|
+
# io_read hook: reads from the given IO.
|
|
235
|
+
#
|
|
236
|
+
# @param io [IO] IO object
|
|
237
|
+
# @param buffer [IO::Buffer] read buffer
|
|
238
|
+
# @param length [Integer] read length
|
|
239
|
+
# @param offset [Integer] buffer offset
|
|
240
|
+
# @return [Integer] bytes read
|
|
241
|
+
def io_read(io, buffer, length, offset)
|
|
242
|
+
length = buffer.size if length == 0
|
|
243
|
+
|
|
244
|
+
if (timeout = io.timeout)
|
|
245
|
+
@machine.timeout(timeout, Timeout::Error) do
|
|
246
|
+
@machine.read(io.fileno, buffer, length, offset)
|
|
247
|
+
rescue Errno::EINTR
|
|
248
|
+
retry
|
|
249
|
+
end
|
|
250
|
+
else
|
|
251
|
+
@machine.read(io.fileno, buffer, length, offset)
|
|
252
|
+
end
|
|
253
|
+
rescue Errno::EINTR
|
|
254
|
+
retry
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# io_pread hook: reads from the given IO at the given offset
|
|
258
|
+
#
|
|
259
|
+
# @param io [IO] IO object
|
|
260
|
+
# @param buffer [IO::Buffer] read buffer
|
|
261
|
+
# @param from [Integer] read offset
|
|
262
|
+
# @param length [Integer] read length
|
|
263
|
+
# @param offset [Integer] buffer offset
|
|
264
|
+
# @return [Integer] bytes read
|
|
265
|
+
def io_pread(io, buffer, from, length, offset)
|
|
266
|
+
length = buffer.size if length == 0
|
|
267
|
+
|
|
268
|
+
if (timeout = io.timeout)
|
|
269
|
+
@machine.timeout(timeout, Timeout::Error) do
|
|
270
|
+
@machine.read(io.fileno, buffer, length, offset, from)
|
|
271
|
+
rescue Errno::EINTR
|
|
272
|
+
retry
|
|
273
|
+
end
|
|
274
|
+
else
|
|
275
|
+
@machine.read(io.fileno, buffer, length, offset, from)
|
|
276
|
+
end
|
|
277
|
+
rescue Errno::EINTR
|
|
278
|
+
retry
|
|
279
|
+
end
|
|
280
|
+
|
|
159
281
|
# io_write hook: writes to the given IO.
|
|
160
282
|
#
|
|
161
283
|
# @param io [IO] IO object
|
|
@@ -164,72 +286,77 @@ class UringMachine
|
|
|
164
286
|
# @param offset [Integer] write offset
|
|
165
287
|
# @return [Integer] bytes written
|
|
166
288
|
def io_write(io, buffer, length, offset)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
289
|
+
# p(io_write: io, length:, offset:, timeout: io.timeout)
|
|
290
|
+
length = buffer.size if length == 0
|
|
291
|
+
buffer = buffer.slice(offset) if offset > 0
|
|
170
292
|
|
|
171
|
-
|
|
172
|
-
|
|
293
|
+
if (timeout = io.timeout)
|
|
294
|
+
@machine.timeout(timeout, Timeout::Error) do
|
|
295
|
+
@machine.write(io.fileno, buffer, length)
|
|
296
|
+
rescue Errno::EINTR
|
|
297
|
+
retry
|
|
298
|
+
end
|
|
299
|
+
else
|
|
300
|
+
@machine.write(io.fileno, buffer, length)
|
|
301
|
+
end
|
|
173
302
|
rescue Errno::EINTR
|
|
174
303
|
retry
|
|
175
304
|
end
|
|
176
305
|
|
|
177
|
-
#
|
|
306
|
+
# io_pwrite hook: writes to the given IO at the given offset.
|
|
178
307
|
#
|
|
179
308
|
# @param io [IO] IO object
|
|
180
|
-
# @param buffer [IO::Buffer]
|
|
181
|
-
# @param length [Integer]
|
|
182
|
-
# @param
|
|
183
|
-
# @
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
ensure_nonblock(io)
|
|
309
|
+
# @param buffer [IO::Buffer] write buffer
|
|
310
|
+
# @param length [Integer] file offset
|
|
311
|
+
# @param length [Integer] write length
|
|
312
|
+
# @param offset [Integer] buffer offset
|
|
313
|
+
# @return [Integer] bytes written
|
|
314
|
+
def io_pwrite(io, buffer, from, length, offset)
|
|
315
|
+
# p(io_pwrite: io, from:, length:, offset:, timeout: io.timeout)
|
|
190
316
|
length = buffer.size if length == 0
|
|
191
|
-
|
|
317
|
+
buffer = buffer.slice(offset) if offset > 0
|
|
318
|
+
|
|
319
|
+
if (timeout = io.timeout)
|
|
320
|
+
@machine.timeout(timeout, Timeout::Error) do
|
|
321
|
+
@machine.write(io.fileno, buffer, length, from)
|
|
322
|
+
rescue Errno::EINTR
|
|
323
|
+
retry
|
|
324
|
+
end
|
|
325
|
+
else
|
|
326
|
+
@machine.write(io.fileno, buffer, length, from)
|
|
327
|
+
end
|
|
192
328
|
rescue Errno::EINTR
|
|
193
329
|
retry
|
|
194
330
|
end
|
|
195
331
|
|
|
196
|
-
if UM.
|
|
332
|
+
if UM.method_defined?(:waitid_status)
|
|
197
333
|
def process_wait(pid, flags)
|
|
198
334
|
flags = UM::WEXITED if flags == 0
|
|
199
335
|
@machine.waitid_status(UM::P_PID, pid, flags)
|
|
200
336
|
end
|
|
201
337
|
end
|
|
202
338
|
|
|
203
|
-
|
|
339
|
+
def fiber_interrupt(fiber, exception)
|
|
340
|
+
@machine.schedule(fiber, exception)
|
|
341
|
+
@machine.wakeup
|
|
342
|
+
end
|
|
204
343
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
# @return [void]
|
|
209
|
-
def ensure_nonblock(io)
|
|
210
|
-
return if @ios.key?(io)
|
|
344
|
+
def address_resolve(hostname)
|
|
345
|
+
Resolv.getaddresses(hostname)
|
|
346
|
+
end
|
|
211
347
|
|
|
212
|
-
|
|
213
|
-
|
|
348
|
+
def timeout_after(duration, exception, message, &block)
|
|
349
|
+
@machine.timeout(duration, exception, &block)
|
|
214
350
|
end
|
|
215
351
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
q, op = m.shift(@blocking_op_queue)
|
|
225
|
-
res = begin
|
|
226
|
-
op.()
|
|
227
|
-
rescue Exception => e
|
|
228
|
-
e
|
|
229
|
-
end
|
|
230
|
-
m.push(q, res)
|
|
231
|
-
end
|
|
232
|
-
end
|
|
352
|
+
private
|
|
353
|
+
|
|
354
|
+
def map_io_fds(ios)
|
|
355
|
+
ios.each_with_object({}) { |io, h| h[io.fileno] = io }
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def unmap_fds(fds, map)
|
|
359
|
+
fds.map { map[it] }
|
|
233
360
|
end
|
|
234
361
|
|
|
235
362
|
end
|
data/lib/uringmachine/version.rb
CHANGED