gastownhall/beads · error

listener not ready after %s: %w

Error message

listener not ready after %s: %w

What it means

waitReady polled the server's listener by dialing it repeatedly; after startReadyTimeout (30s) elapsed without a successful connection, it returns the last dial error wrapped with "listener not ready after 30s". Start() then tears the child down and returns this via error 2405.

Source

Thrown at internal/storage/dbproxy/server/doltserver.go:335

}

func (s *DoltServer) waitReady(ctx context.Context) error {
	deadline := time.Now().Add(startReadyTimeout)
	for {
		if s.egCtx.Err() != nil {
			return errors.New("dolt sql-server exited before listener became ready")
		}

		dctx, dcancel := context.WithTimeout(ctx, startReadyDialTimeout)
		conn, err := s.Dial(dctx)
		dcancel()
		if err == nil {
			_ = conn.Close()
			return nil
		}

		if time.Now().After(deadline) {
			return fmt.Errorf("listener not ready after %s: %w", startReadyTimeout, err)
		}

		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-s.egCtx.Done():
			return errors.New("dolt sql-server exited before listener became ready")
		case <-time.After(startReadyPollInterval):
		}
	}
}

func (s *DoltServer) Stop(ctx context.Context) error {
	gcErr := s.runShutdownGC(ctx)
	if gcErr != nil {
		gcErr = fmt.Errorf("server: DoltServer.Stop: %w", gcErr)
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the server log for the child's fatal output (port in use, config parse error).
  2. Cross-check host/port/socket in the dolt server config YAML against what your client DSN uses.
  3. Free the configured port (kill the conflicting process) or change the port in the config.
  4. Retry on a less loaded system; if starts are consistently >30s, this is a scaling/IO problem to address on the host.

Example fix

// before
// config.yaml: host: 10.0.0.5, port: 3307  (client dials localhost:3307)
// after
// config.yaml: host: 127.0.0.1, port: 3307  — match host/port with client DSN
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the dolt config's host/port pair is reachable and free.
h, p := cfgHost(configPath), cfgPort(configPath)
conn, err := net.DialTimeout("tcp", net.JoinHostPort(h, strconv.Itoa(p)), time.Second)
if err == nil {
    conn.Close()
    return fmt.Errorf("something is already listening on %s:%d", h, p)
}

Try / catch

if err := srv.Start(ctx); err != nil && strings.Contains(err.Error(), "listener not ready after") {
    // inspect log file content for the real dial/bind failure, then surface it
    tail, _ := os.ReadFile(logPath)
    return fmt.Errorf("server not ready: %v; dolt log tail: %s", err, lastLines(tail, 20))
}

Prevention

When it happens

Trigger: Every dial (TCP to host:port, or socket) fails for the full 30s window — dolt never bound the listener because it crashed, is misconfigured (wrong host/port/socket in the YAML), or is catastrophically slow to start.

Common situations: Config binds to a different host than Dial checks; port already taken by another process so dolt exits; SELinux/firewall blocking local connections; extremely slow disk making dolt's bootstrap exceed 30s.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


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