gastownhall/beads · warning · ErrMigrationLockUnavailable
schema: acquire migration lock: %w: returned NULL
Error message
schema: acquire migration lock: %w: returned NULL
What it means
GET_LOCK returned NULL instead of 0/1, which MySQL/Dolt uses to signal an error condition (e.g. the lock name was NULL/invalid or an out-of-memory/server error). The library wraps ErrMigrationLockUnavailable so callers treat it like any other transient lock-acquisition failure.
Source
Thrown at internal/storage/schema/lock.go:355
).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 {
discardConn(conn)
return fmt.Errorf("schema: release migration lock: %w: %w", ErrMigrationLockRelease, err)
}View on GitHub (pinned to 71377f2769)
Solutions
- Use schema.MigrationLockName(databaseName) to build the lock name instead of a hand-rolled string.
- Verify databaseName passed to MigrateUpWithLock is non-empty.
- Check server logs for the underlying GET_LOCK error condition.
- Retry via IsMigrationLockError — the sentinel marks it retryable.
Example fix
// before schema.AcquireMigrationLock(ctx, conn, db) // raw db name as lock name // after lockName := schema.MigrationLockName(db) err := schema.AcquireMigrationLock(ctx, conn, lockName)
Defensive patterns
Strategy: validation
Validate before calling
if db == "" { return errors.New("database name required for migration lock") }
lockName := schema.MigrationLockName(db) // guarantees valid, ≤64-byte name
if len(lockName) == 0 || len(lockName) > 64 { return fmt.Errorf("bad lock name %q", lockName) } Type guard
func isLockUnavailable(err error) bool { return errors.Is(err, schema.ErrMigrationLockUnavailable) } Try / catch
if err := schema.AcquireMigrationLock(ctx, conn, lockName); err != nil {
if schema.IsMigrationLockError(err) { /* retry with backoff or abort gracefully */ }
return err
} Prevention
- Always derive lock names via schema.MigrationLockName, never hand-build them.
- Reject empty database names before calling MigrateUpWithLock.
- Check server logs when GET_LOCK returns NULL — it indicates a server-side error.
- Treat the error as retryable via the ErrMigrationLockUnavailable sentinel.
When it happens
Trigger: AcquireMigrationLock called with an empty or invalid lockName, or a server-side error during GET_LOCK makes it return NULL. MigrationLockName normally guarantees a valid 64-char-max name, so NULL usually indicates a server problem or a hand-built name.
Common situations: Passing an empty database name into a custom lock name; non-ASCII/oversized lock names from custom callers; Dolt server-side anomalies reproducing MySQL NULL semantics.
Related errors
- schema: acquire migration lock: %w: %w
- %w (lock release also failed: %w)
- ErrTransaction
- ErrQuery
- ErrScan
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/f5e545656f005035.
Report an issue: GitHub.