gastownhall/beads · warning
errStartInterrupted
errStartInterrupted
Error message
proxy startup interrupted by concurrent shutdown
What it means
errStartInterrupted signals that a proxy start/spawn was aborted because a concurrent shutdown advanced the stop epoch while startup was in flight. GetCreateDatabaseDatabaseProxyServerEndpoint/spawnAndHandoff/ListenAndServe return it (wrapped with the workspace dir) so the caller knows the failure is a race, not a listen error.
Source
Thrown at internal/storage/dbproxy/proxy/endpoint.go:169
}
}
type adoptionResult struct {
status adoptionStatus
endpoint Endpoint
pidfile *pidfile.PidFile
err error
}
type spawnMarker struct {
Schema int `json:"schema"`
PID int `json:"pid"`
Birth string `json:"birth"`
StopEpoch string `json:"stop_epoch"`
StartedUnix int64 `json:"started_unix"`
}
var errStartInterrupted = errors.New("proxy startup interrupted by concurrent shutdown")
func PickFreePort() (int, error) {
// The managed proxy no longer uses this bind-close allocator: its child
// binds port 0 and publishes the kernel-assigned port. The remaining
// production caller allocates the Dolt config port; that race requires
// the managed-config ownership/retry contract deferred to the PR-C RFC.
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return 0, err
}
port := ln.Addr().(*net.TCPAddr).Port
_ = ln.Close()
return port, nil
}
func GetCreateDatabaseProxyServerEndpoint(rootDir string, opts OpenOpts) (Endpoint, error) {
if err := opts.Backend.Validate(); err != nil {
return Endpoint{}, fmt.Errorf("OpenOpts.Backend: %w", err)View on GitHub (pinned to 71377f2769)
Solutions
- Retry the start after the shutdown completes — the error is transient by design
- Serialize start/stop with a workspace-level lock or single supervisor goroutine
- Check errors.Is(err, errStartInterrupted) and distinguish from real listen failures before alerting
Example fix
// before
return endpoint.GetCreateDatabaseProxyServerEndpoint(ctx, rootDir)
// after
ep, err := endpoint.GetCreateDatabaseProxyServerEndpoint(ctx, rootDir)
if err != nil && errors.Is(err, endpoint.ErrStartInterrupted) {
time.Sleep(500 * time.Millisecond) // let shutdown finish
return endpoint.GetCreateDatabaseProxyServerEndpoint(ctx, rootDir)
} Defensive patterns
Strategy: retry
Validate before calling
// check stop epoch before starting
if epoch, err := readStopEpoch(rootDir); err == nil && epochChanged(epoch) { return errors.New("shutdown in progress") } Type guard
func isStartInterrupted(err error) bool { return errors.Is(err, endpoint.ErrStartInterrupted) } Try / catch
ep, err := GetCreateDatabaseProxyServerEndpoint(ctx, root)
if errors.Is(err, endpoint.ErrStartInterrupted) {
<-shutdownDone
return GetCreateDatabaseProxyServerEndpoint(ctx, root)
} Prevention
- Serialize start and stop operations per workspace
- Avoid rapid start/stop cycles
- Use a single supervisor to coordinate lifecycle
When it happens
Trigger: Calling GetCreateDatabaseProxyServerEndpoint while another goroutine/process calls Stop, advancing stopEpoch during spawnAndHandoff or ListenAndServe startup wait.
Common situations: Rapid start/stop cycles in tests or restart loops; a supervisor issuing shutdown while an initialization request is still being served; concurrent CLI invocations sharing a workspace.
Related errors
- timeout (%s) waiting for spawn marker %s; wait for the in-pr
- %w for %s
- acquire %s: %w
- check proxy stop epoch after acquiring %s: %w
- %w for %s: stop epoch advanced during backend start (%v)
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/46a981064f4becfe.
Report an issue: GitHub.