From 4eb35ea7487ed801e0600d8dbe04ce3c1ad6843a Mon Sep 17 00:00:00 2001 From: Agent Date: Wed, 1 Apr 2026 21:47:17 +0000 Subject: [PATCH] feat(deferred): Improve default retry strategy - Change MAX_ATTEMPTS from 5 to 15 for ~2 days total retry time - Change backoff formula from exponential to polynomial: (attempt+1)**4 + 10 + rand(15) * (attempt+1) - Remove BACKOFF_INTERVAL constant (no longer needed) - Update tests to match new backoff behavior This gives developers more time to discover and fix underlying issues before retry attempts are exhausted. Closes #251 --- lib/rage/deferred/task.rb | 9 ++++----- spec/deferred/task_spec.rb | 25 ++++++++++++++++--------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/rage/deferred/task.rb b/lib/rage/deferred/task.rb index 820b98cd..d76b0e2e 100644 --- a/lib/rage/deferred/task.rb +++ b/lib/rage/deferred/task.rb @@ -31,12 +31,9 @@ # ``` # module Rage::Deferred::Task - MAX_ATTEMPTS = 5 + MAX_ATTEMPTS = 15 private_constant :MAX_ATTEMPTS - BACKOFF_INTERVAL = 5 - private_constant :BACKOFF_INTERVAL - # @private CONTEXT_KEY = :__rage_deferred_execution_context @@ -191,7 +188,9 @@ def __next_retry_in(attempts, exception) # @private def __default_backoff(attempt) - rand(BACKOFF_INTERVAL * 2**attempt) + 1 + # Use 1-indexed attempt for the formula as per issue #251 + retry_attempt = attempt + 1 + (retry_attempt**4) + 10 + (rand(15) * retry_attempt) end end end diff --git a/spec/deferred/task_spec.rb b/spec/deferred/task_spec.rb index 8cce2131..cfd71d81 100644 --- a/spec/deferred/task_spec.rb +++ b/spec/deferred/task_spec.rb @@ -44,17 +44,24 @@ def perform(arg, kwarg:) end describe ".__next_retry_in" do - it "returns the next retry interval with exponential backoff" do - expect(task_class.__next_retry_in(0, nil)).to be_between(1, 5) - expect(task_class.__next_retry_in(1, nil)).to be_between(1, 10) - expect(task_class.__next_retry_in(2, nil)).to be_between(1, 20) - expect(task_class.__next_retry_in(3, nil)).to be_between(1, 40) - expect(task_class.__next_retry_in(4, nil)).to be_between(1, 80) + it "returns the next retry interval with polynomial backoff" do + # New formula: (attempt+1)**4 + 10 + rand(15) * (attempt+1) + # attempt 0 -> 1^4 + 10 + rand(15)*1 = 11-25 + expect(task_class.__next_retry_in(0, nil)).to be_between(11, 25) + # attempt 1 -> 2^4 + 10 + rand(15)*2 = 26-54 + expect(task_class.__next_retry_in(1, nil)).to be_between(26, 54) + # attempt 2 -> 3^4 + 10 + rand(15)*3 = 91-133 + expect(task_class.__next_retry_in(2, nil)).to be_between(91, 133) + # attempt 3 -> 4^4 + 10 + rand(15)*4 = 266-322 + expect(task_class.__next_retry_in(3, nil)).to be_between(266, 322) + # attempt 4 -> 5^4 + 10 + rand(15)*5 = 635-705 + expect(task_class.__next_retry_in(4, nil)).to be_between(635, 705) end it "returns nil when attempts exceed max" do - expect(task_class.__next_retry_in(5, nil)).to be_between(1, 160) - expect(task_class.__next_retry_in(6, nil)).to be_nil + # With MAX_ATTEMPTS=15, attempt 15 should still return backoff + expect(task_class.__next_retry_in(15, nil)).to be_between(50635, 50845) + expect(task_class.__next_retry_in(16, nil)).to be_nil end end @@ -167,7 +174,7 @@ def perform(arg, kwarg:) it "__next_retry_in uses default backoff for unmatched" do interval = task_class.__next_retry_in(1, StandardError.new) - expect(interval).to be_between(1, 10) + expect(interval).to be_between(26, 54) end it "__next_retry_in enforces max_retries even with custom interval" do