mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-09-04 13:36:35 +02:00
cmd/cli: bound API preflight by service lifetime
processCDFlags retries the resolver-config fetch indefinitely by design: a device that has no working network at boot must eventually come up. The loop had no cancellation, so a stop request arriving while the API is unreachable was ignored - the process kept retrying long after the service reported itself stopped, doing work on behalf of a service the OS considers stopped. Thread a context through processCDFlags and derive it from p.stopCh, in both the startup preflight and the config-reload path. The loop now returns as soon as the context is cancelled, checked both before a retry and after backoff returns (backoff can wake up on cancellation). A stop during preflight exits the way a normal stop does, without Fatal, so the service manager does not treat it as a failed start and apply its restart policy to a service the operator just asked to stop. Bind the two API requests themselves as well, so a stop does not have to wait out an in-flight request. Without this the loop honours a stop only between attempts, which leaves up to defaultTimeout (20s) of a request the service is no longer interested in - the same "still working after Service stopped" the loop change exists to end, one layer down. Doing so means a context parameter on FetchResolverConfig, FetchResolverUID, UpdateCustomLastFailed and SendLogs, since all four reach a request builder. The callers that have no context pass context.Background(), which is what master effectively does at those sites: its loggerCtx carries a logger, not cancellation. doWithFallback needs no parameter, because it clones the request with req.Context() and so inherits the binding. This also repairs internal/controld/controld_test.go, which is behind //go:build controld and had already been written against the context-taking signature, so it could not compile. Cover the cancellation paths; removing either check makes the tests hang until timeout. Sampling the stop state is the whole point of runAPIPreflight rather than doing this inline. A stop and a failure need opposite handling - one exits quietly, the other self-uninstalls a deleted device, surfaces the error to a mobile app, and reports a failed start - so the two must not be confused. Reading it from the context after cancelling would report "stopped" for every failure, since CancelFunc sets ctx.Err() regardless of whether anyone asked to stop; the stop channel is read directly instead, which also does not depend on the context watcher goroutine having been scheduled.
This commit is contained in:
+3
-3
@@ -324,7 +324,7 @@ func (p *prog) runWait() {
|
||||
continue
|
||||
}
|
||||
if cdUID != "" {
|
||||
rc, err := processCDFlags(newCfg)
|
||||
rc, err := p.fetchCDConfigBoundedByLifetime(newCfg)
|
||||
if err != nil {
|
||||
logger.Err(err).Msg("could not fetch ControlD config")
|
||||
waitOldRunDone()
|
||||
@@ -491,7 +491,7 @@ func (p *prog) apiConfigReload() {
|
||||
Version: rootCmd.Version,
|
||||
Metadata: ctrld.SystemMetadataRuntime(context.Background()),
|
||||
}
|
||||
resolverConfig, err := controld.FetchResolverConfig(req, cdDev)
|
||||
resolverConfig, err := controld.FetchResolverConfig(context.Background(), req, cdDev)
|
||||
selfUninstallCheck(err, p, logger)
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("could not fetch resolver config")
|
||||
@@ -549,7 +549,7 @@ func (p *prog) apiConfigReload() {
|
||||
}
|
||||
if cfgErr != nil {
|
||||
logger.Warn().Err(err).Msg("skipping invalid custom config")
|
||||
if _, err := controld.UpdateCustomLastFailed(cdUID, rootCmd.Version, cdDev, true); err != nil {
|
||||
if _, err := controld.UpdateCustomLastFailed(context.Background(), cdUID, rootCmd.Version, cdDev, true); err != nil {
|
||||
logger.Error().Err(err).Msg("could not mark custom last update failed")
|
||||
}
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user