gastownhall/beads · error

uow: pin connection: %w

Error message

uow: pin connection: %w

What it means

BeginTx acquires a dedicated connection from the sql.DB pool (p.db.Conn) to pin the unit of work's transaction to one session. This error wraps a pool acquisition failure as 'uow: pin connection: %w'.

Source

Thrown at internal/storage/uow/dolt_sql_provider.go:146

)

func (p *doltSQLProvider) NewUOW(ctx context.Context) (UnitOfWork, error) {
	return NewUOW(ctx, p)
}

func (p *doltSQLProvider) Close(ctx context.Context) error {
	if p.db == nil {
		return nil
	}
	db := p.db
	p.db = nil
	return db.Close()
}

func (p *doltSQLProvider) BeginTx(ctx context.Context) (Tx, error) {
	conn, err := p.db.Conn(ctx)
	if err != nil {
		return nil, fmt.Errorf("uow: pin connection: %w", err)
	}

	_, err = conn.ExecContext(ctx, "START TRANSACTION;")
	if err != nil {
		_ = conn.Close()
		return nil, fmt.Errorf("uow: failed to start transaction: %w", err)
	}

	// Bind journal activation to the connection this unit of work is pinned to,
	// AFTER START TRANSACTION so the seq allocation's UPDATE and the SELECT that
	// must observe it are inside one transaction on one session. The scope is
	// released when the connection is (doltServerTx.releaseConn / poisonConn),
	// so an entry cannot outlive its transaction.
	return &doltServerTx{
		conn:              conn,
		clearJournalScope: issueops.ScopeEventsJournalTransaction(conn, p.eventsJournalEnabled.Load()),
	}, nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the Dolt server is reachable (address/port in the DSN).
  2. Look for leaked transactions: every BeginTx connection must be released via the Tx's Commit/Rollback path (doltServerTx.releaseConn).
  3. Increase MaxOpenConns on the pool if the workload legitimately needs more concurrency.
  4. Retry with a fresh or longer-lived context if it was a transient timeout.

Example fix

// before
ctx := context.Background()
uow, err := provider.NewUOW(ctx) // hangs/fails when pool is busy

// after
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
uow, err := provider.NewUOW(ctx)
if err != nil {
    return fmt.Errorf("begin unit of work: %w", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before opening units of work, verify the server answers
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := provider.Ping(ctx); err != nil {
    return fmt.Errorf("dolt server unavailable: %w", err)
}

Try / catch

tx, err := provider.BeginTx(ctx)
if err != nil {
    var ctxErr = context.DeadlineExceeded
    if errors.Is(err, ctxErr) || errors.Is(err, context.Canceled) {
        return fmt.Errorf("timed out waiting for a pooled connection: %w", err)
    }
    return fmt.Errorf("cannot pin connection (server down or pool exhausted): %w", err)
}

Prevention

When it happens

Trigger: Calling BeginTx (directly or via NewUOW) when the pool cannot hand out a connection: pool exhausted (all connections busy and MaxOpenConns reached), context canceled/timed out while waiting, or the Dolt server is down.

Common situations: Long-running transactions leaking connections so the pool is exhausted; Dolt SQL server restarted or unreachable; a context deadline shorter than the pool's wait.

Related errors


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