Skip to content

Add StreamRetryPolicy.quadratic() with a back-off - #80

Merged
aleksandar-apostolov merged 6 commits into
developfrom
feature/quadratic-retry-policy
Sep 14, 2026
Merged

aleksandar-apostolov merged 6 commits into
developfrom
feature/quadratic-retry-policy

Conversation

@aleksandar-apostolov

@aleksandar-apostolov aleksandar-apostolov commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #79. Merge after parent.

Goal

Closes AND-1508 — keep the triangular back-off curve available under its own name.

#79 corrects exponential() to actually double, which retires the step * n(n+1)/2 curve it used to produce. That curve is a reasonable reconnect profile in its own right — it backs off harder early, which suits a caller that would rather wait than hammer a struggling server — and Feeds may want to keep it. This gives it a name instead of forcing callers into custom().

Worth being precise about the comparison, since it is easy to get backwards. Asymptotically doubling is the steeper curve, but over the range a retry loop actually covers quadratic() is the steeper one: at a 100 ms step it runs 100, 300, 600, 1000 against exponential()'s 100, 200, 400, 800. Doubling only overtakes at retry 5, and StreamRetryProcessorImpl throws on isLastAttempt before computing that delay, so with the default maxRetries = 5 the crossover is never reached. Expect longer waits than exponential(), not shorter.

Implementation

  • New StreamRetryPolicy.quadratic() factory, same parameter set and semantics as the other three.
  • nextBackOffDelayFunction is prev + retryIndex * backoffStepMillis, clamped to [backoffStepMillis, maxBackoffMillis] — the exact lambda exponential() carried before Make StreamRetryPolicy.exponential() be actually exponential #79, minus a coerceAtMost that the following coerceIn already subsumed.
  • Because it reads the previous delay, the curve depends on the retry loop feeding delays back in. KDoc says so explicitly, since exponential() no longer works that way.

With the default 250 ms step: 250, 750, 1500, 2500, 3750.

No consumer is migrated here — Feeds picks its own policy separately. The README retry section is being edited in #74, so quadratic() gets documented there once that lands.

Testing

./gradlew :stream-android-core:testDebugUnitTest — 756 tests, 1 failure in StreamCompositeEventSerializationImplTest, confirmed pre-existing on a clean develop worktree at e80f8a4.

13 new quadratic tests in StreamRetryPolicyTest: defaults, custom parameters, the triangular sequence across four retries, cap saturation at high retry counts, minimum clamping, giveUp behavior, and the five requireValid rejections mirroring the existing linear set.

One test pins all three curves side by side at a 100 ms step over five retries — linear 500, quadratic 1500, exponential 1600 — so a future change to any of them shows up as a relationship break, not just a number change.

Delays accumulated as step * n(n+1)/2 instead of doubling. Compute the
delay from the retry index as step * 2^(n-1), guarding the shift against
overflow at high retry counts.
Preserves the triangular curve that exponential() produced before it was
corrected, so callers that want step * n(n+1)/2 growth have a named
policy for it.
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled, or the PR is bot-authored.
  • An issue is linked (Linear ticket or GitHub issue), or the PR is bot-authored.

🎉 Great job! This PR is ready for review.

@aleksandar-apostolov aleksandar-apostolov changed the title Add StreamRetryPolicy.quadratic() back-off Add StreamRetryPolicy.quadratic() with back-off Sep 9, 2026
@aleksandar-apostolov
aleksandar-apostolov marked this pull request as ready for review September 9, 2026 12:32
@aleksandar-apostolov aleksandar-apostolov changed the title Add StreamRetryPolicy.quadratic() with back-off Add StreamRetryPolicy.quadratic() with a back-off Sep 9, 2026
The branch guard already bounds the shifted value, so the coerceIn could
never fire. Add a processor-level test that pins elapsed scheduler time
across five attempts, where the accumulation bug actually lived.
Removes the duplicated constructor block flagged by SonarCloud (14 lines,
7.7% of new code). custom() already clamps the lambda result to the same
bounds, so behaviour is unchanged.

@andremion andremion left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. One doc and naming thing inline, plus a test nit.

Quadratic is the steeper curve over retries 1-4 and only loses to doubling
at retry 5, which the default maxRetries never reaches. Reword the KDoc,
rename the comparison test and assert the crossover, and strengthen the
cap test to grow into the cap instead of restating a fixed point.
Base automatically changed from fix/and-1507-exponential-backoff to develop September 14, 2026 08:14
@sonarqubecloud

Copy link
Copy Markdown

@aleksandar-apostolov
aleksandar-apostolov merged commit 6193989 into develop Sep 14, 2026
9 checks passed
@aleksandar-apostolov
aleksandar-apostolov deleted the feature/quadratic-retry-policy branch September 14, 2026 08:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:new-feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants