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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 982c11168837e41b1cb170b200732867a648c00546ef530b5f390da2436f4c63
4
- data.tar.gz: 42a74eeba76762d3ec9ed5f3dd5e379be15d941a7a04a4ee7431da732e03b3bf
3
+ metadata.gz: 03b2c0bd50b0d212d915829d9aab2bac8931cf15610fe450618911a06e6ba34c
4
+ data.tar.gz: 87e21b3dc2b88cd1b58de05c9f28a956e5a2488191eac190ae9c4fe8972466a0
5
5
  SHA512:
6
- metadata.gz: 200f9cb47a64c132688613fffb42b5a642c288f47004821e60ee451758c4fb241e9a2136e26ac23053db33e474bb9aad082949d03f7e04f1197fdc2fe09ead11
7
- data.tar.gz: 38817593a3040a762ad3fff44e18352dfa7c0367e47a3e8aa7434222ce059cedc58c113b249df39a0f93cecf4fe87189dccd25820a5d2a870b6448cebf791e17
6
+ metadata.gz: e4735eb0c3bb0850ce368358e152b7f442c4405cb8fe15f6168a3be4e92508697583cde21da979262dd0b848dd8fffb71a47ef4a62e25d9f3021140f74a23616
7
+ data.tar.gz: 22b75abdb0b5abd387f823aafcbb0e954ed5f791026d1d9c6dacfeb5a558cf9f31df9c320027d68f0887520ea3e006b5c135c9ca6fb50f763102ffac3b5c1f3a
data/README.md CHANGED
@@ -25,7 +25,7 @@ Ori provides a set of primitives that allow you to build concurrent applications
25
25
  ## Installation
26
26
 
27
27
  ```ruby
28
- gem "ori-rb", "~> 0.2"
28
+ gem "ori-rb", "~> 0.4.6"
29
29
  ```
30
30
 
31
31
  Then execute:
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
- inherit_or_set_deadline(deadline)
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
- # Public API
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
- def tag(name)
141
- @tracer&.record_scope(@scope_id, :tag, name)
142
- end
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
- # Scope lifecycle
408
-
409
- def inherit_or_set_deadline(duration)
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
- # TODO: This doesn't work because it only looks in the current scope, not the parents
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
- # Timeouts and deadlines
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
- if pending.empty? && waiting.empty? && readable.empty? && writable.empty?
581
- error = CancellationError.new(self, "All fibers are blocked, impossible to proceed")
582
- shutdown!(error)
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
- # Fiber management
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
- # Registration
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
- # Cleanup
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
- # Add helper method to access thread-local state
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
@@ -1,5 +1,5 @@
1
1
  # typed: strict
2
2
 
3
3
  module Ori
4
- VERSION = "0.4.5"
4
+ VERSION = "0.4.6"
5
5
  end
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
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.5
4
+ version: 0.4.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jahfer Husain