{"record":{"id":"a9b08b7cd28e9ac8","repo":"gastownhall/beads","slug":"server-externaldoltserver-start-server-already-s","errorCode":null,"errorMessage":"server: ExternalDoltServer.Start: server already started","messagePattern":"server: ExternalDoltServer\\.Start: server already started","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/storage/dbproxy/server/external_dolt_server.go","lineNumber":80,"sourceCode":"\nfunc (s *ExternalDoltServer) DSN(_ context.Context, database, user, password string) string {\n\tdsn := util.DoltServerDSN{\n\t\tUser:     user,\n\t\tPassword: password,\n\t\tDatabase: database,\n\t}\n\tif s.socket != \"\" {\n\t\tdsn.Socket = s.socket\n\t} else {\n\t\tdsn.Host = s.host\n\t\tdsn.Port = s.port\n\t}\n\treturn dsn.String()\n}\n\nfunc (s *ExternalDoltServer) Start(_ context.Context) error {\n\tif !s.started.CompareAndSwap(false, true) {\n\t\treturn errors.New(\"server: ExternalDoltServer.Start: server already started\")\n\t}\n\treturn nil\n}\n\nfunc (s *ExternalDoltServer) Stop(_ context.Context) error {\n\ts.started.Store(false)\n\treturn nil\n}\n\nfunc (s *ExternalDoltServer) Running(_ context.Context) bool {\n\treturn s.started.Load()\n}\n\nfunc (s *ExternalDoltServer) Dial(ctx context.Context) (net.Conn, error) {\n\tnetwork, addr := \"tcp\", net.JoinHostPort(s.host, strconv.Itoa(s.port))\n\tif s.socket != \"\" {\n\t\tnetwork, addr = \"unix\", s.socket\n\t}","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dbproxy/server/external_dolt_server.go#L62-L98","documentation":"ExternalDoltServer wraps an already-running Dolt server (no child process to spawn), so Start is just a guard that marks the wrapper as started using an atomic compare-and-swap. Calling Start twice without an intervening Stop is treated as a programming error and returns this sentinel instead of silently succeeding.","triggerScenarios":"Calling ExternalDoltServer.Start a second time on the same instance after a previous Start succeeded and Stop has not been called (or is still in flight), because started.CompareAndSwap(false, true) fails on the re-entry.","commonSituations":"Retry loops that re-call Start after a partial startup; wiring Start into both an init function and a request handler; multiple goroutines starting the same server concurrently; confusion with DoltServer, whose Start semantics differ.","solutions":["Call Start once per instance; call Stop before any legitimate restart","Make startup idempotent in your code: skip Start when the server is already running","Serialize lifecycle management behind a mutex or Once so concurrent callers cannot double-Start","If the double-start is expected in your design, treat this sentinel as a no-op success"],"exampleFix":"// before\nif err := srv.Start(ctx); err != nil { return err } // panics on second call in retries\nif err := srv.Start(ctx); err != nil { return err }\n// after\nvar startOnce sync.Once\nstartOnce.Do(func() { startErr = srv.Start(ctx) })\nif startErr != nil { return startErr }","handlingStrategy":"type-guard","validationCode":"var started atomic.Bool // track in the owner before calling Start\nif !started.CompareAndSwap(false, true) {\n    return nil // already started; skip\n}","typeGuard":"func isAlreadyStarted(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"server already started\")\n}","tryCatchPattern":"if err := srv.Start(ctx); err != nil {\n    if isAlreadyStarted(err) {\n        return nil // idempotent: treat as success\n    }\n    return err\n}","preventionTips":["Pair every Start with exactly one Stop in the same owner (defer Stop)","Guard startup with sync.Once or an atomic flag in your wiring","Never call Start from multiple code paths (init + request handler) on the same instance"],"tags":["go","lifecycle","concurrency","server-startup"],"backgroundTag":"already-started-server","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}