Skip to content

feat: add WithDisablePolling and WithSynchronousFetchOnInitialisation options - #267

Closed
sukesh2000 wants to merge 6 commits into
Unleash:v6from
sukesh2000:feat/disable-polling-synchronous-fetch
Closed

feat: add WithDisablePolling and WithSynchronousFetchOnInitialisation options#267
sukesh2000 wants to merge 6 commits into
Unleash:v6from
sukesh2000:feat/disable-polling-synchronous-fetch

Conversation

@sukesh2000

@sukesh2000 sukesh2000 commented May 28, 2026

Copy link
Copy Markdown

About the changes

Adds two new config options available in other Unleash SDKs (Node, Java) but not yet present in the Go v6 SDK:

  • WithDisablePolling(true) - stops the client from polling the Unleash server after the initial startup. The client serves whatever state was loaded from backup storage or fetched on initialisation. Intended for serverless/lambda environments, air-gapped deployments, and CI setups where toggle state only changes on redeploy.

  • WithSynchronousFetchOnInitialisation(true) - makes NewClient block until the first fetch completes, so toggles are available immediately on return without calling WaitForReady(). Subsequent polling (if enabled) continues as normal in the background.

The two options compose:

disablePolling synchronousFetch behaviour
false false unchanged - goroutine fetches once then polls (default)
false true NewClient blocks on first fetch, goroutine skips the leading fetch, polls normally
true false ready fires immediately from backup/empty state, no polling goroutine
true true NewClient blocks on first fetch, ready fires, no polling goroutine

In all four cases, if the initial fetch fails the client falls back to whatever is in backup storage rather than returning an error, and ready still fires so the application is not blocked.

Closes #185

Important files

  • config.go - the two new With* option funcs and their doc comments, start here for the public API shape
  • polling_fetcher.go - core logic: the four-quadrant behaviour matrix, hasHydrated(), and the skip-leading-fetch guard in runPollingLoop that prevents a double-fetch when synchronousFetch already succeeded
  • adaptive_fetcher.go - streaming failover: disablePolling=true suppresses fallback to polling even during a streaming failure, the failover path always clears synchronousFetch (see Discussion points)
  • polling_fetcher_test.go - fetcher-level unit tests for all four flag combinations
  • client_test.go - client-level integration tests including the streaming + disablePolling interaction
  • adaptive_fetcher_test.go - adaptive fetcher tests including failover-before-hydration and disablePolling-suppresses-failover scenarios.

Discussion points

WithSynchronousFetchOnInitialisation has no effect in streaming mode.

In streaming mode the first SSE hydration event drives the ready signal, so there is no pre-ready blocking fetch to perform. This is documented on the option func.

disablePolling=true suppresses streaming failover to polling.

When streaming is configured and fails, the adaptive fetcher normally falls back to polling. If the user has set disablePolling=true we honour that even during failover - the client fires ready with whatever state it has (backup storage or empty) rather than silently starting a polling loop the user opted out of.

Failover always clears synchronousFetch.

cutover() holds af.mu when it calls start() on the new polling fetcher. If synchronousFetch=true were allowed through, start() would make a blocking HTTP request while holding the mutex, stop() also acquires af.mu, causing a deadlock. The failover path therefore always sets synchronousFetch=false regardless of what the user configured.

isReady write protection.

hasHydrated() is introduced by this PR. To pair correctly with its RLock, the writes to isReady in fetchAndReportError and the disablePolling branch of start() are protected with Lock/Unlock.

@sukesh2000 sukesh2000 changed the title Feat/disable polling synchronous fetch feat: add WithDisablePolling and WithSynchronousFetchOnInitialisation options May 28, 2026
@sighphyre

Copy link
Copy Markdown
Member

Hey @sukesh2000, what's the use case here? I'm a bit leery to make changes to the public API of this project at the moment because it conflicts with upcoming plans.

Synchronous initialization probably isn't something I really want to support right now unless there's a very strong reason for it. I'm much more open to adding disabling fetch but would rather do that in a different way from the approach in this PR and ideally that would go into a smaller self contained PR.

If you have a reason for wanting either of these features, could I bother you to open an issue first? Right now, 700 lines that touch the public API without context is a bit more than I want to tackle, sorry to be a nuisance!

@sukesh2000

Copy link
Copy Markdown
Author

@sighphyre Thanks for the detailed feedback! That totally makes sense. I should've opened an issue first before proposing API changes of this scope. I'll close this PR, open an issue describing the use cases and then follow up with a smaller, focused PR if there's agreement on the approach. Thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Support running in offline mode (e.g. bootstrap feature toggles from a file)

3 participants