ori-rb 0.4.2 → 0.4.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf224a1d5d0199cc58e2a4949a29f86ee513fcedb3abebd505c493c3e9287768
4
- data.tar.gz: 9c2ff8e9fb423ee3e541397ca67172d7940d9f8025ae8c4c1b90b92cf988fd13
3
+ metadata.gz: dd8326ee1d0ef86eff23c6b472b82ee72d2253e41d1367f053aeb878ed926320
4
+ data.tar.gz: 02b872fdd48ef913857e022f9333f5a447c59493a2dd5ccdbccee5486c479d0d
5
5
  SHA512:
6
- metadata.gz: 69ffdacdf5521d68c5106110ee13a69bc86c06a4aed4f461dd1e7c3809580873c5400ea5029ad03bca22504016463f4933a1ce229a27631c5a455ffc6b7bba83
7
- data.tar.gz: 5fa8eeadf0a5948e85df6cfd2e1fd705ce3a87647856c08bc20dade18722e93d5a03a142b291f8184081a3a30b18e4891d3ae7961737e7d72c87b5e13330b8ad
6
+ metadata.gz: 102ff1557da066713c20b08e87f29e01fff6c07978c9a384d657df6dfb2bfc4f6e95edb0f966e58b68ff44b4430232a95d23be846bba66838848792e8f3dcd86
7
+ data.tar.gz: 76e6f9e1e3ed9d2dfbf8078325560a4ca40dcc770c5a2ba6ec16e58ed3809203e18a15d859ead27d236f0d9439a342326b64fe31edf81ff426e1b85926ed5107
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.4.7
1
+ 4.0.1
data/lib/ori/lazy.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # typed: true
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Ori
4
5
  class Lazy
data/lib/ori/scope.rb CHANGED
@@ -1,8 +1,11 @@
1
1
  # typed: true
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "nio"
5
+ require "io/nonblock"
4
6
  require "random/formatter"
5
7
  require "ori/lazy"
8
+ require "English"
6
9
 
7
10
  module Ori
8
11
  class Scope
@@ -51,6 +54,11 @@ module Ori
51
54
  @cancelled = false
52
55
  @closed = false
53
56
 
57
+ # Cross-thread wakeup mechanism for unblock
58
+ @wakeup_mutex = ::Mutex.new
59
+ @wakeup_queue = [] #: Array[Fiber]
60
+ @wakeup_reader, @wakeup_writer = IO.pipe
61
+
54
62
  # Instead, use thread-local storage
55
63
  thread_local_state[object_id] = ThreadLocalState.new
56
64
 
@@ -228,19 +236,102 @@ module Ori
228
236
  end
229
237
 
230
238
  def unblock(blocker, fiber)
231
- unless fiber_ids.key?(Fiber.current)
239
+ unless fiber_ids.key?(fiber)
232
240
  return @parent_scope.unblock(blocker, fiber) if @parent_scope
233
241
  end
234
242
 
235
- resume_fiber(fiber)
243
+ # Thread-safe: enqueue the fiber and signal the event loop
244
+ # via the wakeup pipe. unblock may be called from any thread.
245
+ @wakeup_mutex.synchronize { @wakeup_queue << fiber }
246
+ @wakeup_writer.write_nonblock(".") rescue nil # rubocop:disable Style/RescueModifier
247
+ end
248
+
249
+ def io_read(io, buffer, length, offset)
250
+ return @parent_scope.io_read(io, buffer, length, offset) unless fiber_ids.key?(Fiber.current)
251
+
252
+ io.nonblock = true unless io.closed?
253
+ total = 0 #: Integer
254
+
255
+ loop do
256
+ maximum_size = buffer.size - offset - total
257
+ break if maximum_size <= 0
258
+
259
+ case result = Fiber.blocking { io.read_nonblock(maximum_size, exception: false) }
260
+ when :wait_readable
261
+ break if total > 0
262
+
263
+ io_wait(io, IO::READABLE)
264
+ when nil # EOF
265
+ break
266
+ else
267
+ buffer.set_string(result, offset + total)
268
+ total += result.bytesize
269
+ break if length == 0 || total >= length
270
+ end
271
+ end
272
+
273
+ total
274
+ rescue SystemCallError => e
275
+ (total || 0) > 0 ? total : -(e.errno || 0)
276
+ end
277
+
278
+ def io_write(io, buffer, length, offset)
279
+ return @parent_scope.io_write(io, buffer, length, offset) unless fiber_ids.key?(Fiber.current)
280
+
281
+ io.nonblock = true unless io.closed?
282
+ total = 0 #: Integer
283
+ max_bytes = buffer.size - offset
284
+
285
+ while total < max_bytes
286
+ chunk = buffer.get_string(offset + total, max_bytes - total)
287
+ case result = Fiber.blocking { io.write_nonblock(chunk, exception: false) }
288
+ when :wait_writable
289
+ io_wait(io, IO::WRITABLE)
290
+ else
291
+ total += result
292
+ end
293
+ end
294
+
295
+ total
296
+ rescue SystemCallError => e
297
+ (total || 0) > 0 ? total : -(e.errno || 0)
236
298
  end
237
299
 
238
- # def io_write(...) = ()
239
300
  # def io_pread(...) = ()
240
301
  # def io_pwrite(...) = ()
241
302
 
303
+ def process_wait(pid, flags)
304
+ return @parent_scope.process_wait(pid, flags) unless fiber_ids.key?(Fiber.current)
305
+
306
+ fiber = Fiber.current
307
+ id = fiber_ids[fiber]
308
+ @tracer&.record(id, :waiting_process, "pid=#{pid}")
309
+
310
+ # Bridge thread-based process waiting into the IO event loop.
311
+ # A pipe signals completion; the thread closes its end when done.
312
+ reader, writer = IO.pipe
313
+
314
+ thread = Thread.new(writer) do |w|
315
+ ::Process.wait(pid, flags)
316
+ $CHILD_STATUS # return the Process::Status
317
+ ensure
318
+ w.syswrite(".") rescue nil # rubocop:disable Style/RescueModifier
319
+ w.close rescue nil # rubocop:disable Style/RescueModifier
320
+ end
321
+
322
+ # Register directly on the event loop's readable set
323
+ readable[reader].add(fiber)
324
+ Fiber.yield
325
+
326
+ thread.value
327
+ ensure
328
+ readable[reader]&.delete(fiber) if reader
329
+ readable.delete(reader) if reader && readable[reader]&.empty?
330
+ reader&.close unless reader&.closed?
331
+ writer&.close unless writer&.closed?
332
+ end
333
+
242
334
  # TODO: Implement these
243
- # def process_wait(...) = ()
244
335
  # def timeout_after(...) = ()
245
336
  # def address_resolve(...) = ()
246
337
 
@@ -262,6 +353,7 @@ module Ori
262
353
  def pending_work?
263
354
  return false if closed?
264
355
 
356
+ return true if @wakeup_mutex.synchronize { @wakeup_queue.any? }
265
357
  return true if pending.any?(&:alive?)
266
358
  return true if waiting.any? { |fiber, _| fiber.alive? }
267
359
  return true if blocked.any? { |fiber, _| fiber.alive? }
@@ -362,9 +454,24 @@ module Ori
362
454
  end
363
455
 
364
456
  def process_io_operations(now = nil)
365
- return if readable.none? && writable.none?
457
+ has_io = readable.any? || writable.any?
458
+ has_wakeup = @wakeup_mutex.synchronize { @wakeup_queue.any? }
459
+
460
+ # Process any already-queued wakeups before selecting
461
+ drain_wakeup_queue if has_wakeup
462
+
463
+ return unless has_io
366
464
 
367
- readable_out, writable_out = IO.select(readable.keys, writable.keys, [], next_timeout(now))
465
+ select_readable = readable.keys
466
+ select_readable << @wakeup_reader
467
+
468
+ readable_out, writable_out = IO.select(select_readable, writable.keys, [], next_timeout(now))
469
+
470
+ # Drain wakeup pipe if signaled
471
+ if readable_out&.delete(@wakeup_reader)
472
+ @wakeup_reader.read_nonblock(256) rescue nil # rubocop:disable Style/RescueModifier
473
+ drain_wakeup_queue
474
+ end
368
475
 
369
476
  process_ready_io(readable_out, readable)
370
477
  process_ready_io(writable_out, writable)
@@ -378,10 +485,17 @@ module Ori
378
485
  end
379
486
  end
380
487
 
488
+ def drain_wakeup_queue
489
+ fibers = @wakeup_mutex.synchronize { @wakeup_queue.shift(@wakeup_queue.size) }
490
+ fibers.each { |fiber| resume_fiber(fiber) if fiber.alive? }
491
+ end
492
+
381
493
  def close_scope
382
494
  @closed = true
383
495
  @tracer&.record_scope(@scope_id, :closed)
384
496
  thread_local_state&.delete(object_id)
497
+ @wakeup_reader&.close unless @wakeup_reader&.closed?
498
+ @wakeup_writer&.close unless @wakeup_writer&.closed?
385
499
  end
386
500
 
387
501
  # Timeouts and deadlines
@@ -428,7 +542,7 @@ module Ori
428
542
  end
429
543
 
430
544
  def next_timeout(now = nil)
431
- timeouts = T.let([], T::Array[Numeric])
545
+ timeouts = [] #: Array[Numeric]
432
546
  timeouts.concat(waiting.values.compact) unless waiting.empty?
433
547
  timeouts << @deadline_at if @deadline_at
434
548
 
data/lib/ori/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # typed: strict
2
2
 
3
3
  module Ori
4
- VERSION = "0.4.2"
4
+ VERSION = "0.4.4"
5
5
  end
data/mise.toml CHANGED
@@ -1,5 +1,5 @@
1
1
  [tools]
2
- ruby = "3.4.7"
2
+ ruby = "4.0.1"
3
3
  usage = "latest"
4
4
  watchexec = "latest"
5
5
 
@@ -11,6 +11,13 @@ class Fiber
11
11
 
12
12
  sig { returns(T.untyped) }
13
13
  def current_scheduler; end
14
+
15
+ sig do
16
+ type_parameters(:T)
17
+ .params(block: T.proc.returns(T.type_parameter(:T)))
18
+ .returns(T.type_parameter(:T))
19
+ end
20
+ def blocking(&block); end
14
21
  end
15
22
 
16
23
  sig { params(block: T.proc.void).void }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ori-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jahfer Husain
@@ -152,7 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
152
  - !ruby/object:Gem::Version
153
153
  version: '0'
154
154
  requirements: []
155
- rubygems_version: 3.6.9
155
+ rubygems_version: 4.0.3
156
156
  specification_version: 4
157
157
  summary: A library for building concurrent applications.
158
158
  test_files: []