Skip to content

Make StreamRetryPolicy.exponential() be actually exponential - #79

Merged
aleksandar-apostolov merged 2 commits into
developfrom
fix/and-1507-exponential-backoff
Sep 14, 2026
Merged

aleksandar-apostolov merged 2 commits into
developfrom
fix/and-1507-exponential-backoff

Conversation

@aleksandar-apostolov

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

Copy link
Copy Markdown
Collaborator

Goal

Fixes AND-1507StreamRetryPolicy.exponential() produced quadratic growth, not exponential.

nextBackOffDelayFunction was prev + retry * backoffStepMillis, and StreamRetryProcessorImpl feeds the previous delay back in, so delays accumulated as step * n(n+1)/2. With the default 250 ms step that is 250, 750, 1500, 2500, 3750 where both the KDoc and the README promise 250, 500, 1000, 2000.

Implementation

  • exponential() now derives the delay from the retry index alone: backoffStepMillis * 2^(retryIndex - 1), clamped to [backoffStepMillis, maxBackoffMillis].
  • The shift is guarded against overflow — a step that cannot be shifted under the cap returns maxBackoffMillis directly, so high retry counts saturate instead of wrapping negative.
  • KDoc corrected: the formula line now matches the code, and the worked example is extended to a fourth retry.
  • linear() and fixed() are unchanged.

Behavior change for consumers of @StreamInternalApi: delays are shorter through retry 4 and only longer from retry 5, so callers retry faster than before in the common case. stream-feeds-android uses exponential(maxRetries = 3) in FeedOwnValuesRepositoryImpl and FeedWatchHandler — that sleeps twice before giving up, so its total wait drops from 1000 ms to 750 ms.

Testing

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

StreamRetryPolicyTest (36 tests, all green) updated and extended:

  • exponential backoff delay calculation increases exponentially now asserts 100/200/400/800 for a 100 ms step.
  • exponential backoff delay does not overflow on very high retry counts covers retry 64 and Int.MAX_VALUE.
  • exponential backoff delay ignores the previous delay pins the new index-based contract.

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.
@aleksandar-apostolov aleksandar-apostolov added the pr:bug Bug fix label Sep 9, 2026
@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.

@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 8ea9f22a-a529-4e3b-8dd9-666f508ffa3a


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@aleksandar-apostolov aleksandar-apostolov changed the title Make StreamRetryPolicy.exponential() actually exponential Make StreamRetryPolicy.exponential() be actually exponential Sep 9, 2026

@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. Two nits inline, both optional.

One thing on the description: the behavior note is backwards. New delays are shorter up to retry 4 and only longer from retry 5, so feeds retries faster now, not slower (750 ms total before giving up instead of 1000 ms). Mind correcting that line?

Not for this PR, but two follow-ups if you want them: the README exponential example still uses StreamRetryPolicy.Exponential and maxDelayMillis, which don't exist, and stream-video-android has a copy of this class with the old formula plus a KDoc note saying it matches core deliberately. Happy to open tickets.

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.
@aleksandar-apostolov

aleksandar-apostolov commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks — all three were right.

@sonarqubecloud

Copy link
Copy Markdown

@aleksandar-apostolov
aleksandar-apostolov merged commit dd64493 into develop Sep 14, 2026
11 checks passed
@aleksandar-apostolov
aleksandar-apostolov deleted the fix/and-1507-exponential-backoff branch September 14, 2026 08:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants