{"record":{"id":"ae47e485b782482b","repo":"kubernetes/kops","slug":"shutdown-already-in-progress","errorCode":null,"errorMessage":"shutdown already in progress","messagePattern":"shutdown already in progress","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"dns-controller/pkg/util/stoppable.go","lineNumber":54,"sourceCode":"\n// StopChannel gets the stopChannel, initializing it if needed\nfunc (s *Stoppable) StopChannel() <-chan struct{} {\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\tif s.stopChannel == nil {\n\t\ts.stopChannel = make(chan struct{})\n\t}\n\treturn s.stopChannel\n}\n\n// Stop stops the controller.\nfunc (s *Stoppable) Stop() error {\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\tif s.shutdown {\n\t\treturn fmt.Errorf(\"shutdown already in progress\")\n\t}\n\n\t// We initialize the channel to avoid a race if we Stop before anyone is watching\n\tif s.stopChannel == nil {\n\t\ts.stopChannel = make(chan struct{})\n\t}\n\tclose(s.stopChannel)\n\tklog.Infof(\"shutting down controller\")\n\ts.shutdown = true\n\n\treturn nil\n}\n\nfunc (s *Stoppable) StopRequested() bool {\n\treturn s.shutdown\n}\n","sourceCodeStart":36,"sourceCodeEnd":71,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/dns-controller/pkg/util/stoppable.go#L36-L71","documentation":"Stoppable.Stop() is guarded so shutdown happens only once: a mutex plus a shutdown flag. If Stop() is invoked after shutdown has already started (shutdown==true), it returns this error instead of closing the channel a second time (which would panic).","triggerScenarios":"Calling Stop() twice on the same Stoppable — e.g. SIGTERM handler and an HTTP /stop endpoint both firing, or a deferred Stop plus an explicit Stop.","commonSituations":"Graceful shutdown triggered by both SIGINT/SIGTERM and healthz/stop HTTP handler; test code stopping controllers in Cleanup after the suite already stopped them; double invocation on reload.","solutions":["Call Stop() only once; check StopRequested() before calling it","Ignore this error on the second call (it's benign and means shutdown already happened)","Deduplicate signal handling: register the SIGTERM handler once","Refactor so only one component owns shutdown responsibility"],"exampleFix":"// before\n_ = stoppable.Stop() // may be called twice\n// after\nif !stoppable.StopRequested() {\n    if err := stoppable.Stop(); err != nil {\n        klog.V(2).Infof(\"stop: %v\", err)\n    }\n}","handlingStrategy":"type-guard","validationCode":"if s.StopRequested() {\n    klog.V(2).Info(\"shutdown already requested; skipping Stop\")\n    return\n}","typeGuard":"func canStop(s *util.Stoppable) bool { return !s.StopRequested() }","tryCatchPattern":"// Stop is idempotent-intent: treat repeat shutdown as benign\nif err := stoppable.Stop(); err != nil && err.Error() != \"shutdown already in progress\" {\n    return fmt.Errorf(\"stopping dns-controller: %w\", err)\n}","preventionTips":["Call Stop from a single shutdown owner (signal handler only)","Check StopRequested() before Stop()","Register signal handlers and HTTP stop endpoints through one deduplicating wrapper"],"tags":["shutdown","lifecycle","race-condition","go"],"backgroundTag":"shutdown-already-in-progress","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T07:17:12.445Z"}