{"record":{"id":"54a0fb8d3ee5d8c3","repo":"gastownhall/beads","slug":"w-for-s-stop-epoch-advanced-during-startup","errorCode":null,"errorMessage":"%w for %s: stop epoch advanced during startup","messagePattern":"%w for (.+?): stop epoch advanced during startup","errorType":"exception","errorClass":"errStartInterrupted","httpStatus":null,"severity":"warning","filePath":"internal/storage/dbproxy/proxy/server.go","lineNumber":220,"sourceCode":"\t\t\t}\n\t\t}\n\t}()\n\tstopEpochWatch := func() {\n\t\tepochWatchCancel()\n\t\t<-epochWatchDone\n\t}\n\tdefer stopEpochWatch()\n\n\t// abortInterruptedStart tears down a doomed start whose stop epoch\n\t// advanced mid-boot. The interrupting stop is polling proxy.lock under a\n\t// budget (shutdownConfirmDeadline) far smaller than a backend stop can\n\t// take, and nothing has been published, so release the lock BEFORE the\n\t// backend teardown instead of starving the stopper into its timeout.\n\tabortInterruptedStart := func() error {\n\t\tp.stats.IncBackendStop()\n\t\treleaseLock()\n\t\t_ = stopBackendBounded(p.server)\n\t\treturn fmt.Errorf(\"%w for %s: stop epoch advanced during startup\", errStartInterrupted, p.rootDir)\n\t}\n\n\taddr := fmt.Sprintf(\"127.0.0.1:%d\", p.port)\n\n\tln, err := net.Listen(\"tcp\", addr)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"listen on %s: %w\", addr, err)\n\t}\n\n\tp.listener = ln\n\tdefer func() { _ = ln.Close() }()\n\tp.stats.IncListenAndServe()\n\tdataPort, ok := ln.Addr().(*net.TCPAddr)\n\tif !ok {\n\t\treturn fmt.Errorf(\"proxy: unexpected data listener address %T\", ln.Addr())\n\t}\n\n\tif _, err := identity.WriteSecret(p.rootDir); err != nil {","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dbproxy/proxy/server.go#L202-L238","documentation":"During startup, if the stop epoch advances after the listener/backend boot began but before startup completes, the proxy invokes abortInterruptedStart, which stops the backend, releases proxy.lock early, and returns this errStartInterrupted-wrapped error. Nothing has been published yet, so a clean abort avoids starving the stopper into its timeout.","triggerScenarios":"`bd dolt stop` advances the stop epoch while the proxy is mid-startup (after the post-lock epoch check, before publishing); detected via the startup-completion epoch check.","commonSituations":"Stop command racing an auto-restart; CI/tests issuing rapid start/stop cycles; an operator stopping the service at the same moment a supervisor restarts it.","solutions":["Re-run the start after the stop has fully completed","Sequence start/stop in automation so they don't overlap","Treat errStartInterrupted as an expected abort: verify the service is stopped rather than retrying blindly"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// verify the stop epoch is stable before starting\nepoch1, _ := readStopEpoch(rootDir)\ntime.Sleep(50 * time.Millisecond)\nepoch2, err := readStopEpoch(rootDir)\nif err != nil || epoch1 != epoch2 { return fmt.Errorf(\"stop in flight\") }","typeGuard":null,"tryCatchPattern":"err := p.ListenAndServe(ctx)\nif errors.Is(err, errStartInterrupted) { /* stop won the race; decide whether to restart */ }","preventionTips":["Avoid overlapping start/stop commands in automation","Add small settle delays between stop and restart in tests","Treat errStartInterrupted as an expected clean abort, not a bug"],"tags":["concurrency","stop-epoch","race-condition"],"backgroundTag":"stop-epoch-conflict","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}