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

  1. Retry the start after the shutdown completes — the error is transient by design
  2. Serialize start/stop with a workspace-level lock or single supervisor goroutine
  3. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/46a981064f4becfe. Report an issue: GitHub.