{"record":{"id":"d61b0a5208dd5876","repo":"kopia/kopia","slug":"upgrade-drain-interrupted","errorCode":null,"errorMessage":"upgrade drain interrupted","messagePattern":"upgrade drain interrupted","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"cli/command_repository_upgrade.go","lineNumber":429,"sourceCode":"\n\t\tupgradeTime := l.UpgradeTime()\n\t\tnow := rep.Time()\n\n\t\tlog(ctx).Infof(\"Waiting for %s to allow all other kopia clients to drain ...\", upgradeTime.Sub(rep.Time()).Round(time.Second))\n\n\t\tlocked, writersDrained := l.IsLocked(now)\n\t\tif locked {\n\t\t\tif writersDrained {\n\t\t\t\t// we have the lock now\n\t\t\t\tbreak\n\t\t\t}\n\t\t} else {\n\t\t\treturn errors.Wrap(err, \"upgrade lock got revoked after the intent was placed, giving up\")\n\t\t}\n\n\t\t// TODO: this can get stuck\n\t\tif !c.sleepWithContext(ctx, l.StatusPollInterval) {\n\t\t\treturn errors.New(\"upgrade drain interrupted\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// upgrade phase performs the actual upgrade action that upgrades the target\n// repository. This phase runs after the lock has been acquired in one of the\n// prior phases.\nfunc (c *commandRepositoryUpgrade) upgrade(ctx context.Context, rep repo.DirectRepositoryWriter) error {\n\tmp, mperr := rep.ContentReader().ContentFormat().GetMutableParameters(ctx)\n\tif mperr != nil {\n\t\treturn errors.Wrap(mperr, \"mutable parameters\")\n\t}\n\n\trf, err := rep.FormatManager().RequiredFeatures(ctx)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"error getting repository features\")","sourceCodeStart":411,"sourceCodeEnd":447,"githubUrl":"https://github.com/kopia/kopia/blob/82495e54b584c1ef6073c9e1be048f57f8aef078/cli/command_repository_upgrade.go#L411-L447","documentation":"drainAllClients waits for all connected clients to disconnect while a repository upgrade is in progress. It polls the lock status on a timer (sleepWithContext); if the context is cancelled or the sleep is interrupted before draining completes, it throws 'upgrade drain interrupted'.","triggerScenarios":"During a repository upgrade, pressing Ctrl-C, the process receiving SIGINT/SIGTERM, or the parent context being cancelled while the loop is sleeping between status polls (l.StatusPollInterval).","commonSituations":"Operator aborts a long-running upgrade because drain is slow; systemd or CI kills the upgrade command on timeout; running the upgrade in a shell session that disconnects.","solutions":["Re-run the upgrade command and let it run until drain completes","Ensure the machine/process is not interrupted (use nohup/tmux or a long system service timeout)","Disconnect idle kopia clients so draining finishes faster","Check that the upgrade lock is still valid; if it was revoked, the sibling error 'upgrade lock got revoked' will surface instead"],"exampleFix":"// before\nctx, cancel := context.WithCancel(context.Background())\ndefer cancel()\n// interrupted by Ctrl-C\n// after - shield from cancellation\nrunCtx := context.WithoutCancel(ctx) // or a detached context for the drain loop\nif !c.sleepWithContext(runCtx, l.StatusPollInterval) { ... }","handlingStrategy":"retry","validationCode":"// before starting: ensure the context has a generous deadline\nctx, cancel := context.WithTimeout(context.Background(), maxDrainTime)","typeGuard":null,"tryCatchPattern":"if err := runUpgrade(ctx); errors.Is(err, context.Canceled) || strings.Contains(err.Error(), \"drain interrupted\") {\n    // resume or re-run the upgrade; the intent/lock remains\n}","preventionTips":["Run upgrades under tmux/nohup or a service with a long timeout","Disconnect idle clients before starting an upgrade so drain is quick","Avoid Ctrl-C during the drain phase; poll status instead"],"tags":["context","repository","upgrade","interruption"],"backgroundTag":"request-timeout","analyzedSha":"82495e54b584c1ef6073c9e1be048f57f8aef078","analyzedAt":"2026-09-07T20:35:21.689Z","contentChangedAt":"2026-09-07T20:35:21.689Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}