gastownhall/beads · warning · ErrMigrationLockUnavailable
schema: acquire migration lock: %w: %w
Error message
schema: acquire migration lock: %w: %w
What it means
GET_LOCK itself errored at the driver level while acquiring the database-scoped schema migration lock. The error wraps both the sentinel ErrMigrationLockUnavailable (making it retryable via IsMigrationLockError) and the underlying driver error.
Source
Thrown at internal/storage/schema/lock.go:352
var ancestorCount int
if err := conn.QueryRowContext(ctx,
"SELECT COUNT(*) FROM dolt_log WHERE commit_hash = ?", c.initialHead,
).Scan(&ancestorCount); err != nil {
return false
}
if ancestorCount != 1 {
return false
}
return c.consumed.CompareAndSwap(false, true)
}
// AcquireMigrationLock acquires the named schema migration lock on the pinned
// connection's current Dolt/MySQL session.
func AcquireMigrationLock(ctx context.Context, conn *sql.Conn, lockName string) error {
var locked sql.NullInt64
if err := conn.QueryRowContext(ctx, "SELECT GET_LOCK(?, ?)", lockName, migrationLockAcquireTimeoutSeconds).Scan(&locked); err != nil {
return fmt.Errorf("schema: acquire migration lock: %w: %w", ErrMigrationLockUnavailable, err)
}
if !locked.Valid {
return fmt.Errorf("schema: acquire migration lock: %w: returned NULL", ErrMigrationLockUnavailable)
}
if locked.Int64 != 1 {
return fmt.Errorf("schema: acquire migration lock: %w: timeout", ErrMigrationLockUnavailable)
}
return nil
}
// ReleaseMigrationLock releases the named schema migration lock from the same
// pinned Dolt/MySQL session used to acquire it.
func ReleaseMigrationLock(conn *sql.Conn, lockName string) error {
cleanupCtx, cancel := context.WithTimeout(context.Background(), migrationLockCleanupTimeout)
defer cancel()
var released sql.NullInt64
if err := conn.QueryRowContext(cleanupCtx, "SELECT RELEASE_LOCK(?)", lockName).Scan(&released); err != nil {View on GitHub (pinned to 71377f2769)
Solutions
- Treat as retryable: check schema.IsMigrationLockError(err) and retry with backoff within your retry budget.
- Ensure the caller's context outlives the 5-second lock acquire timeout.
- Verify the pinned connection is alive (ping) before attempting migration.
- Reduce contention: fewer concurrent initializers, or stagger bd process startup on shared rigs.
Example fix
// before
applied, err := schema.MigrateUpWithLock(ctx, conn, db) // fails on flaky conn
// after
if err := conn.PingContext(ctx); err != nil {
conn, err = pool.Conn(ctx)
}
applied, err := schema.MigrateUpWithLock(ctx, conn, db)
if schema.IsMigrationLockError(err) { /* retry with backoff */ } Defensive patterns
Strategy: retry
Validate before calling
if err := conn.PingContext(ctx); err != nil { return err }
ctx, cancel := context.WithTimeout(ctx, 10*time.Second) // must exceed the 5s lock timeout
defer cancel() Type guard
func isLockUnavailable(err error) bool { return errors.Is(err, schema.ErrMigrationLockUnavailable) } Try / catch
applied, err := schema.MigrateUpWithLock(ctx, conn, db)
if schema.IsMigrationLockError(err) {
time.Sleep(backoff)
return retryOpen(ctx) // retry with exponential backoff, within retry budget
} Prevention
- Give the context a deadline comfortably longer than the 5-second lock wait.
- Ping or refresh the pinned connection before migration.
- Use schema.IsMigrationLockError to gate retries.
- Reduce concurrent initializers on shared Dolt servers.
When it happens
Trigger: conn.QueryRowContext("SELECT GET_LOCK(?, ?)") fails: connection broken/closed, context cancelled or deadline exceeded during the up-to-5s lock wait, server restart.
Common situations: Heavily contended shared Dolt server (many bd processes); idle connection reaped by the server; caller's ctx deadline shorter than the 5-second lock timeout.
Related errors
- schema: acquire migration lock: %w: returned NULL
- %w (lock release also failed: %w)
- ErrTransaction
- ErrQuery
- ErrScan
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/82a92f6ca7c20727.
Report an issue: GitHub.