ori-rb 0.4.5 → 0.4.6
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/README.md +1 -1
- data/lib/ori/scope.rb +101 -80
- data/lib/ori/version.rb +1 -1
- data/lib/ori.rb +9 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 03b2c0bd50b0d212d915829d9aab2bac8931cf15610fe450618911a06e6ba34c
|
|
4
|
+
data.tar.gz: 87e21b3dc2b88cd1b58de05c9f28a956e5a2488191eac190ae9c4fe8972466a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e4735eb0c3bb0850ce368358e152b7f442c4405cb8fe15f6168a3be4e92508697583cde21da979262dd0b848dd8fffb71a47ef4a62e25d9f3021140f74a23616
|
|
7
|
+
data.tar.gz: 22b75abdb0b5abd387f823aafcbb0e954ed5f791026d1d9c6dacfeb5a558cf9f31df9c320027d68f0887520ea3e006b5c135c9ca6fb50f763102ffac3b5c1f3a
|
data/README.md
CHANGED
data/lib/ori/scope.rb
CHANGED
|
@@ -9,7 +9,6 @@ require "English"
|
|
|
9
9
|
|
|
10
10
|
module Ori
|
|
11
11
|
class Scope
|
|
12
|
-
# Add thread-local state management
|
|
13
12
|
class ThreadLocalState
|
|
14
13
|
attr_reader :fiber_ids,
|
|
15
14
|
:tasks,
|
|
@@ -54,16 +53,14 @@ module Ori
|
|
|
54
53
|
@cancelled = false
|
|
55
54
|
@closed = false
|
|
56
55
|
|
|
57
|
-
# Cross-thread wakeup mechanism for unblock and fiber_interrupt
|
|
58
56
|
@wakeup_mutex = ::Mutex.new
|
|
59
57
|
@wakeup_queue = [] #: Array[Fiber]
|
|
60
58
|
@pending_interrupts = {} #: Hash[Fiber, Exception]
|
|
61
59
|
@wakeup_reader, @wakeup_writer = IO.pipe
|
|
62
60
|
|
|
63
|
-
# Instead, use thread-local storage
|
|
64
61
|
thread_local_state[object_id] = ThreadLocalState.new
|
|
65
62
|
|
|
66
|
-
|
|
63
|
+
inherit_or_register_deadline(deadline)
|
|
67
64
|
|
|
68
65
|
if @tracer
|
|
69
66
|
@scope_id = Random.uuid_v7(extra_timestamp_bits: 12)
|
|
@@ -80,15 +77,14 @@ module Ori
|
|
|
80
77
|
process_available_work
|
|
81
78
|
Fiber.yield if parent_scope && pending_work?
|
|
82
79
|
end
|
|
83
|
-
rescue Interrupt => error
|
|
84
|
-
puts "Scope interrupted! Shutting down..."
|
|
85
|
-
shutdown!(error)
|
|
86
80
|
ensure
|
|
87
81
|
close_scope
|
|
88
82
|
@parent_scope&.deregister_child_scope(self)
|
|
89
83
|
end
|
|
90
84
|
|
|
91
|
-
#
|
|
85
|
+
# -------------------
|
|
86
|
+
# --- Public API ---
|
|
87
|
+
# -------------------
|
|
92
88
|
|
|
93
89
|
def fork(&block)
|
|
94
90
|
task = create_task(&block)
|
|
@@ -137,19 +133,9 @@ module Ori
|
|
|
137
133
|
raise(cause || exn)
|
|
138
134
|
end
|
|
139
135
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def print_ascii_trace
|
|
145
|
-
@tracer&.visualize
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def write_html_trace(directory)
|
|
149
|
-
@tracer&.write_timeline_data(directory)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# Ruby FiberScheduler interface implementation
|
|
136
|
+
# ----------------------------------------------------
|
|
137
|
+
# --- Ruby FiberScheduler interface implementation ---
|
|
138
|
+
# ----------------------------------------------------
|
|
153
139
|
|
|
154
140
|
def fiber(&block)
|
|
155
141
|
task = fork(&block)
|
|
@@ -236,6 +222,9 @@ module Ori
|
|
|
236
222
|
return @parent_scope.block(...) if @parent_scope
|
|
237
223
|
end
|
|
238
224
|
|
|
225
|
+
# TODO: Track blocked fibers separately so we don't
|
|
226
|
+
# try to resume them indefinitely prior to unblock
|
|
227
|
+
|
|
239
228
|
Fiber.yield
|
|
240
229
|
end
|
|
241
230
|
|
|
@@ -309,9 +298,6 @@ module Ori
|
|
|
309
298
|
(total || 0) > 0 ? total : -(e.errno || 0)
|
|
310
299
|
end
|
|
311
300
|
|
|
312
|
-
# def io_pread(...) = ()
|
|
313
|
-
# def io_pwrite(...) = ()
|
|
314
|
-
|
|
315
301
|
def process_wait(pid, flags)
|
|
316
302
|
return @parent_scope.process_wait(pid, flags) unless fiber_ids.key?(Fiber.current)
|
|
317
303
|
|
|
@@ -344,6 +330,8 @@ module Ori
|
|
|
344
330
|
end
|
|
345
331
|
|
|
346
332
|
# TODO: Implement these
|
|
333
|
+
# def io_pread(...) = ()
|
|
334
|
+
# def io_pwrite(...) = ()
|
|
347
335
|
# def timeout_after(...) = ()
|
|
348
336
|
# def address_resolve(...) = ()
|
|
349
337
|
|
|
@@ -376,6 +364,24 @@ module Ori
|
|
|
376
364
|
false
|
|
377
365
|
end
|
|
378
366
|
|
|
367
|
+
def root_scope
|
|
368
|
+
@parent_scope ? @parent_scope.root_scope : self
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# Purposefully excludes blocked fibers from checks
|
|
372
|
+
def has_active_work?
|
|
373
|
+
return false if closed?
|
|
374
|
+
|
|
375
|
+
return true if @wakeup_mutex.synchronize { @wakeup_queue.any? }
|
|
376
|
+
return true if pending.any?(&:alive?)
|
|
377
|
+
return true if waiting.any? { |fiber, _| fiber.alive? }
|
|
378
|
+
return true if readable.any? { |_, fibers| fibers.any?(&:alive?) }
|
|
379
|
+
return true if writable.any? { |_, fibers| fibers.any?(&:alive?) }
|
|
380
|
+
return true if child_scopes? && child_scopes.any? { |scope| scope.has_active_work? }
|
|
381
|
+
|
|
382
|
+
false
|
|
383
|
+
end
|
|
384
|
+
|
|
379
385
|
def register_child_scope(scope)
|
|
380
386
|
child_scopes.add(scope)
|
|
381
387
|
end
|
|
@@ -404,20 +410,9 @@ module Ori
|
|
|
404
410
|
state.child_scopes?
|
|
405
411
|
end
|
|
406
412
|
|
|
407
|
-
#
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
parent_deadline = parent_scope&.remaining_deadline
|
|
411
|
-
|
|
412
|
-
if parent_deadline && (duration.nil? || parent_deadline < duration)
|
|
413
|
-
# Inherit parent's deadline
|
|
414
|
-
@deadline_at = current_time + parent_deadline
|
|
415
|
-
@deadline_owner = parent_scope.deadline_owner
|
|
416
|
-
elsif duration
|
|
417
|
-
@deadline_at = current_time + duration
|
|
418
|
-
@deadline_owner = self
|
|
419
|
-
end
|
|
420
|
-
end
|
|
413
|
+
# -----------------------
|
|
414
|
+
# --- Scope lifecycle ---
|
|
415
|
+
# -----------------------
|
|
421
416
|
|
|
422
417
|
def process_available_work
|
|
423
418
|
now = current_time
|
|
@@ -456,8 +451,7 @@ module Ori
|
|
|
456
451
|
end
|
|
457
452
|
end
|
|
458
453
|
|
|
459
|
-
|
|
460
|
-
# check_stalled_fibers! if fibers_to_resume.empty?
|
|
454
|
+
check_stalled_fibers! if fibers_to_resume.empty?
|
|
461
455
|
|
|
462
456
|
fibers_to_resume.each do |fiber|
|
|
463
457
|
blocked.delete(fiber)
|
|
@@ -498,7 +492,7 @@ module Ori
|
|
|
498
492
|
end
|
|
499
493
|
|
|
500
494
|
def drain_wakeup_queue
|
|
501
|
-
interrupts = nil
|
|
495
|
+
interrupts = nil #: Hash[Fiber, Exception]?
|
|
502
496
|
fibers = @wakeup_mutex.synchronize do
|
|
503
497
|
unless @pending_interrupts.empty?
|
|
504
498
|
interrupts = @pending_interrupts.dup
|
|
@@ -518,21 +512,6 @@ module Ori
|
|
|
518
512
|
end
|
|
519
513
|
end
|
|
520
514
|
|
|
521
|
-
def interrupt_fiber(fiber, exception)
|
|
522
|
-
id = fiber_ids[fiber]
|
|
523
|
-
@tracer&.record(id, :interrupted, exception.message)
|
|
524
|
-
|
|
525
|
-
# Remove from wait states before cancelling
|
|
526
|
-
waiting.delete(fiber)
|
|
527
|
-
blocked.delete(fiber)
|
|
528
|
-
|
|
529
|
-
if (task = task_queue[fiber])
|
|
530
|
-
task.cancel(exception)
|
|
531
|
-
else
|
|
532
|
-
fiber.kill
|
|
533
|
-
end
|
|
534
|
-
end
|
|
535
|
-
|
|
536
515
|
def close_scope
|
|
537
516
|
@closed = true
|
|
538
517
|
@tracer&.record_scope(@scope_id, :closed)
|
|
@@ -541,7 +520,9 @@ module Ori
|
|
|
541
520
|
@wakeup_writer&.close unless @wakeup_writer&.closed?
|
|
542
521
|
end
|
|
543
522
|
|
|
544
|
-
#
|
|
523
|
+
# ------------------------------
|
|
524
|
+
# --- Timeouts and deadlines ---
|
|
525
|
+
# ------------------------------
|
|
545
526
|
|
|
546
527
|
def current_time
|
|
547
528
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
@@ -576,12 +557,11 @@ module Ori
|
|
|
576
557
|
|
|
577
558
|
def check_stalled_fibers!
|
|
578
559
|
return false if blocked.none?
|
|
560
|
+
return false if root_scope.has_active_work?
|
|
579
561
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
raise(error)
|
|
584
|
-
end
|
|
562
|
+
error = DeadlockError.new(self)
|
|
563
|
+
shutdown!(error)
|
|
564
|
+
raise(error)
|
|
585
565
|
end
|
|
586
566
|
|
|
587
567
|
def next_timeout(now = nil)
|
|
@@ -599,7 +579,9 @@ module Ori
|
|
|
599
579
|
[0, delay].max
|
|
600
580
|
end
|
|
601
581
|
|
|
602
|
-
#
|
|
582
|
+
# ------------------------
|
|
583
|
+
# --- Fiber management ---
|
|
584
|
+
# ------------------------
|
|
603
585
|
|
|
604
586
|
def create_task(&block)
|
|
605
587
|
return false if @cancelled
|
|
@@ -610,16 +592,6 @@ module Ori
|
|
|
610
592
|
task
|
|
611
593
|
end
|
|
612
594
|
|
|
613
|
-
def register_task(task)
|
|
614
|
-
fiber_ids[task.fiber] = task.id
|
|
615
|
-
task_queue[task.fiber] = task
|
|
616
|
-
|
|
617
|
-
if @tracer
|
|
618
|
-
@tracer.register_fiber(task.id, @scope_id)
|
|
619
|
-
@tracer.record(task.id, :created)
|
|
620
|
-
end
|
|
621
|
-
end
|
|
622
|
-
|
|
623
595
|
def resume_fiber(fiber)
|
|
624
596
|
resume_task_or_fiber(task_queue.fetch(fiber, fiber))
|
|
625
597
|
end
|
|
@@ -655,6 +627,13 @@ module Ori
|
|
|
655
627
|
@tracer&.record(id, :completed) unless fiber.alive?
|
|
656
628
|
end
|
|
657
629
|
|
|
630
|
+
def interrupt_fiber(fiber, exception)
|
|
631
|
+
waiting.delete(fiber)
|
|
632
|
+
blocked.delete(fiber)
|
|
633
|
+
|
|
634
|
+
cancel_fiber!(fiber, exception)
|
|
635
|
+
end
|
|
636
|
+
|
|
658
637
|
def cancel_fiber!(fiber, error)
|
|
659
638
|
return unless fiber.alive?
|
|
660
639
|
|
|
@@ -664,15 +643,38 @@ module Ori
|
|
|
664
643
|
if (task = task_queue[fiber])
|
|
665
644
|
task.cancel(error)
|
|
666
645
|
else
|
|
667
|
-
# For raw fibers, we still need to resume them one last time
|
|
668
|
-
# to give them a chance to cleanup
|
|
669
646
|
fiber.raise(error)
|
|
670
647
|
end
|
|
671
648
|
|
|
672
649
|
@tracer&.record(id, :cancelled, error.message)
|
|
673
650
|
end
|
|
674
651
|
|
|
675
|
-
#
|
|
652
|
+
# --------------------
|
|
653
|
+
# --- Registration ---
|
|
654
|
+
# --------------------
|
|
655
|
+
|
|
656
|
+
def inherit_or_register_deadline(duration)
|
|
657
|
+
parent_deadline = parent_scope&.remaining_deadline
|
|
658
|
+
|
|
659
|
+
if parent_deadline && (duration.nil? || parent_deadline < duration)
|
|
660
|
+
# Inherit parent's deadline
|
|
661
|
+
@deadline_at = current_time + parent_deadline
|
|
662
|
+
@deadline_owner = parent_scope.deadline_owner
|
|
663
|
+
elsif duration
|
|
664
|
+
@deadline_at = current_time + duration
|
|
665
|
+
@deadline_owner = self
|
|
666
|
+
end
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
def register_task(task)
|
|
670
|
+
fiber_ids[task.fiber] = task.id
|
|
671
|
+
task_queue[task.fiber] = task
|
|
672
|
+
|
|
673
|
+
if @tracer
|
|
674
|
+
@tracer.register_fiber(task.id, @scope_id)
|
|
675
|
+
@tracer.record(task.id, :created)
|
|
676
|
+
end
|
|
677
|
+
end
|
|
676
678
|
|
|
677
679
|
def register_timeout(fiber, deadline)
|
|
678
680
|
return unless deadline
|
|
@@ -699,7 +701,9 @@ module Ori
|
|
|
699
701
|
added
|
|
700
702
|
end
|
|
701
703
|
|
|
702
|
-
#
|
|
704
|
+
# ---------------
|
|
705
|
+
# --- Cleanup ---
|
|
706
|
+
# ---------------
|
|
703
707
|
|
|
704
708
|
def cleanup_dead_fibers
|
|
705
709
|
dead_fibers = fiber_ids.reject { |fiber, _| fiber.alive? }.to_set
|
|
@@ -749,14 +753,15 @@ module Ori
|
|
|
749
753
|
state&.waiting&.delete(fiber)
|
|
750
754
|
end
|
|
751
755
|
|
|
752
|
-
#
|
|
756
|
+
# -------------
|
|
757
|
+
# --- State ---
|
|
758
|
+
# -------------
|
|
759
|
+
|
|
753
760
|
def state
|
|
754
761
|
thread_local_state&.[](object_id) or
|
|
755
762
|
raise "Scope accessed from wrong thread"
|
|
756
763
|
end
|
|
757
764
|
|
|
758
|
-
# Update all instance variable references to use state
|
|
759
|
-
|
|
760
765
|
#: () -> LazyHash
|
|
761
766
|
def task_queue = state.tasks
|
|
762
767
|
|
|
@@ -777,5 +782,21 @@ module Ori
|
|
|
777
782
|
|
|
778
783
|
#: () -> Set[Scope]
|
|
779
784
|
def child_scopes = state.child_scopes
|
|
785
|
+
|
|
786
|
+
# -----------------
|
|
787
|
+
# --- Debugging ---
|
|
788
|
+
# -----------------
|
|
789
|
+
|
|
790
|
+
def tag(name)
|
|
791
|
+
@tracer&.record_scope(@scope_id, :tag, name)
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
def print_ascii_trace
|
|
795
|
+
@tracer&.visualize
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
def write_html_trace(directory)
|
|
799
|
+
@tracer&.write_timeline_data(directory)
|
|
800
|
+
end
|
|
780
801
|
end
|
|
781
802
|
end
|
data/lib/ori/version.rb
CHANGED
data/lib/ori.rb
CHANGED
|
@@ -6,7 +6,6 @@ loader.setup
|
|
|
6
6
|
|
|
7
7
|
module Ori
|
|
8
8
|
class CancellationError < StandardError
|
|
9
|
-
|
|
10
9
|
#: Scope
|
|
11
10
|
attr_reader :scope
|
|
12
11
|
|
|
@@ -17,6 +16,13 @@ module Ori
|
|
|
17
16
|
end
|
|
18
17
|
end
|
|
19
18
|
|
|
19
|
+
class DeadlockError < CancellationError
|
|
20
|
+
#: (Scope scope, ?String? message) -> void
|
|
21
|
+
def initialize(scope, message = "All fibers are blocked, impossible to proceed")
|
|
22
|
+
super(scope, message)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
20
26
|
class << self
|
|
21
27
|
#: (?name: String?, ?cancel_after: Numeric?, ?raise_after: Numeric?, ?trace: bool) { (Scope) -> void } -> Scope
|
|
22
28
|
def sync(name: nil, cancel_after: nil, raise_after: nil, trace: false, &block)
|
|
@@ -41,6 +47,8 @@ module Ori
|
|
|
41
47
|
|
|
42
48
|
scope.await
|
|
43
49
|
scope
|
|
50
|
+
rescue DeadlockError
|
|
51
|
+
raise
|
|
44
52
|
rescue CancellationError => error
|
|
45
53
|
# Re-raise if:
|
|
46
54
|
# 1. The error is from a different scope, or
|