retriable 3.5.1 → 3.6.1
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/CHANGELOG.md +10 -0
- data/README.md +82 -80
- data/docs/superpowers/specs/2026-05-26-on-give-up-callback-followups-design.md +116 -0
- data/docs/testing.md +212 -0
- data/lib/retriable/config.rb +1 -0
- data/lib/retriable/validation.rb +41 -0
- data/lib/retriable/version.rb +1 -1
- data/lib/retriable.rb +39 -14
- data/spec/config_spec.rb +66 -0
- data/spec/retriable_spec.rb +285 -79
- metadata +3 -1
data/spec/retriable_spec.rb
CHANGED
|
@@ -9,7 +9,7 @@ describe Retriable do
|
|
|
9
9
|
|
|
10
10
|
before(:each) do
|
|
11
11
|
described_class.instance_variable_set(:@config, nil)
|
|
12
|
-
|
|
12
|
+
Thread.current.thread_variable_set(Retriable::OVERRIDE_THREAD_KEY, nil)
|
|
13
13
|
described_class.configure { |c| c.sleep_disabled = true }
|
|
14
14
|
@tries = 0
|
|
15
15
|
@next_interval_table = {}
|
|
@@ -406,6 +406,16 @@ describe Retriable do
|
|
|
406
406
|
|
|
407
407
|
expect(@tries).to eq(1)
|
|
408
408
|
end
|
|
409
|
+
|
|
410
|
+
it "rejects on: Object before invoking the block" do
|
|
411
|
+
block_invoked = false
|
|
412
|
+
|
|
413
|
+
expect do
|
|
414
|
+
described_class.retriable(on: Object) { block_invoked = true }
|
|
415
|
+
end.to raise_error(ArgumentError, /on must be an Exception class/)
|
|
416
|
+
|
|
417
|
+
expect(block_invoked).to be(false)
|
|
418
|
+
end
|
|
409
419
|
end
|
|
410
420
|
|
|
411
421
|
context "#configure" do
|
|
@@ -415,8 +425,7 @@ describe Retriable do
|
|
|
415
425
|
with_context
|
|
416
426
|
configure
|
|
417
427
|
config
|
|
418
|
-
|
|
419
|
-
reset_override
|
|
428
|
+
with_override
|
|
420
429
|
]
|
|
421
430
|
|
|
422
431
|
expect(described_class.singleton_methods(false)).to match_array(public_api_methods)
|
|
@@ -427,25 +436,23 @@ describe Retriable do
|
|
|
427
436
|
end
|
|
428
437
|
end
|
|
429
438
|
|
|
430
|
-
context "#
|
|
431
|
-
after(:each) do
|
|
432
|
-
described_class.reset_override
|
|
433
|
-
end
|
|
434
|
-
|
|
439
|
+
context "#with_override" do
|
|
435
440
|
it "takes precedence over both global config and local options" do
|
|
436
441
|
described_class.configure { |c| c.tries = 2 }
|
|
437
|
-
described_class.override(tries: 1)
|
|
438
442
|
|
|
439
|
-
|
|
443
|
+
described_class.with_override(tries: 1) do
|
|
444
|
+
expect { described_class.retriable(tries: 10) { increment_tries_with_exception } }.to raise_error(StandardError)
|
|
445
|
+
end
|
|
446
|
+
|
|
440
447
|
expect(@tries).to eq(1)
|
|
441
448
|
end
|
|
442
449
|
|
|
443
450
|
it "lets override tries take precedence over local intervals" do
|
|
444
|
-
described_class.
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
end
|
|
451
|
+
described_class.with_override(tries: 1) do
|
|
452
|
+
expect do
|
|
453
|
+
described_class.retriable(intervals: [0.5, 1.0]) { increment_tries_with_exception }
|
|
454
|
+
end.to raise_error(StandardError)
|
|
455
|
+
end
|
|
449
456
|
|
|
450
457
|
expect(@tries).to eq(1)
|
|
451
458
|
end
|
|
@@ -454,9 +461,11 @@ describe Retriable do
|
|
|
454
461
|
described_class.configure do |c|
|
|
455
462
|
c.contexts[:api] = { intervals: [0.5, 1.0] }
|
|
456
463
|
end
|
|
457
|
-
described_class.override(tries: 1)
|
|
458
464
|
|
|
459
|
-
|
|
465
|
+
described_class.with_override(tries: 1) do
|
|
466
|
+
expect { described_class.with_context(:api) { increment_tries_with_exception } }.to raise_error(StandardError)
|
|
467
|
+
end
|
|
468
|
+
|
|
460
469
|
expect(@tries).to eq(1)
|
|
461
470
|
end
|
|
462
471
|
|
|
@@ -464,31 +473,34 @@ describe Retriable do
|
|
|
464
473
|
described_class.configure do |c|
|
|
465
474
|
c.contexts[:api] = { intervals: [0.5, 1.0] }
|
|
466
475
|
end
|
|
467
|
-
described_class.override(contexts: { api: { tries: 1 } })
|
|
468
476
|
|
|
469
|
-
|
|
477
|
+
described_class.with_override(contexts: { api: { tries: 1 } }) do
|
|
478
|
+
expect { described_class.with_context(:api) { increment_tries_with_exception } }.to raise_error(StandardError)
|
|
479
|
+
end
|
|
480
|
+
|
|
470
481
|
expect(@tries).to eq(1)
|
|
471
482
|
end
|
|
472
483
|
|
|
473
484
|
it "replaces hash-valued options instead of deep-merging them" do
|
|
474
|
-
described_class.
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
end
|
|
485
|
+
described_class.with_override(on: { NonStandardError => nil }) do
|
|
486
|
+
expect do
|
|
487
|
+
described_class.retriable(on: { StandardError => nil }, tries: 2) { increment_tries_with_exception }
|
|
488
|
+
end.to raise_error(StandardError)
|
|
489
|
+
end
|
|
479
490
|
|
|
480
491
|
expect(@tries).to eq(1)
|
|
481
492
|
end
|
|
482
493
|
|
|
483
494
|
it "can override local intervals with nil to use configured backoff" do
|
|
484
495
|
described_class.configure { |c| c.tries = 3 }
|
|
485
|
-
described_class.override(intervals: nil)
|
|
486
496
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
497
|
+
described_class.with_override(intervals: nil) do
|
|
498
|
+
expect do
|
|
499
|
+
described_class.retriable(intervals: [0.5, 1.0], on_retry: time_table_handler) do
|
|
500
|
+
increment_tries_with_exception
|
|
501
|
+
end
|
|
502
|
+
end.to raise_error(StandardError)
|
|
503
|
+
end
|
|
492
504
|
|
|
493
505
|
expect(@tries).to eq(3)
|
|
494
506
|
expect(@next_interval_table[1]).to be_between(0.0, 1.0)
|
|
@@ -499,23 +511,26 @@ describe Retriable do
|
|
|
499
511
|
c.contexts[:api] = { tries: 3, base_interval: 1.0 }
|
|
500
512
|
end
|
|
501
513
|
|
|
502
|
-
described_class.
|
|
514
|
+
described_class.with_override(contexts: { api: { tries: 1 } }) do
|
|
515
|
+
described_class.with_context(:api, tries: 10) { increment_tries }
|
|
516
|
+
end
|
|
503
517
|
|
|
504
|
-
described_class.with_context(:api, tries: 10) { increment_tries }
|
|
505
518
|
expect(@tries).to eq(1)
|
|
506
519
|
end
|
|
507
520
|
|
|
508
521
|
it "can define a context only in override config" do
|
|
509
|
-
described_class.
|
|
522
|
+
described_class.with_override(contexts: { test_only: { tries: 1 } }) do
|
|
523
|
+
described_class.with_context(:test_only) { increment_tries }
|
|
524
|
+
end
|
|
510
525
|
|
|
511
|
-
described_class.with_context(:test_only) { increment_tries }
|
|
512
526
|
expect(@tries).to eq(1)
|
|
513
527
|
end
|
|
514
528
|
|
|
515
529
|
it "does not apply context-only overrides to plain retriable calls" do
|
|
516
|
-
described_class.
|
|
530
|
+
described_class.with_override(contexts: { api: { tries: 1 } }) do
|
|
531
|
+
expect { described_class.retriable(tries: 3) { increment_tries_with_exception } }.to raise_error(StandardError)
|
|
532
|
+
end
|
|
517
533
|
|
|
518
|
-
expect { described_class.retriable(tries: 3) { increment_tries_with_exception } }.to raise_error(StandardError)
|
|
519
534
|
expect(@tries).to eq(3)
|
|
520
535
|
end
|
|
521
536
|
|
|
@@ -523,21 +538,24 @@ describe Retriable do
|
|
|
523
538
|
described_class.configure do |c|
|
|
524
539
|
c.contexts[:api] = { tries: 3, on: NonStandardError }
|
|
525
540
|
end
|
|
526
|
-
described_class.override(tries: 1)
|
|
527
541
|
|
|
528
|
-
|
|
529
|
-
.
|
|
542
|
+
described_class.with_override(tries: 1) do
|
|
543
|
+
expect { described_class.with_context(:api) { increment_tries_with_exception(NonStandardError) } }
|
|
544
|
+
.to raise_error(NonStandardError)
|
|
545
|
+
end
|
|
546
|
+
|
|
530
547
|
expect(@tries).to eq(1)
|
|
531
548
|
end
|
|
532
549
|
|
|
533
550
|
it "combines local options with override-only contexts" do
|
|
534
|
-
described_class.
|
|
551
|
+
described_class.with_override(contexts: { api: { tries: 1 } }) do
|
|
552
|
+
expect do
|
|
553
|
+
described_class.with_context(:api, on: NonStandardError) do
|
|
554
|
+
increment_tries_with_exception(NonStandardError)
|
|
555
|
+
end
|
|
556
|
+
end.to raise_error(NonStandardError)
|
|
557
|
+
end
|
|
535
558
|
|
|
536
|
-
expect do
|
|
537
|
-
described_class.with_context(:api, on: NonStandardError) do
|
|
538
|
-
increment_tries_with_exception(NonStandardError)
|
|
539
|
-
end
|
|
540
|
-
end.to raise_error(NonStandardError)
|
|
541
559
|
expect(@tries).to eq(1)
|
|
542
560
|
end
|
|
543
561
|
|
|
@@ -546,9 +564,10 @@ describe Retriable do
|
|
|
546
564
|
c.contexts[:api] = { tries: 1 }
|
|
547
565
|
end
|
|
548
566
|
|
|
549
|
-
described_class.
|
|
567
|
+
described_class.with_override(tries: 1) do
|
|
568
|
+
described_class.with_context(:api) { increment_tries }
|
|
569
|
+
end
|
|
550
570
|
|
|
551
|
-
described_class.with_context(:api) { increment_tries }
|
|
552
571
|
expect(@tries).to eq(1)
|
|
553
572
|
end
|
|
554
573
|
|
|
@@ -556,9 +575,10 @@ describe Retriable do
|
|
|
556
575
|
begin
|
|
557
576
|
described_class.configure { |c| c.contexts = nil }
|
|
558
577
|
|
|
559
|
-
described_class.
|
|
578
|
+
described_class.with_override(contexts: { api: { tries: 1 } }) do
|
|
579
|
+
described_class.with_context(:api) { increment_tries }
|
|
580
|
+
end
|
|
560
581
|
|
|
561
|
-
described_class.with_context(:api) { increment_tries }
|
|
562
582
|
expect(@tries).to eq(1)
|
|
563
583
|
ensure
|
|
564
584
|
described_class.configure { |c| c.contexts = {} }
|
|
@@ -570,31 +590,54 @@ describe Retriable do
|
|
|
570
590
|
c.contexts[:api] = { tries: 1 }
|
|
571
591
|
end
|
|
572
592
|
|
|
573
|
-
described_class.
|
|
593
|
+
described_class.with_override(contexts: nil) do
|
|
594
|
+
described_class.with_context(:api) { increment_tries }
|
|
595
|
+
end
|
|
574
596
|
|
|
575
|
-
described_class.with_context(:api) { increment_tries }
|
|
576
597
|
expect(@tries).to eq(1)
|
|
577
598
|
end
|
|
578
599
|
|
|
579
|
-
it "
|
|
580
|
-
|
|
581
|
-
c.contexts[:api] = { tries: 1 }
|
|
582
|
-
end
|
|
600
|
+
it "raises ArgumentError on non-hash override contexts values" do
|
|
601
|
+
block_called = false
|
|
583
602
|
|
|
584
|
-
described_class.
|
|
603
|
+
expect { described_class.with_override(contexts: 123) { block_called = true } }
|
|
604
|
+
.to raise_error(ArgumentError, /contexts must be a Hash or nil/)
|
|
605
|
+
expect(block_called).to be(false)
|
|
606
|
+
end
|
|
585
607
|
|
|
586
|
-
|
|
587
|
-
|
|
608
|
+
it "raises ArgumentError on non-hash per-context override values" do
|
|
609
|
+
block_called = false
|
|
610
|
+
|
|
611
|
+
expect { described_class.with_override(contexts: { api: 123 }) { block_called = true } }
|
|
612
|
+
.to raise_error(ArgumentError, /contexts\[:api\] must be a Hash/)
|
|
613
|
+
expect(block_called).to be(false)
|
|
588
614
|
end
|
|
589
615
|
|
|
590
|
-
it "
|
|
616
|
+
it "preserves outer override after rejected nested override contexts values" do
|
|
617
|
+
described_class.with_override(tries: 2) do
|
|
618
|
+
expect { described_class.with_override(tries: 1, contexts: 123) { :noop } }
|
|
619
|
+
.to raise_error(ArgumentError, /contexts must be a Hash or nil/)
|
|
620
|
+
|
|
621
|
+
expect { described_class.retriable(tries: 10) { increment_tries_with_exception } }
|
|
622
|
+
.to raise_error(StandardError)
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
expect(@tries).to eq(2)
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
it "preserves outer context override after rejected nested per-context values" do
|
|
591
629
|
described_class.configure do |c|
|
|
592
|
-
c.contexts[:api] = { tries:
|
|
630
|
+
c.contexts[:api] = { tries: 10 }
|
|
593
631
|
end
|
|
594
632
|
|
|
595
|
-
described_class.
|
|
633
|
+
described_class.with_override(contexts: { api: { tries: 2 } }) do
|
|
634
|
+
expect { described_class.with_override(contexts: { api: 123 }) { :noop } }
|
|
635
|
+
.to raise_error(ArgumentError, /contexts\[:api\] must be a Hash/)
|
|
636
|
+
|
|
637
|
+
expect { described_class.with_context(:api) { increment_tries_with_exception } }
|
|
638
|
+
.to raise_error(StandardError)
|
|
639
|
+
end
|
|
596
640
|
|
|
597
|
-
expect { described_class.with_context(:api) { increment_tries_with_exception } }.to raise_error(StandardError)
|
|
598
641
|
expect(@tries).to eq(2)
|
|
599
642
|
end
|
|
600
643
|
|
|
@@ -603,10 +646,10 @@ describe Retriable do
|
|
|
603
646
|
c.contexts[:configured] = { tries: 2 }
|
|
604
647
|
end
|
|
605
648
|
|
|
606
|
-
described_class.
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
649
|
+
described_class.with_override(contexts: { override_only: { tries: 1 } }) do
|
|
650
|
+
expect { described_class.with_context(:missing) { increment_tries } }
|
|
651
|
+
.to raise_error(ArgumentError, /override_only/)
|
|
652
|
+
end
|
|
610
653
|
end
|
|
611
654
|
|
|
612
655
|
it "does not snapshot configured contexts when adding override-only contexts" do
|
|
@@ -614,37 +657,200 @@ describe Retriable do
|
|
|
614
657
|
c.contexts[:api] = { tries: 2 }
|
|
615
658
|
end
|
|
616
659
|
|
|
617
|
-
described_class.
|
|
660
|
+
described_class.with_override(contexts: { test_only: { tries: 1 } }) do
|
|
661
|
+
described_class.configure do |c|
|
|
662
|
+
c.contexts[:api] = { tries: 5 }
|
|
663
|
+
end
|
|
618
664
|
|
|
619
|
-
|
|
620
|
-
c.contexts[:api] = { tries: 5 }
|
|
665
|
+
expect { described_class.with_context(:api) { increment_tries_with_exception } }.to raise_error(StandardError)
|
|
621
666
|
end
|
|
622
667
|
|
|
623
|
-
expect { described_class.with_context(:api) { increment_tries_with_exception } }.to raise_error(StandardError)
|
|
624
668
|
expect(@tries).to eq(5)
|
|
625
669
|
end
|
|
626
670
|
|
|
627
671
|
it "raises ArgumentError on invalid override options" do
|
|
628
|
-
expect { described_class.
|
|
672
|
+
expect { described_class.with_override(does_not_exist: 123) { :noop } }.to raise_error(ArgumentError)
|
|
629
673
|
end
|
|
630
674
|
|
|
631
675
|
it "raises ArgumentError on empty override options" do
|
|
632
|
-
expect { described_class.
|
|
676
|
+
expect { described_class.with_override({}) { :noop } }.to raise_error(ArgumentError, /empty override/)
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
it "raises ArgumentError when called without a block" do
|
|
680
|
+
expect { described_class.with_override(tries: 1) }.to raise_error(ArgumentError, /requires a block/)
|
|
633
681
|
end
|
|
634
682
|
|
|
635
683
|
it "raises ArgumentError on invalid context override options" do
|
|
636
|
-
expect { described_class.
|
|
684
|
+
expect { described_class.with_override(contexts: { api: { does_not_exist: 123 } }) { :noop } }
|
|
637
685
|
.to raise_error(ArgumentError, /does_not_exist is not a valid option/)
|
|
638
686
|
end
|
|
639
687
|
|
|
640
|
-
it "
|
|
641
|
-
|
|
642
|
-
|
|
688
|
+
it "clears the override after the block returns" do
|
|
689
|
+
described_class.with_override(tries: 1) do
|
|
690
|
+
# active here
|
|
691
|
+
end
|
|
643
692
|
|
|
644
|
-
|
|
693
|
+
expect { described_class.retriable(tries: 3) { increment_tries_with_exception } }.to raise_error(StandardError)
|
|
694
|
+
expect(@tries).to eq(3)
|
|
695
|
+
end
|
|
645
696
|
|
|
646
|
-
|
|
647
|
-
expect
|
|
697
|
+
it "clears the override when the block raises" do
|
|
698
|
+
expect do
|
|
699
|
+
described_class.with_override(tries: 1) { raise "boom" }
|
|
700
|
+
end.to raise_error(RuntimeError, "boom")
|
|
701
|
+
|
|
702
|
+
expect { described_class.retriable(tries: 3) { increment_tries_with_exception } }.to raise_error(StandardError)
|
|
703
|
+
expect(@tries).to eq(3)
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
it "returns the block's return value" do
|
|
707
|
+
result = described_class.with_override(tries: 1) { :return_value }
|
|
708
|
+
expect(result).to eq(:return_value)
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
it "restores the outer override when nested blocks exit" do
|
|
712
|
+
tries_seen = []
|
|
713
|
+
handler = ->(_exception, try, _elapsed, _next) { tries_seen << [Thread.current.object_id, try] }
|
|
714
|
+
|
|
715
|
+
described_class.with_override(tries: 2, on_retry: handler) do
|
|
716
|
+
described_class.with_override(tries: 4, on_retry: handler) do
|
|
717
|
+
expect { described_class.retriable { increment_tries_with_exception } }.to raise_error(StandardError)
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
# After the inner block exits, the outer tries: 2 override is restored.
|
|
721
|
+
@tries = 0
|
|
722
|
+
expect { described_class.retriable { increment_tries_with_exception } }.to raise_error(StandardError)
|
|
723
|
+
expect(@tries).to eq(2)
|
|
724
|
+
end
|
|
725
|
+
end
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
context "#with_override thread safety" do
|
|
729
|
+
# Coordinate threads with queues rather than sleep so tests are deterministic.
|
|
730
|
+
# sleep_disabled is already set to true in the top-level before(:each), so
|
|
731
|
+
# retriable calls do not actually sleep between attempts.
|
|
732
|
+
|
|
733
|
+
it "isolates overrides between threads" do
|
|
734
|
+
ready = Queue.new
|
|
735
|
+
proceed = Queue.new
|
|
736
|
+
results = {}
|
|
737
|
+
mutex = Mutex.new
|
|
738
|
+
|
|
739
|
+
threads = [1, 2].map do |id|
|
|
740
|
+
Thread.new do
|
|
741
|
+
described_class.with_override(tries: id) do
|
|
742
|
+
ready << true
|
|
743
|
+
proceed.pop
|
|
744
|
+
tries = 0
|
|
745
|
+
begin
|
|
746
|
+
described_class.retriable do
|
|
747
|
+
tries += 1
|
|
748
|
+
raise StandardError
|
|
749
|
+
end
|
|
750
|
+
rescue StandardError
|
|
751
|
+
mutex.synchronize { results[id] = tries }
|
|
752
|
+
end
|
|
753
|
+
end
|
|
754
|
+
end
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
2.times { ready.pop }
|
|
758
|
+
2.times { proceed << true }
|
|
759
|
+
threads.each(&:join)
|
|
760
|
+
|
|
761
|
+
expect(results).to eq(1 => 1, 2 => 2)
|
|
762
|
+
end
|
|
763
|
+
|
|
764
|
+
it "does not leak an active override into a sibling thread" do
|
|
765
|
+
override_active = Queue.new
|
|
766
|
+
sibling_done = Queue.new
|
|
767
|
+
sibling_tries = nil
|
|
768
|
+
|
|
769
|
+
setter = Thread.new do
|
|
770
|
+
described_class.with_override(tries: 1) do
|
|
771
|
+
override_active << true
|
|
772
|
+
sibling_done.pop
|
|
773
|
+
end
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
sibling = Thread.new do
|
|
777
|
+
override_active.pop
|
|
778
|
+
tries = 0
|
|
779
|
+
begin
|
|
780
|
+
described_class.retriable(tries: 3) do
|
|
781
|
+
tries += 1
|
|
782
|
+
raise StandardError
|
|
783
|
+
end
|
|
784
|
+
rescue StandardError
|
|
785
|
+
sibling_tries = tries
|
|
786
|
+
end
|
|
787
|
+
sibling_done << true
|
|
788
|
+
end
|
|
789
|
+
|
|
790
|
+
[setter, sibling].each(&:join)
|
|
791
|
+
expect(sibling_tries).to eq(3)
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
it "does not propagate an active override to a child thread" do
|
|
795
|
+
child_tries = nil
|
|
796
|
+
|
|
797
|
+
described_class.with_override(tries: 1) do
|
|
798
|
+
Thread.new do
|
|
799
|
+
tries = 0
|
|
800
|
+
begin
|
|
801
|
+
described_class.retriable(tries: 3) do
|
|
802
|
+
tries += 1
|
|
803
|
+
raise StandardError
|
|
804
|
+
end
|
|
805
|
+
rescue StandardError
|
|
806
|
+
child_tries = tries
|
|
807
|
+
end
|
|
808
|
+
end.join
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
expect(child_tries).to eq(3)
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
it "shares the active override with fibers in the same thread" do
|
|
815
|
+
fiber_tries = nil
|
|
816
|
+
|
|
817
|
+
Thread.new do
|
|
818
|
+
described_class.with_override(tries: 1) do
|
|
819
|
+
Fiber.new do
|
|
820
|
+
tries = 0
|
|
821
|
+
begin
|
|
822
|
+
described_class.retriable(tries: 10) do
|
|
823
|
+
tries += 1
|
|
824
|
+
raise StandardError
|
|
825
|
+
end
|
|
826
|
+
rescue StandardError
|
|
827
|
+
fiber_tries = tries
|
|
828
|
+
end
|
|
829
|
+
end.resume
|
|
830
|
+
end
|
|
831
|
+
end.join
|
|
832
|
+
|
|
833
|
+
expect(fiber_tries).to eq(1)
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
it "does not treat a main-thread override as a global default for other threads" do
|
|
837
|
+
other_thread_tries = nil
|
|
838
|
+
|
|
839
|
+
described_class.with_override(tries: 1) do
|
|
840
|
+
Thread.new do
|
|
841
|
+
tries = 0
|
|
842
|
+
begin
|
|
843
|
+
described_class.retriable(tries: 3) do
|
|
844
|
+
tries += 1
|
|
845
|
+
raise StandardError
|
|
846
|
+
end
|
|
847
|
+
rescue StandardError
|
|
848
|
+
other_thread_tries = tries
|
|
849
|
+
end
|
|
850
|
+
end.join
|
|
851
|
+
end
|
|
852
|
+
|
|
853
|
+
expect(other_thread_tries).to eq(3)
|
|
648
854
|
end
|
|
649
855
|
end
|
|
650
856
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: retriable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.6.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jack Chu
|
|
@@ -74,6 +74,8 @@ files:
|
|
|
74
74
|
- Rakefile
|
|
75
75
|
- bin/console
|
|
76
76
|
- bin/setup
|
|
77
|
+
- docs/superpowers/specs/2026-05-26-on-give-up-callback-followups-design.md
|
|
78
|
+
- docs/testing.md
|
|
77
79
|
- lib/retriable.rb
|
|
78
80
|
- lib/retriable/config.rb
|
|
79
81
|
- lib/retriable/core_ext/kernel.rb
|