gastownhall/beads · error

acquire connection for merge: %w

Error message

acquire connection for merge: %w

What it means

MergeWithStrategy needs a dedicated connection pinned for the Dolt merge session. This error wraps a failure from s.db.Conn(ctx) when acquiring that connection. Pool exhaustion is the notable trigger here — the surrounding code even documents that recomputeBlockedAfterPull needs its own connection and the pool may be MaxOpenConns:1.

Source

Thrown at internal/storage/dolt/store.go:4917

	ctx, span := doltTracer.Start(ctx, "dolt.merge_with_strategy",
		trace.WithSpanKind(trace.SpanKindClient),
		trace.WithAttributes(append(s.doltSpanAttrs(),
			attribute.String("dolt.merge_branch", branch),
			attribute.String("dolt.merge_strategy", strategy),
		)...),
	)
	defer func() { endSpan(span, retErr) }()

	preHead := ""
	if !s.readOnly {
		if h, err := s.GetCurrentCommit(ctx); err == nil {
			preHead = h
		}
	}

	conn, err := s.db.Conn(ctx)
	if err != nil {
		return nil, fmt.Errorf("acquire connection for merge: %w", err)
	}
	conflicts, err = versioncontrolops.MergeWithStrategy(ctx, conn, branch, s.commitAuthorString(), strategy)
	// Release the pinned connection before the recompute: s.db's pool can be
	// configured with a single connection (setupTestStore's MaxOpenConns: 1
	// mirrors constrained production configs), and recomputeBlockedAfterPull
	// acquires its own connection — held past this point, conn would starve
	// it of the only one available.
	closeErr := conn.Close()
	if len(conflicts) > 0 {
		span.SetAttributes(attribute.Int("dolt.conflicts", len(conflicts)))
	}
	if err != nil {
		return conflicts, err
	}
	if closeErr != nil {
		return conflicts, fmt.Errorf("release merge connection: %w", closeErr)
	}
	if !s.readOnly {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Close/commit all other connections, txs, and rows before merging.
  2. Follow the codebase's own pattern: release the conn (conn.Close()) before running recomputeBlockedAfterPull.
  3. Increase MaxOpenConns so merge and recompute can each hold a connection.
  4. Check ctx deadlines if the pool wait is being cancelled.
  5. Confirm the Dolt server is healthy; retry to evict stale pooled connections.

Example fix

// before
conn, err := s.db.Conn(ctx)
if err != nil { return nil, fmt.Errorf("acquire connection for merge: %w", err) }
conflicts, err = versioncontrolops.MergeWithStrategy(ctx, conn, ...)
// recompute runs while conn still held -> starvation
// after
conflicts, err := func() ([]Conflict, error) {
    conn, err := s.db.Conn(ctx)
    if err != nil { return nil, fmt.Errorf("acquire connection for merge: %w", err) }
    defer conn.Close()
    return versioncontrolops.MergeWithStrategy(ctx, conn, ...)
}()
if err != nil { return conflicts, err }
// now recompute can acquire its own connection
Defensive patterns

Strategy: validation

Validate before calling

stats := db.Stats()
if stats.MaxOpenConnections > 0 && stats.InUse >= stats.MaxOpenConnections {
    return errors.New("cannot merge: connection pool exhausted; release pinned connections first")
}
if err := ctx.Err(); err != nil { return err }

Try / catch

conn, err := s.db.Conn(ctx)
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        return fmt.Errorf("merge starved waiting for connection (pool=%d in use)", db.Stats().InUse)
    }
    return fmt.Errorf("acquire connection for merge: %w", err)
}

Prevention

When it happens

Trigger: Calling the store's strategy merge (bd vc merge --strategy) when s.db.Conn(ctx) fails: another operation (often a still-pinned conn or an in-flight recompute) holds the sole connection, or ctx expires while queued for the pool.

Common situations: Test stores configured with MaxOpenConns:1 mirroring constrained production; concurrent merge and recompute operations; Dolt server unresponsive so pooled conns are dead.

Related errors


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