uringmachine 0.21.0 → 0.22.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/.rubocop.yml +2 -0
- data/CHANGELOG.md +14 -0
- data/TODO.md +144 -0
- data/benchmark/README.md +173 -0
- data/benchmark/bm_io_pipe.rb +70 -0
- data/benchmark/bm_io_socketpair.rb +71 -0
- data/benchmark/bm_mutex_cpu.rb +57 -0
- data/benchmark/bm_mutex_io.rb +64 -0
- data/benchmark/bm_pg_client.rb +109 -0
- data/benchmark/bm_queue.rb +76 -0
- data/benchmark/chart.png +0 -0
- data/benchmark/common.rb +135 -0
- data/benchmark/dns_client.rb +47 -0
- data/{examples/bm_http_parse.rb → benchmark/http_parse.rb} +1 -1
- data/benchmark/run_bm.rb +8 -0
- data/benchmark/sqlite.rb +108 -0
- data/{examples/bm_write.rb → benchmark/write.rb} +4 -4
- data/ext/um/um.c +189 -100
- data/ext/um/um.h +36 -10
- data/ext/um/um_async_op.c +1 -1
- data/ext/um/um_class.c +87 -13
- data/ext/um/um_op.c +6 -0
- data/ext/um/um_sync.c +2 -2
- data/ext/um/um_utils.c +16 -0
- data/grant-2025/journal.md +118 -1
- data/grant-2025/tasks.md +48 -22
- data/lib/uringmachine/actor.rb +8 -0
- data/lib/uringmachine/dns_resolver.rb +1 -2
- data/lib/uringmachine/fiber_scheduler.rb +127 -81
- data/lib/uringmachine/version.rb +1 -1
- data/lib/uringmachine.rb +32 -3
- data/test/helper.rb +7 -18
- data/test/test_actor.rb +12 -3
- data/test/test_async_op.rb +10 -10
- data/test/test_fiber.rb +84 -1
- data/test/test_fiber_scheduler.rb +950 -47
- data/test/test_um.rb +297 -120
- data/uringmachine.gemspec +2 -1
- metadata +38 -16
- data/examples/bm_fileno.rb +0 -33
- data/examples/bm_queue.rb +0 -111
- data/examples/bm_side_running.rb +0 -83
- data/examples/bm_sqlite.rb +0 -89
- data/examples/dns_client.rb +0 -12
- /data/{examples/bm_mutex.rb → benchmark/mutex.rb} +0 -0
- /data/{examples/bm_mutex_single.rb → benchmark/mutex_single.rb} +0 -0
- /data/{examples/bm_send.rb → benchmark/send.rb} +0 -0
- /data/{examples/bm_snooze.rb → benchmark/snooze.rb} +0 -0
|
@@ -10,7 +10,6 @@ class UringMachine
|
|
|
10
10
|
@nameservers = get_nameservers
|
|
11
11
|
@fiber = @machine.spin { handle_requests_loop }
|
|
12
12
|
@last_id = 0
|
|
13
|
-
@cache = {}
|
|
14
13
|
end
|
|
15
14
|
|
|
16
15
|
def resolve(hostname, type)
|
|
@@ -59,7 +58,7 @@ class UringMachine
|
|
|
59
58
|
msg = Resolv::DNS::Message.decode buf
|
|
60
59
|
addrs = []
|
|
61
60
|
msg.each_answer do |name, ttl, data|
|
|
62
|
-
p [name, ttl, data]
|
|
61
|
+
# p [name, ttl, data]
|
|
63
62
|
if data.kind_of?(Resolv::DNS::Resource::IN::A) ||
|
|
64
63
|
data.kind_of?(Resolv::DNS::Resource::IN::AAAA)
|
|
65
64
|
addrs << data.address.to_s
|
|
@@ -2,31 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
require 'resolv'
|
|
4
4
|
require 'etc'
|
|
5
|
+
require 'uringmachine'
|
|
5
6
|
|
|
6
7
|
class UringMachine
|
|
7
|
-
# Implements a thread pool for running blocking operations.
|
|
8
|
+
# Implements a worker thread pool for running blocking operations. Worker
|
|
9
|
+
# threads are started as needed. Worker thread count is limited to the number
|
|
10
|
+
# of CPU cores available.
|
|
8
11
|
class BlockingOperationThreadPool
|
|
12
|
+
|
|
13
|
+
# Initializes a new worker pool.
|
|
14
|
+
#
|
|
15
|
+
# @return [void]
|
|
9
16
|
def initialize
|
|
10
|
-
@blocking_op_queue = UM::Queue.new
|
|
11
17
|
@pending_count = 0
|
|
12
18
|
@worker_count = 0
|
|
13
19
|
@max_workers = Etc.nprocessors
|
|
14
20
|
@worker_mutex = UM::Mutex.new
|
|
21
|
+
@job_queue = UM::Queue.new
|
|
15
22
|
@workers = []
|
|
16
23
|
end
|
|
17
24
|
|
|
25
|
+
# Processes a request by submitting it to the job queue and waiting for the
|
|
26
|
+
# return value. Starts a worker if needed.
|
|
27
|
+
#
|
|
28
|
+
# @param machine [UringMachine] machine
|
|
29
|
+
# @param job [any] callable job object
|
|
30
|
+
# @return [any] return value
|
|
18
31
|
def process(machine, job)
|
|
19
|
-
queue =
|
|
20
|
-
|
|
32
|
+
queue = Fiber.current.mailbox
|
|
21
33
|
if @worker_count == 0 || (@pending_count > 0 && @worker_count < @max_workers)
|
|
22
34
|
start_worker(machine)
|
|
23
35
|
end
|
|
24
|
-
machine.push(@
|
|
36
|
+
machine.push(@job_queue, [queue, job])
|
|
25
37
|
machine.shift(queue)
|
|
26
38
|
end
|
|
27
39
|
|
|
28
40
|
private
|
|
29
41
|
|
|
42
|
+
# @param machine [UringMachine] machine
|
|
43
|
+
# @return [void]
|
|
30
44
|
def start_worker(machine)
|
|
31
45
|
machine.synchronize(@worker_mutex) do
|
|
32
46
|
return if @worker_count == @max_workers
|
|
@@ -36,10 +50,11 @@ class UringMachine
|
|
|
36
50
|
end
|
|
37
51
|
end
|
|
38
52
|
|
|
53
|
+
# @return [void]
|
|
39
54
|
def run_worker_thread
|
|
40
|
-
machine = UM.new(4)
|
|
55
|
+
machine = UM.new(4)
|
|
41
56
|
loop do
|
|
42
|
-
q, op = machine.shift(@
|
|
57
|
+
q, op = machine.shift(@job_queue)
|
|
43
58
|
@pending_count += 1
|
|
44
59
|
res = begin
|
|
45
60
|
op.()
|
|
@@ -50,6 +65,7 @@ class UringMachine
|
|
|
50
65
|
machine.push(q, res)
|
|
51
66
|
rescue => e
|
|
52
67
|
UM.debug("worker e: #{e.inspect}")
|
|
68
|
+
exit!
|
|
53
69
|
end
|
|
54
70
|
end
|
|
55
71
|
end
|
|
@@ -58,9 +74,15 @@ class UringMachine
|
|
|
58
74
|
# creating fiber-based concurrent applications in Ruby, in tight integration
|
|
59
75
|
# with the standard Ruby I/O and locking APIs.
|
|
60
76
|
class FiberScheduler
|
|
77
|
+
|
|
78
|
+
# The blocking operation thread pool is shared by all fiber schedulers.
|
|
61
79
|
@@blocking_operation_thread_pool = BlockingOperationThreadPool.new
|
|
62
80
|
|
|
63
|
-
|
|
81
|
+
# UringMachine instance associated with scheduler.
|
|
82
|
+
attr_reader :machine
|
|
83
|
+
|
|
84
|
+
# WeakMap holding references scheduler fibers as keys.
|
|
85
|
+
attr_reader :fiber_map
|
|
64
86
|
|
|
65
87
|
# Instantiates a scheduler with the given UringMachine instance.
|
|
66
88
|
#
|
|
@@ -73,40 +95,36 @@ class UringMachine
|
|
|
73
95
|
def initialize(machine = nil)
|
|
74
96
|
@machine = machine || UM.new
|
|
75
97
|
@fiber_map = ObjectSpace::WeakMap.new
|
|
98
|
+
@thread = Thread.current
|
|
76
99
|
end
|
|
77
100
|
|
|
101
|
+
# :nodoc:
|
|
78
102
|
def instance_variables_to_inspect
|
|
79
103
|
[:@machine]
|
|
80
104
|
end
|
|
81
105
|
|
|
82
|
-
#
|
|
83
|
-
#
|
|
106
|
+
# Creates a new fiber with the given block. The created fiber is added to
|
|
107
|
+
# the fiber map, scheduled on the scheduler machine, and started before this
|
|
108
|
+
# method returns (by calling snooze).
|
|
84
109
|
#
|
|
85
|
-
# @return [
|
|
86
|
-
def
|
|
87
|
-
|
|
88
|
-
@fiber_map = ObjectSpace::WeakMap.new
|
|
89
|
-
self
|
|
90
|
-
end
|
|
110
|
+
# @param block [Proc] fiber block @return [Fiber]
|
|
111
|
+
def fiber(&block)
|
|
112
|
+
fiber = Fiber.new(blocking: false) { @machine.run(fiber, &block) }
|
|
91
113
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
@machine.
|
|
95
|
-
|
|
96
|
-
super
|
|
114
|
+
@fiber_map[fiber] = true
|
|
115
|
+
@machine.schedule(fiber, nil)
|
|
116
|
+
@machine.snooze
|
|
117
|
+
fiber
|
|
97
118
|
end
|
|
98
119
|
|
|
99
|
-
#
|
|
100
|
-
#
|
|
120
|
+
# Waits for all fiber to terminate. Called upon thread termination or when
|
|
121
|
+
# the thread's fiber scheduler is changed.
|
|
101
122
|
#
|
|
102
123
|
# @return [void]
|
|
103
124
|
def scheduler_close
|
|
104
125
|
join()
|
|
105
126
|
end
|
|
106
127
|
|
|
107
|
-
# For debugging purposes
|
|
108
|
-
def p(o) = UM.debug(o.inspect)
|
|
109
|
-
|
|
110
128
|
# Waits for the given fibers to terminate. If no fibers are given, waits for
|
|
111
129
|
# all fibers to terminate.
|
|
112
130
|
#
|
|
@@ -118,49 +136,36 @@ class UringMachine
|
|
|
118
136
|
@fiber_map = ObjectSpace::WeakMap.new
|
|
119
137
|
end
|
|
120
138
|
|
|
121
|
-
@machine.
|
|
139
|
+
@machine.await_fibers(fibers)
|
|
122
140
|
end
|
|
123
141
|
|
|
124
|
-
#
|
|
125
|
-
#
|
|
142
|
+
# Runs the given operation in a separate thread, so as not to block other
|
|
143
|
+
# fibers.
|
|
126
144
|
#
|
|
127
|
-
# @param
|
|
145
|
+
# @param op [callable] blocking operation
|
|
128
146
|
# @return [void]
|
|
129
|
-
def blocking_operation_wait(
|
|
130
|
-
|
|
131
|
-
@@blocking_operation_thread_pool.process(@machine, blocking_operation)
|
|
132
|
-
end
|
|
133
|
-
|
|
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
|
|
147
|
+
def blocking_operation_wait(op)
|
|
148
|
+
@@blocking_operation_thread_pool.process(@machine, op)
|
|
142
149
|
end
|
|
143
150
|
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
# etc.
|
|
151
|
+
# Blocks the current fiber by yielding to the machine. This hook is called
|
|
152
|
+
# when a synchronization mechanism blocks, e.g. a mutex, a queue, etc.
|
|
147
153
|
#
|
|
148
154
|
# @param blocker [any] blocker object
|
|
149
|
-
# @param timeout [Number, nil] optional
|
|
150
|
-
#
|
|
155
|
+
# @param timeout [Number, nil] optional timeout
|
|
156
|
+
# @return [bool] was the operation successful
|
|
151
157
|
def block(blocker, timeout = nil)
|
|
152
158
|
if timeout
|
|
153
159
|
@machine.timeout(timeout, Timeout::Error) { @machine.yield }
|
|
154
160
|
else
|
|
155
161
|
@machine.yield
|
|
156
162
|
end
|
|
157
|
-
|
|
158
163
|
true
|
|
159
164
|
rescue Timeout::Error
|
|
160
165
|
false
|
|
161
166
|
end
|
|
162
167
|
|
|
163
|
-
#
|
|
168
|
+
# Unblocks the given fiber by scheduling it. This hook is
|
|
164
169
|
# called when a synchronization mechanism unblocks, e.g. a mutex, a queue,
|
|
165
170
|
# etc.
|
|
166
171
|
#
|
|
@@ -169,28 +174,31 @@ class UringMachine
|
|
|
169
174
|
# @return [void]
|
|
170
175
|
def unblock(blocker, fiber)
|
|
171
176
|
@machine.schedule(fiber, nil)
|
|
172
|
-
@machine.wakeup
|
|
177
|
+
@machine.wakeup if Thread.current != @thread
|
|
173
178
|
end
|
|
174
179
|
|
|
175
|
-
#
|
|
180
|
+
# Sleeps for the given duration.
|
|
176
181
|
#
|
|
177
182
|
# @param duration [Number, nil] sleep duration
|
|
178
183
|
# @return [void]
|
|
179
184
|
def kernel_sleep(duration = nil)
|
|
180
|
-
|
|
181
|
-
@machine.sleep(duration)
|
|
182
|
-
else
|
|
183
|
-
@machine.yield
|
|
184
|
-
end
|
|
185
|
+
duration ? @machine.sleep(duration) : @machine.yield
|
|
185
186
|
end
|
|
186
187
|
|
|
187
|
-
#
|
|
188
|
+
# Yields to the next runnable fiber.
|
|
189
|
+
def yield
|
|
190
|
+
@machine.snooze
|
|
191
|
+
# @machine.yield
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Waits for the given io to become ready.
|
|
188
195
|
#
|
|
189
196
|
# @param io [IO] IO object
|
|
190
197
|
# @param events [Number] readiness bitmask
|
|
191
198
|
# @param timeout [Number, nil] optional timeout
|
|
192
199
|
# @param return
|
|
193
200
|
def io_wait(io, events, timeout = nil)
|
|
201
|
+
# p(io_wait: io, events:)
|
|
194
202
|
timeout ||= io.timeout
|
|
195
203
|
if timeout
|
|
196
204
|
@machine.timeout(timeout, Timeout::Error) {
|
|
@@ -201,10 +209,16 @@ class UringMachine
|
|
|
201
209
|
end
|
|
202
210
|
end
|
|
203
211
|
|
|
212
|
+
# Selects the first ready IOs from the given sets of IOs.
|
|
213
|
+
#
|
|
214
|
+
# @param rios [Array<IO>] readable IOs
|
|
215
|
+
# @param wios [Array<IO>] writable IOs
|
|
216
|
+
# @param eios [Array<IO>] exceptable IOs
|
|
217
|
+
# @param timeout [Number, nil] optional timeout
|
|
204
218
|
def io_select(rios, wios, eios, timeout = nil)
|
|
205
|
-
map_r =
|
|
206
|
-
map_w =
|
|
207
|
-
map_e =
|
|
219
|
+
map_r = map_fds(rios)
|
|
220
|
+
map_w = map_fds(wios)
|
|
221
|
+
map_e = map_fds(eios)
|
|
208
222
|
|
|
209
223
|
r, w, e = nil
|
|
210
224
|
if timeout
|
|
@@ -218,20 +232,7 @@ class UringMachine
|
|
|
218
232
|
[unmap_fds(r, map_r), unmap_fds(w, map_w), unmap_fds(e, map_e)]
|
|
219
233
|
end
|
|
220
234
|
|
|
221
|
-
#
|
|
222
|
-
# added to the fiber map, scheduled on the scheduler machine, and started
|
|
223
|
-
# before this method returns (by calling snooze).
|
|
224
|
-
#
|
|
225
|
-
# @param block [Proc] fiber block @return [Fiber]
|
|
226
|
-
def fiber(&block)
|
|
227
|
-
fiber = Fiber.new(blocking: false) { @machine.run(fiber, &block) }
|
|
228
|
-
@fiber_map[fiber] = true
|
|
229
|
-
@machine.schedule(fiber, nil)
|
|
230
|
-
@machine.snooze
|
|
231
|
-
fiber
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
# io_read hook: reads from the given IO.
|
|
235
|
+
# Reads from the given IO.
|
|
235
236
|
#
|
|
236
237
|
# @param io [IO] IO object
|
|
237
238
|
# @param buffer [IO::Buffer] read buffer
|
|
@@ -254,7 +255,7 @@ class UringMachine
|
|
|
254
255
|
retry
|
|
255
256
|
end
|
|
256
257
|
|
|
257
|
-
#
|
|
258
|
+
# Reads from the given IO at the given file offset
|
|
258
259
|
#
|
|
259
260
|
# @param io [IO] IO object
|
|
260
261
|
# @param buffer [IO::Buffer] read buffer
|
|
@@ -278,7 +279,7 @@ class UringMachine
|
|
|
278
279
|
retry
|
|
279
280
|
end
|
|
280
281
|
|
|
281
|
-
#
|
|
282
|
+
# Writes to the given IO.
|
|
282
283
|
#
|
|
283
284
|
# @param io [IO] IO object
|
|
284
285
|
# @param buffer [IO::Buffer] write buffer
|
|
@@ -303,7 +304,7 @@ class UringMachine
|
|
|
303
304
|
retry
|
|
304
305
|
end
|
|
305
306
|
|
|
306
|
-
#
|
|
307
|
+
# Writes to the given IO at the given file offset.
|
|
307
308
|
#
|
|
308
309
|
# @param io [IO] IO object
|
|
309
310
|
# @param buffer [IO::Buffer] write buffer
|
|
@@ -329,35 +330,80 @@ class UringMachine
|
|
|
329
330
|
retry
|
|
330
331
|
end
|
|
331
332
|
|
|
333
|
+
# Closes the given fd.
|
|
334
|
+
#
|
|
335
|
+
# @param fd [Integer] file descriptor
|
|
336
|
+
# @return [Integer] file descriptor
|
|
337
|
+
def io_close(fd)
|
|
338
|
+
# p(io_close: fd)
|
|
339
|
+
@machine.close_async(fd)
|
|
340
|
+
end
|
|
341
|
+
|
|
332
342
|
if UM.method_defined?(:waitid_status)
|
|
343
|
+
|
|
344
|
+
# Waits for a process to terminate.
|
|
345
|
+
#
|
|
346
|
+
# @param pid [Integer] process pid (0 for any child process)
|
|
347
|
+
# @param flags [Integer] waitpid flags
|
|
348
|
+
# @return [Process::Status] terminated process status
|
|
333
349
|
def process_wait(pid, flags)
|
|
334
350
|
flags = UM::WEXITED if flags == 0
|
|
335
351
|
@machine.waitid_status(UM::P_PID, pid, flags)
|
|
336
352
|
end
|
|
337
353
|
end
|
|
338
354
|
|
|
355
|
+
# Interrupts the given fiber with an exception.
|
|
356
|
+
#
|
|
357
|
+
# @param fiber [Fiber] fiber to interrupt
|
|
358
|
+
# @param exception [Exception] Exception
|
|
359
|
+
# @return [void]
|
|
339
360
|
def fiber_interrupt(fiber, exception)
|
|
340
361
|
@machine.schedule(fiber, exception)
|
|
341
362
|
@machine.wakeup
|
|
342
363
|
end
|
|
343
364
|
|
|
365
|
+
# Resolves an hostname.
|
|
366
|
+
#
|
|
367
|
+
# @param hostname [String] hostname to resolve
|
|
368
|
+
# @return [Array<Addrinfo>] array of resolved addresses
|
|
344
369
|
def address_resolve(hostname)
|
|
345
370
|
Resolv.getaddresses(hostname)
|
|
346
371
|
end
|
|
347
372
|
|
|
373
|
+
# Run the given block with a timeout.
|
|
374
|
+
#
|
|
375
|
+
# @param duration [Number] timeout duration
|
|
376
|
+
# @param exception [Class] exception Class
|
|
377
|
+
# @param message [String] exception message
|
|
378
|
+
# @param block [Proc] block to run
|
|
379
|
+
# @return [any] block return value
|
|
348
380
|
def timeout_after(duration, exception, message, &block)
|
|
349
381
|
@machine.timeout(duration, exception, &block)
|
|
350
382
|
end
|
|
351
383
|
|
|
352
384
|
private
|
|
353
385
|
|
|
354
|
-
|
|
386
|
+
# Prints the given object for debugging purposes.
|
|
387
|
+
#
|
|
388
|
+
# @param o [any]
|
|
389
|
+
# @return [void]
|
|
390
|
+
def p(o) = UM.debug(o.inspect)
|
|
391
|
+
|
|
392
|
+
# Maps the given ios to fds.
|
|
393
|
+
#
|
|
394
|
+
# @param ios [Array<IO>] IOs to map
|
|
395
|
+
# @return [Hash] hash mapping fds to IOs
|
|
396
|
+
def map_fds(ios)
|
|
355
397
|
ios.each_with_object({}) { |io, h| h[io.fileno] = io }
|
|
356
398
|
end
|
|
357
399
|
|
|
400
|
+
# Maps the given fds to IOs using the given fd-to-IO map.
|
|
401
|
+
#
|
|
402
|
+
# @param fds [Array<Integer>] fds to map
|
|
403
|
+
# @param map [Hash] hash mapping fds to IOs
|
|
404
|
+
# @return [Array<IO>] IOs corresponding to fds
|
|
358
405
|
def unmap_fds(fds, map)
|
|
359
406
|
fds.map { map[it] }
|
|
360
407
|
end
|
|
361
|
-
|
|
362
408
|
end
|
|
363
|
-
end
|
|
409
|
+
end
|
data/lib/uringmachine/version.rb
CHANGED
data/lib/uringmachine.rb
CHANGED
|
@@ -38,7 +38,7 @@ class UringMachine
|
|
|
38
38
|
results[f] = f.result
|
|
39
39
|
else
|
|
40
40
|
(pending ||= []) << f
|
|
41
|
-
queue ||=
|
|
41
|
+
queue ||= Fiber.current.mailbox
|
|
42
42
|
f.add_done_listener(queue)
|
|
43
43
|
end
|
|
44
44
|
end
|
|
@@ -53,6 +53,35 @@ class UringMachine
|
|
|
53
53
|
fibers.size == 1 ? values.first : values
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
+
def await_fibers(fibers)
|
|
57
|
+
if fibers.is_a?(Fiber)
|
|
58
|
+
f = fibers
|
|
59
|
+
if !f.done?
|
|
60
|
+
queue = Fiber.current.mailbox
|
|
61
|
+
f.add_done_listener(queue)
|
|
62
|
+
self.shift(queue)
|
|
63
|
+
end
|
|
64
|
+
return 1
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
queue = nil
|
|
68
|
+
pending = nil
|
|
69
|
+
fibers.each do |f|
|
|
70
|
+
if !f.done?
|
|
71
|
+
(pending ||= []) << f
|
|
72
|
+
queue ||= Fiber.current.mailbox
|
|
73
|
+
f.add_done_listener(queue)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
if pending
|
|
77
|
+
while !pending.empty?
|
|
78
|
+
f = self.shift(queue)
|
|
79
|
+
pending.delete(f)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
fibers.count
|
|
83
|
+
end
|
|
84
|
+
|
|
56
85
|
def resolve(hostname, type = :A)
|
|
57
86
|
@resolver ||= DNSResolver.new(self)
|
|
58
87
|
@resolver.resolve(hostname, type)
|
|
@@ -71,8 +100,8 @@ class UringMachine
|
|
|
71
100
|
@@fiber_map.delete(fiber)
|
|
72
101
|
self.notify_done_listeners(fiber)
|
|
73
102
|
|
|
74
|
-
#
|
|
75
|
-
self.
|
|
103
|
+
# switch away to a different fiber
|
|
104
|
+
self.switch
|
|
76
105
|
end
|
|
77
106
|
|
|
78
107
|
def notify_done_listeners(fiber)
|
data/test/helper.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative './coverage' if ENV['COVERAGE']
|
|
|
5
5
|
require 'uringmachine'
|
|
6
6
|
require 'socket'
|
|
7
7
|
require 'minitest/autorun'
|
|
8
|
+
require 'securerandom'
|
|
8
9
|
|
|
9
10
|
STDOUT.sync = true
|
|
10
11
|
STDERR.sync = true
|
|
@@ -64,30 +65,18 @@ class UMBaseTest < Minitest::Test
|
|
|
64
65
|
end
|
|
65
66
|
|
|
66
67
|
def teardown
|
|
67
|
-
|
|
68
|
+
pending_fibers = @machine.pending_fibers.keys
|
|
69
|
+
if pending_fibers.size > 0
|
|
70
|
+
raise "leaked fibers: #{pending_fibers}"
|
|
71
|
+
end
|
|
72
|
+
GC.start
|
|
68
73
|
end
|
|
69
74
|
|
|
70
75
|
def assign_port
|
|
71
76
|
@@port_assign_mutex ||= Mutex.new
|
|
72
77
|
@@port_assign_mutex.synchronize do
|
|
73
|
-
@@port ||= 1024 + rand(60000)
|
|
78
|
+
@@port ||= 1024 + SecureRandom.rand(60000)
|
|
74
79
|
@@port += 1
|
|
75
80
|
end
|
|
76
81
|
end
|
|
77
|
-
|
|
78
|
-
def make_socket_pair
|
|
79
|
-
port = 10000 + rand(30000)
|
|
80
|
-
server_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
|
|
81
|
-
@machine.setsockopt(server_fd, UM::SOL_SOCKET, UM::SO_REUSEADDR, true)
|
|
82
|
-
@machine.bind(server_fd, '127.0.0.1', port)
|
|
83
|
-
@machine.listen(server_fd, UM::SOMAXCONN)
|
|
84
|
-
|
|
85
|
-
client_conn_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
|
|
86
|
-
@machine.connect(client_conn_fd, '127.0.0.1', port)
|
|
87
|
-
|
|
88
|
-
server_conn_fd = @machine.accept(server_fd)
|
|
89
|
-
|
|
90
|
-
@machine.close(server_fd)
|
|
91
|
-
[client_conn_fd, server_conn_fd]
|
|
92
|
-
end
|
|
93
82
|
end
|
data/test/test_actor.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'helper'
|
|
4
|
-
require 'socket'
|
|
5
4
|
require 'uringmachine/actor'
|
|
6
5
|
|
|
7
6
|
class ActorTest < UMBaseTest
|
|
@@ -25,7 +24,7 @@ class ActorTest < UMBaseTest
|
|
|
25
24
|
|
|
26
25
|
def test_basic_actor_functionality
|
|
27
26
|
mailbox = UM::Queue.new
|
|
28
|
-
actor =
|
|
27
|
+
actor = machine.spin_actor(Counter)
|
|
29
28
|
|
|
30
29
|
assert_kind_of Fiber, actor
|
|
31
30
|
|
|
@@ -35,6 +34,11 @@ class ActorTest < UMBaseTest
|
|
|
35
34
|
assert_equal 2, actor.call(mailbox, :get)
|
|
36
35
|
assert_equal actor, actor.cast(:reset)
|
|
37
36
|
assert_equal 0, actor.call(mailbox, :get)
|
|
37
|
+
ensure
|
|
38
|
+
if actor
|
|
39
|
+
actor.stop
|
|
40
|
+
machine.join(actor)
|
|
41
|
+
end
|
|
38
42
|
end
|
|
39
43
|
|
|
40
44
|
module Counter2
|
|
@@ -56,10 +60,15 @@ class ActorTest < UMBaseTest
|
|
|
56
60
|
end
|
|
57
61
|
|
|
58
62
|
|
|
59
|
-
def
|
|
63
|
+
def test_spin_actor_with_args
|
|
60
64
|
actor = @machine.spin_actor(Counter2, 43)
|
|
61
65
|
mailbox = UM::Queue.new
|
|
62
66
|
|
|
63
67
|
assert_equal 43, actor.call(mailbox, :get)
|
|
68
|
+
ensure
|
|
69
|
+
if actor
|
|
70
|
+
actor.stop
|
|
71
|
+
machine.join(actor)
|
|
72
|
+
end
|
|
64
73
|
end
|
|
65
74
|
end
|
data/test/test_async_op.rb
CHANGED
|
@@ -11,22 +11,22 @@ class AsyncOpTest < UMBaseTest
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def test_async_op_await
|
|
14
|
-
assert_equal 1, machine.
|
|
14
|
+
assert_equal 1, machine.metrics[:ops_pending]
|
|
15
15
|
res = @op.await
|
|
16
16
|
t1 = monotonic_clock
|
|
17
17
|
assert_in_range 0.04..0.08, t1 - @t0
|
|
18
|
-
assert_equal 0, machine.
|
|
18
|
+
assert_equal 0, machine.metrics[:ops_pending]
|
|
19
19
|
assert_equal (-ETIME), res
|
|
20
20
|
assert_equal true, @op.done?
|
|
21
21
|
assert_equal false, @op.cancelled?
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def test_async_op_join
|
|
25
|
-
assert_equal 1, machine.
|
|
25
|
+
assert_equal 1, machine.metrics[:ops_pending]
|
|
26
26
|
res = @op.join
|
|
27
27
|
t1 = monotonic_clock
|
|
28
28
|
assert_in_range 0.04..0.08, t1 - @t0
|
|
29
|
-
assert_equal 0, machine.
|
|
29
|
+
assert_equal 0, machine.metrics[:ops_pending]
|
|
30
30
|
assert_equal (-ETIME), res
|
|
31
31
|
assert_equal true, @op.done?
|
|
32
32
|
assert_equal false, @op.cancelled?
|
|
@@ -34,13 +34,13 @@ class AsyncOpTest < UMBaseTest
|
|
|
34
34
|
|
|
35
35
|
def test_async_op_cancel
|
|
36
36
|
machine.sleep(0.01)
|
|
37
|
-
assert_equal 1, machine.
|
|
37
|
+
assert_equal 1, machine.metrics[:ops_pending]
|
|
38
38
|
@op.cancel
|
|
39
39
|
assert_equal false, @op.done?
|
|
40
40
|
|
|
41
41
|
machine.sleep(0.01)
|
|
42
42
|
|
|
43
|
-
assert_equal 0, machine.
|
|
43
|
+
assert_equal 0, machine.metrics[:ops_pending]
|
|
44
44
|
assert_equal true, @op.done?
|
|
45
45
|
assert_equal (-ECANCELED), @op.result
|
|
46
46
|
assert_equal true, @op.cancelled?
|
|
@@ -53,7 +53,7 @@ class AsyncOpTest < UMBaseTest
|
|
|
53
53
|
|
|
54
54
|
res = @op.await
|
|
55
55
|
|
|
56
|
-
assert_equal 0, machine.
|
|
56
|
+
assert_equal 0, machine.metrics[:ops_pending]
|
|
57
57
|
assert_equal true, @op.done?
|
|
58
58
|
assert_equal (-ECANCELED), res
|
|
59
59
|
assert_equal true, @op.cancelled?
|
|
@@ -71,7 +71,7 @@ class AsyncOpTest < UMBaseTest
|
|
|
71
71
|
rescue => e
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
assert_equal 0, machine.
|
|
74
|
+
assert_equal 0, machine.metrics[:ops_pending]
|
|
75
75
|
assert_kind_of TOError, e
|
|
76
76
|
assert_equal true, @op.done?
|
|
77
77
|
assert_equal (-ECANCELED), @op.result
|
|
@@ -89,7 +89,7 @@ class AsyncOpTest < UMBaseTest
|
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
# machine.timeout is cancelled async, so CQE is not yet reaped
|
|
92
|
-
assert_equal 1, machine.
|
|
92
|
+
assert_equal 1, machine.metrics[:ops_pending]
|
|
93
93
|
assert_nil e
|
|
94
94
|
assert_equal true, @op.done?
|
|
95
95
|
assert_equal (-ETIME), @op.result
|
|
@@ -97,7 +97,7 @@ class AsyncOpTest < UMBaseTest
|
|
|
97
97
|
|
|
98
98
|
# wait for timeout cancellation
|
|
99
99
|
machine.sleep(0.01)
|
|
100
|
-
assert_equal 0, machine.
|
|
100
|
+
assert_equal 0, machine.metrics[:ops_pending]
|
|
101
101
|
end
|
|
102
102
|
end
|
|
103
103
|
|