feat: Add optional exponential backoff for polling interval (#99)#139
Open
mem-5514-tahara wants to merge 7 commits into
Open
feat: Add optional exponential backoff for polling interval (#99)#139mem-5514-tahara wants to merge 7 commits into
mem-5514-tahara wants to merge 7 commits into
Conversation
When `useExponentialBackoff` is enabled, the retry interval grows exponentially on consecutive failures and resets to `checkInterval` on reconnect, reducing unnecessary battery drain and network traffic during prolonged outages. New `createInstance` parameters: - `useExponentialBackoff` (bool, default false) - `backoffInitialDelay` (Duration?, default: checkInterval) - `backoffMaxDelay` (Duration, default: 60s) - `backoffMultiplier` (double, default: 2.0) Closes OutdatedGuy#99
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an opt-in exponential backoff strategy to the InternetConnection.createInstance polling loop to reduce polling frequency during extended disconnects (while preserving existing behavior by default), and extends the test suite to cover the new timing behavior.
Changes:
- Introduces
useExponentialBackoffand related backoff configuration parameters onInternetConnection.createInstance. - Implements adaptive timer rescheduling in
_maybeEmitStatusUpdate()based on connection status and backoff state. - Adds a new
exponentialBackofftest group to validate backoff growth, capping, and reset behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
lib/src/internet_connection.dart |
Adds backoff configuration/state and uses it to compute the next polling delay. |
test/internet_connection_test.dart |
Adds tests intended to validate backoff timing and reset behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
As pointed out by Copilot, calling `setIntervalAndResetTimer` while already disconnected would cause the next poll to be treated as an ongoing failure (immediately multiplying the delay) because `previousStatus` remained disconnected. This commit introduces a `_backoffNeedsReset` flag to force the next poll to be treated as a "first failure" without mutating `_lastStatus` (which would have caused duplicate disconnected events). Also includes a new test to verify implicit initial delay synchronization.
- Update assertion for `backoffMultiplier` to require a value >= 1.0. Values between 0 and 1 would cause the polling delay to decay toward 0ms, leading to a tight polling loop during an outage.
- Add assertions to ensure `backoffInitialDelay` (or its fallback `checkInterval`) is greater than zero to prevent immediate/negative timer loops. - Add assertion to ensure `backoffInitialDelay` is less than or equal to `backoffMaxDelay`, keeping the backoff sequence logically sound and preventing the delay from shrinking on the second poll.
Repository owner
locked and limited conversation to collaborators
Jun 8, 2026
Repository owner
unlocked this conversation
Jun 8, 2026
…y on first failure
Replace always-true expect(sub, isNotNull) with real behavioral checks: verify call count (~4-5 in 550ms) and that consecutive gaps stay under 200ms, catching regressions where backoff activates unintentionally.
Author
|
Hi @OutdatedGuy! I’ve addressed the issues GitHub Copilot flagged, so could you please take another look at my proposal? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hi @OutdatedGuy! Thanks for maintaining this awesome package.
Closes #99
Problem
The
onStatusChangepolling loop retries at a fixedcheckInterval(default 10 s) regardless of connection state. During extended outages this results in unnecessary battery drain, wasted CPU cycles, and pointless network traffic on mobile devices.Analysis
Inspecting
_maybeEmitStatusUpdate()inlib/src/internet_connection.dartconfirmed the timer is always rescheduled with the same_checkInterval:Key design constraints identified before implementation:
_lastStatusis mutated inside_maybeEmitStatusUpdate, so distinguishing "first failure" from "ongoing failure" requires snapshotting its value before the update.useExponentialBackoff = false) to leave the singletonInternetConnection()and existing callers completely unaffected.setIntervalAndResetTimer(), and on last-listener cancel.backoffInitialDelayis not explicitly provided, it should track_checkIntervalso asetIntervalAndResetTimer()call does not leave a stale initial delay from construction time.Changes
Files modified:
lib/src/internet_connection.darttest/internet_connection_test.dartNew
createInstanceparametersuseExponentialBackoffboolfalsefalsepreserves existing behaviour exactlybackoffInitialDelayDuration?checkIntervalbackoffMaxDelayDurationDuration(seconds: 60)backoffMultiplierdouble2.0Backoff logic
previousStatusis snapshotted before the_lastStatusmutation so that the branch correctly identifies the first failure even whenpreviousStatusisnull(very first poll):Backoff state reset conditions
currentStatus == connected_currentBackoffDelayto_backoffInitialDelaysetIntervalAndResetTimer(duration)backoffInitialDelaywas not explicitly provided, also update_backoffInitialDelaytodurationbefore resetting_currentBackoffDelay; otherwise keep the caller-specified value_handleStatusChangeCancel)_currentBackoffDelayto_backoffInitialDelayso the next subscription starts cleanThe
backoffInitialDelaytracking is implemented with afinal bool _backoffInitialDelayExplicitflag set in the initializer list. Whenfalse,_backoffInitialDelayis updated insetIntervalAndResetTimerto stay in sync with_checkInterval, preventing a stale initial delay from lingering after the interval is changed dynamically.Usage example
Tests
Added
group('exponentialBackoff', ...)totest/internet_connection_test.dart. Tests usecustomConnectivityCheckwithDateTime.now()call logging to avoid real network calls and enable deterministic timing assertions.backoffInitialDelayis used on the first disconnectbackoffMaxDelaycheckIntervalafter reconnectbackoffInitialDelayafter interval changepreviousStatus == nullis treated as first failure, not ongoing