feat: add WithDisablePolling and WithSynchronousFetchOnInitialisation options - #267
feat: add WithDisablePolling and WithSynchronousFetchOnInitialisation options#267sukesh2000 wants to merge 6 commits into
Conversation
|
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! |
|
@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! |
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)- makesNewClientblock until the first fetch completes, so toggles are available immediately on return without callingWaitForReady(). Subsequent polling (if enabled) continues as normal in the background.The two options compose:
disablePollingsynchronousFetchfalsefalsefalsetrueNewClientblocks on first fetch, goroutine skips the leading fetch, polls normallytruefalsetruetrueNewClientblocks on first fetch, ready fires, no polling goroutineIn all four cases, if the initial fetch fails the client falls back to whatever is in backup storage rather than returning an error, and
readystill fires so the application is not blocked.Closes #185
Important files
config.go- the two newWith*option funcs and their doc comments, start here for the public API shapepolling_fetcher.go- core logic: the four-quadrant behaviour matrix,hasHydrated(), and the skip-leading-fetch guard inrunPollingLoopthat prevents a double-fetch whensynchronousFetchalready succeededadaptive_fetcher.go- streaming failover:disablePolling=truesuppresses fallback to polling even during a streaming failure, the failover path always clearssynchronousFetch(see Discussion points)polling_fetcher_test.go- fetcher-level unit tests for all four flag combinationsclient_test.go- client-level integration tests including the streaming + disablePolling interactionadaptive_fetcher_test.go- adaptive fetcher tests including failover-before-hydration and disablePolling-suppresses-failover scenarios.Discussion points
WithSynchronousFetchOnInitialisationhas 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=truesuppresses streaming failover to polling.When streaming is configured and fails, the adaptive fetcher normally falls back to polling. If the user has set
disablePolling=truewe honour that even during failover - the client firesreadywith 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()holdsaf.muwhen it callsstart()on the new polling fetcher. IfsynchronousFetch=truewere allowed through,start()would make a blocking HTTP request while holding the mutex,stop()also acquiresaf.mu, causing a deadlock. The failover path therefore always setssynchronousFetch=falseregardless of what the user configured.isReadywrite protection.hasHydrated()is introduced by this PR. To pair correctly with itsRLock, the writes toisReadyinfetchAndReportErrorand thedisablePollingbranch ofstart()are protected withLock/Unlock.