uringmachine 0.20.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +3 -4
  3. data/.rubocop.yml +2 -0
  4. data/CHANGELOG.md +34 -0
  5. data/TODO.md +132 -26
  6. data/benchmark/README.md +173 -0
  7. data/benchmark/bm_io_pipe.rb +70 -0
  8. data/benchmark/bm_io_socketpair.rb +71 -0
  9. data/benchmark/bm_mutex_cpu.rb +57 -0
  10. data/benchmark/bm_mutex_io.rb +64 -0
  11. data/benchmark/bm_pg_client.rb +109 -0
  12. data/benchmark/bm_queue.rb +76 -0
  13. data/benchmark/chart.png +0 -0
  14. data/benchmark/common.rb +135 -0
  15. data/benchmark/dns_client.rb +47 -0
  16. data/{examples/bm_http_parse.rb → benchmark/http_parse.rb} +1 -1
  17. data/benchmark/run_bm.rb +8 -0
  18. data/benchmark/sqlite.rb +108 -0
  19. data/{examples/bm_write.rb → benchmark/write.rb} +6 -3
  20. data/ext/um/extconf.rb +1 -1
  21. data/ext/um/um.c +404 -95
  22. data/ext/um/um.h +77 -24
  23. data/ext/um/um_async_op.c +2 -2
  24. data/ext/um/um_class.c +168 -18
  25. data/ext/um/um_op.c +43 -0
  26. data/ext/um/um_sync.c +10 -16
  27. data/ext/um/um_utils.c +16 -0
  28. data/grant-2025/journal.md +242 -1
  29. data/grant-2025/tasks.md +136 -41
  30. data/lib/uringmachine/actor.rb +8 -0
  31. data/lib/uringmachine/dns_resolver.rb +1 -2
  32. data/lib/uringmachine/fiber_scheduler.rb +283 -110
  33. data/lib/uringmachine/version.rb +1 -1
  34. data/lib/uringmachine.rb +32 -3
  35. data/test/helper.rb +7 -18
  36. data/test/test_actor.rb +12 -3
  37. data/test/test_async_op.rb +10 -10
  38. data/test/test_fiber.rb +84 -1
  39. data/test/test_fiber_scheduler.rb +1425 -20
  40. data/test/test_um.rb +565 -113
  41. data/uringmachine.gemspec +6 -5
  42. data/vendor/liburing/src/include/liburing/io_uring.h +1 -0
  43. data/vendor/liburing/src/include/liburing.h +13 -0
  44. data/vendor/liburing/src/liburing-ffi.map +1 -0
  45. data/vendor/liburing/test/bind-listen.c +175 -13
  46. data/vendor/liburing/test/read-write.c +4 -4
  47. data/vendor/liburing/test/ringbuf-read.c +4 -4
  48. data/vendor/liburing/test/send_recv.c +8 -7
  49. metadata +50 -28
  50. data/examples/bm_fileno.rb +0 -33
  51. data/examples/bm_queue.rb +0 -110
  52. data/examples/bm_side_running.rb +0 -83
  53. data/examples/bm_sqlite.rb +0 -89
  54. data/examples/dns_client.rb +0 -12
  55. /data/{examples/bm_mutex.rb → benchmark/mutex.rb} +0 -0
  56. /data/{examples/bm_mutex_single.rb → benchmark/mutex_single.rb} +0 -0
  57. /data/{examples/bm_send.rb → benchmark/send.rb} +0 -0
  58. /data/{examples/bm_snooze.rb → benchmark/snooze.rb} +0 -0
@@ -1,11 +1,88 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'resolv'
4
+ require 'etc'
5
+ require 'uringmachine'
6
+
3
7
  class UringMachine
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.
11
+ class BlockingOperationThreadPool
12
+
13
+ # Initializes a new worker pool.
14
+ #
15
+ # @return [void]
16
+ def initialize
17
+ @pending_count = 0
18
+ @worker_count = 0
19
+ @max_workers = Etc.nprocessors
20
+ @worker_mutex = UM::Mutex.new
21
+ @job_queue = UM::Queue.new
22
+ @workers = []
23
+ end
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
31
+ def process(machine, job)
32
+ queue = Fiber.current.mailbox
33
+ if @worker_count == 0 || (@pending_count > 0 && @worker_count < @max_workers)
34
+ start_worker(machine)
35
+ end
36
+ machine.push(@job_queue, [queue, job])
37
+ machine.shift(queue)
38
+ end
39
+
40
+ private
41
+
42
+ # @param machine [UringMachine] machine
43
+ # @return [void]
44
+ def start_worker(machine)
45
+ machine.synchronize(@worker_mutex) do
46
+ return if @worker_count == @max_workers
47
+
48
+ @workers << Thread.new { run_worker_thread }
49
+ @worker_count += 1
50
+ end
51
+ end
52
+
53
+ # @return [void]
54
+ def run_worker_thread
55
+ machine = UM.new(4)
56
+ loop do
57
+ q, op = machine.shift(@job_queue)
58
+ @pending_count += 1
59
+ res = begin
60
+ op.()
61
+ rescue Exception => e
62
+ e
63
+ end
64
+ @pending_count -= 1
65
+ machine.push(q, res)
66
+ rescue => e
67
+ UM.debug("worker e: #{e.inspect}")
68
+ exit!
69
+ end
70
+ end
71
+ end
72
+
4
73
  # UringMachine::FiberScheduler implements the Fiber::Scheduler interface for
5
74
  # creating fiber-based concurrent applications in Ruby, in tight integration
6
75
  # with the standard Ruby I/O and locking APIs.
7
76
  class FiberScheduler
8
- attr_reader :machine, :fiber_map
77
+
78
+ # The blocking operation thread pool is shared by all fiber schedulers.
79
+ @@blocking_operation_thread_pool = BlockingOperationThreadPool.new
80
+
81
+ # UringMachine instance associated with scheduler.
82
+ attr_reader :machine
83
+
84
+ # WeakMap holding references scheduler fibers as keys.
85
+ attr_reader :fiber_map
9
86
 
10
87
  # Instantiates a scheduler with the given UringMachine instance.
11
88
  #
@@ -17,48 +94,37 @@ class UringMachine
17
94
  # @return [void]
18
95
  def initialize(machine = nil)
19
96
  @machine = machine || UM.new
20
- @ios = ObjectSpace::WeakMap.new
21
97
  @fiber_map = ObjectSpace::WeakMap.new
98
+ @thread = Thread.current
22
99
  end
23
100
 
101
+ # :nodoc:
24
102
  def instance_variables_to_inspect
25
103
  [:@machine]
26
104
  end
27
105
 
28
- # Should be called after a fork (eventually, we'll want Ruby to call this
29
- # automatically after a fork).
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).
30
109
  #
31
- # @return [self]
32
- def post_fork
33
- @machine = UM.new
34
- @ios = ObjectSpace::WeakMap.new
35
- @fiber_map = ObjectSpace::WeakMap.new
36
- self
37
- end
110
+ # @param block [Proc] fiber block @return [Fiber]
111
+ def fiber(&block)
112
+ fiber = Fiber.new(blocking: false) { @machine.run(fiber, &block) }
38
113
 
39
- # For debugging purposes
40
- def method_missing(sym, *a, **b)
41
- @machine.write(1, "method_missing: #{sym.inspect} #{a.inspect} #{b.inspect}\n")
42
- @machine.write(1, "#{caller.inspect}\n")
43
- super
114
+ @fiber_map[fiber] = true
115
+ @machine.schedule(fiber, nil)
116
+ @machine.snooze
117
+ fiber
44
118
  end
45
119
 
46
- # scheduler_close hook: Waits for all fiber to terminate. Called upon thread
47
- # termination or when the thread's fiber scheduler is changed.
120
+ # Waits for all fiber to terminate. Called upon thread termination or when
121
+ # the thread's fiber scheduler is changed.
48
122
  #
49
123
  # @return [void]
50
124
  def scheduler_close
51
125
  join()
52
126
  end
53
127
 
54
- # fiber_interrupt hook: to be implemented.
55
- def fiber_interrupt(fiber, exception)
56
- raise NotImplementedError, "Implement me!"
57
- end
58
-
59
- # For debugging purposes
60
- def p(o) = UM.debug(o.inspect)
61
-
62
128
  # Waits for the given fibers to terminate. If no fibers are given, waits for
63
129
  # all fibers to terminate.
64
130
  #
@@ -70,36 +136,36 @@ class UringMachine
70
136
  @fiber_map = ObjectSpace::WeakMap.new
71
137
  end
72
138
 
73
- @machine.join(*fibers)
139
+ @machine.await_fibers(fibers)
74
140
  end
75
141
 
76
- # blocking_operation_wait hook: runs the given operation in a separate
77
- # thread, so as not to block other fibers.
142
+ # Runs the given operation in a separate thread, so as not to block other
143
+ # fibers.
78
144
  #
79
- # @param blocking_operation [callable] blocking operation
145
+ # @param op [callable] blocking operation
80
146
  # @return [void]
81
- def blocking_operation_wait(blocking_operation)
82
- start_blocking_operation_thread if !@blocking_op_queue
83
-
84
- queue = UM::Queue.new
85
- @machine.push(@blocking_op_queue, [queue, blocking_operation])
86
- @machine.shift(queue)
147
+ def blocking_operation_wait(op)
148
+ @@blocking_operation_thread_pool.process(@machine, op)
87
149
  end
88
150
 
89
- # block hook: blocks the current fiber by yielding to the machine. This hook
90
- # is called when a synchronization mechanism blocks, e.g. a mutex, a queue,
91
- # 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.
92
153
  #
93
154
  # @param blocker [any] blocker object
94
- # @param timeout [Number, nil] optional
95
- # timeout @return [void]
155
+ # @param timeout [Number, nil] optional timeout
156
+ # @return [bool] was the operation successful
96
157
  def block(blocker, timeout = nil)
97
- raise NotImplementedError, "Implement me!" if timeout
98
-
99
- @machine.yield
158
+ if timeout
159
+ @machine.timeout(timeout, Timeout::Error) { @machine.yield }
160
+ else
161
+ @machine.yield
162
+ end
163
+ true
164
+ rescue Timeout::Error
165
+ false
100
166
  end
101
167
 
102
- # unblock hook: unblocks the given fiber by scheduling it. This hook is
168
+ # Unblocks the given fiber by scheduling it. This hook is
103
169
  # called when a synchronization mechanism unblocks, e.g. a mutex, a queue,
104
170
  # etc.
105
171
  #
@@ -108,55 +174,112 @@ class UringMachine
108
174
  # @return [void]
109
175
  def unblock(blocker, fiber)
110
176
  @machine.schedule(fiber, nil)
177
+ @machine.wakeup if Thread.current != @thread
111
178
  end
112
179
 
113
- # kernel_sleep hook: sleeps for the given duration.
180
+ # Sleeps for the given duration.
114
181
  #
115
182
  # @param duration [Number, nil] sleep duration
116
183
  # @return [void]
117
184
  def kernel_sleep(duration = nil)
118
- if duration
119
- @machine.sleep(duration)
120
- else
121
- @machine.yield
122
- end
185
+ duration ? @machine.sleep(duration) : @machine.yield
123
186
  end
124
187
 
125
- # io_wait hook: waits for the given io to become ready.
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.
126
195
  #
127
196
  # @param io [IO] IO object
128
197
  # @param events [Number] readiness bitmask
129
198
  # @param timeout [Number, nil] optional timeout
130
199
  # @param return
131
200
  def io_wait(io, events, timeout = nil)
201
+ # p(io_wait: io, events:)
132
202
  timeout ||= io.timeout
133
203
  if timeout
134
204
  @machine.timeout(timeout, Timeout::Error) {
135
- @machine.poll(io.fileno, events).tap { p 3 }
205
+ @machine.poll(io.fileno, events)
136
206
  }
137
207
  else
138
- @machine.poll(io.fileno, events).tap { p 6 }
208
+ @machine.poll(io.fileno, events)
209
+ end
210
+ end
139
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
218
+ def io_select(rios, wios, eios, timeout = nil)
219
+ map_r = map_fds(rios)
220
+ map_w = map_fds(wios)
221
+ map_e = map_fds(eios)
222
+
223
+ r, w, e = nil
224
+ if timeout
225
+ @machine.timeout(timeout, Timeout::Error) {
226
+ r, w, e = @machine.select(map_r.keys, map_w.keys, map_e.keys)
227
+ }
228
+ else
229
+ r, w, e = @machine.select(map_r.keys, map_w.keys, map_e.keys)
140
230
  end
141
- rescue => e
142
- p e: e
143
- raise
231
+
232
+ [unmap_fds(r, map_r), unmap_fds(w, map_w), unmap_fds(e, map_e)]
144
233
  end
145
234
 
146
- # fiber hook: creates a new fiber with the given block. The created fiber is
147
- # added to the fiber map, scheduled on the scheduler machine, and started
148
- # before this method returns (by calling snooze).
235
+ # Reads from the given IO.
149
236
  #
150
- # @param block [Proc] fiber block @return [Fiber]
151
- def fiber(&block)
152
- fiber = Fiber.new(blocking: false) { @machine.run(fiber, &block) }
153
- @fiber_map[fiber] = true
154
- @machine.schedule(fiber, nil)
155
- @machine.snooze
156
- fiber
237
+ # @param io [IO] IO object
238
+ # @param buffer [IO::Buffer] read buffer
239
+ # @param length [Integer] read length
240
+ # @param offset [Integer] buffer offset
241
+ # @return [Integer] bytes read
242
+ def io_read(io, buffer, length, offset)
243
+ length = buffer.size if length == 0
244
+
245
+ if (timeout = io.timeout)
246
+ @machine.timeout(timeout, Timeout::Error) do
247
+ @machine.read(io.fileno, buffer, length, offset)
248
+ rescue Errno::EINTR
249
+ retry
250
+ end
251
+ else
252
+ @machine.read(io.fileno, buffer, length, offset)
253
+ end
254
+ rescue Errno::EINTR
255
+ retry
256
+ end
257
+
258
+ # Reads from the given IO at the given file offset
259
+ #
260
+ # @param io [IO] IO object
261
+ # @param buffer [IO::Buffer] read buffer
262
+ # @param from [Integer] read offset
263
+ # @param length [Integer] read length
264
+ # @param offset [Integer] buffer offset
265
+ # @return [Integer] bytes read
266
+ def io_pread(io, buffer, from, length, offset)
267
+ length = buffer.size if length == 0
268
+
269
+ if (timeout = io.timeout)
270
+ @machine.timeout(timeout, Timeout::Error) do
271
+ @machine.read(io.fileno, buffer, length, offset, from)
272
+ rescue Errno::EINTR
273
+ retry
274
+ end
275
+ else
276
+ @machine.read(io.fileno, buffer, length, offset, from)
277
+ end
278
+ rescue Errno::EINTR
279
+ retry
157
280
  end
158
281
 
159
- # io_write hook: writes to the given IO.
282
+ # Writes to the given IO.
160
283
  #
161
284
  # @param io [IO] IO object
162
285
  # @param buffer [IO::Buffer] write buffer
@@ -164,73 +287,123 @@ class UringMachine
164
287
  # @param offset [Integer] write offset
165
288
  # @return [Integer] bytes written
166
289
  def io_write(io, buffer, length, offset)
167
- if offset > 0
168
- raise NotImplementedError, "UringMachine currently does not support writing at an offset"
169
- end
290
+ # p(io_write: io, length:, offset:, timeout: io.timeout)
291
+ length = buffer.size if length == 0
292
+ buffer = buffer.slice(offset) if offset > 0
170
293
 
171
- ensure_nonblock(io)
172
- @machine.write(io.fileno, buffer)
294
+ if (timeout = io.timeout)
295
+ @machine.timeout(timeout, Timeout::Error) do
296
+ @machine.write(io.fileno, buffer, length)
297
+ rescue Errno::EINTR
298
+ retry
299
+ end
300
+ else
301
+ @machine.write(io.fileno, buffer, length)
302
+ end
173
303
  rescue Errno::EINTR
174
304
  retry
175
305
  end
176
306
 
177
- # io_read hook: reads from the given IO.
307
+ # Writes to the given IO at the given file offset.
178
308
  #
179
309
  # @param io [IO] IO object
180
- # @param buffer [IO::Buffer] read buffer
181
- # @param length [Integer] read length
182
- # @param offset [Integer] read offset
183
- # @return [Integer] bytes read
184
- def io_read(io, buffer, length, offset)
185
- if offset > 0
186
- raise NotImplementedError, "UringMachine currently does not support reading at an offset"
187
- end
188
-
189
- ensure_nonblock(io)
310
+ # @param buffer [IO::Buffer] write buffer
311
+ # @param length [Integer] file offset
312
+ # @param length [Integer] write length
313
+ # @param offset [Integer] buffer offset
314
+ # @return [Integer] bytes written
315
+ def io_pwrite(io, buffer, from, length, offset)
316
+ # p(io_pwrite: io, from:, length:, offset:, timeout: io.timeout)
190
317
  length = buffer.size if length == 0
191
- @machine.read(io.fileno, buffer, length)
318
+ buffer = buffer.slice(offset) if offset > 0
319
+
320
+ if (timeout = io.timeout)
321
+ @machine.timeout(timeout, Timeout::Error) do
322
+ @machine.write(io.fileno, buffer, length, from)
323
+ rescue Errno::EINTR
324
+ retry
325
+ end
326
+ else
327
+ @machine.write(io.fileno, buffer, length, from)
328
+ end
192
329
  rescue Errno::EINTR
193
330
  retry
194
331
  end
195
332
 
196
- if UM.instance_methods.include?(:waitid_status)
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
+
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
197
349
  def process_wait(pid, flags)
198
350
  flags = UM::WEXITED if flags == 0
199
351
  @machine.waitid_status(UM::P_PID, pid, flags)
200
352
  end
201
353
  end
202
354
 
203
- private
204
-
205
- # Ensures the given IO is in blocking mode.
355
+ # Interrupts the given fiber with an exception.
206
356
  #
207
- # @param io [IO] IO object
357
+ # @param fiber [Fiber] fiber to interrupt
358
+ # @param exception [Exception] Exception
208
359
  # @return [void]
209
- def ensure_nonblock(io)
210
- return if @ios.key?(io)
360
+ def fiber_interrupt(fiber, exception)
361
+ @machine.schedule(fiber, exception)
362
+ @machine.wakeup
363
+ end
364
+
365
+ # Resolves an hostname.
366
+ #
367
+ # @param hostname [String] hostname to resolve
368
+ # @return [Array<Addrinfo>] array of resolved addresses
369
+ def address_resolve(hostname)
370
+ Resolv.getaddresses(hostname)
371
+ end
211
372
 
212
- @ios[io] = true
213
- UM.io_set_nonblock(io, false)
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
380
+ def timeout_after(duration, exception, message, &block)
381
+ @machine.timeout(duration, exception, &block)
214
382
  end
215
383
 
216
- # Starts a background thread for running blocking operations.
384
+ private
385
+
386
+ # Prints the given object for debugging purposes.
217
387
  #
388
+ # @param o [any]
218
389
  # @return [void]
219
- def start_blocking_operation_thread
220
- @blocking_op_queue = UM::Queue.new
221
- @blocking_op_thread = Thread.new do
222
- m = UM.new
223
- loop do
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
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)
397
+ ios.each_with_object({}) { |io, h| h[io.fileno] = io }
233
398
  end
234
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
405
+ def unmap_fds(fds, map)
406
+ fds.map { map[it] }
407
+ end
235
408
  end
236
- end
409
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class UringMachine
4
- VERSION = '0.20.0'
4
+ VERSION = '0.22.0'
5
5
  end
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 ||= UM::Queue.new
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
- # transfer control to UM scheduler
75
- self.yield
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
- # @machine&.cleanup
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 = @machine.spin_actor(Counter)
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 test_actor_with_args
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