gastownhall/beads · error
%w (lock release also failed: %w)
Error message
%w (lock release also failed: %w)
What it means
MigrateUpWithLock's deferred lock-release failed while the migration itself had also failed. The library combines both errors with two %w verbs so errors.Is/As match the primary migration error first and the release error (ErrMigrationLockRelease) second, without errors.Join's newline.
Source
Thrown at internal/storage/schema/lock.go:260
// mode that leaves the GET_LOCK saturation this exists to remove
// looking like a mystery. Say so where BD_DEBUG/-v can see it.
debug.Logf("schema: convergence fast path unavailable for %q, taking the migration lock: %v\n",
databaseName, convergedErr)
case converged:
return 0, nil
}
}
lockName := MigrationLockName(databaseName)
if err := AcquireMigrationLock(ctx, conn, lockName); err != nil {
return 0, err
}
defer func() {
if releaseErr := ReleaseMigrationLock(conn, lockName); releaseErr != nil {
if err != nil {
// Two %w verbs keep errors.Is/As working for both errors
// without errors.Join's separator newline, primary first.
err = fmt.Errorf("%w (lock release also failed: %w)", err, releaseErr)
} else {
err = errors.Join(err, releaseErr)
}
}
}()
if o.lockedPreparation != nil && o.lockedPreparation.fn != nil {
capability, preparationErr := o.lockedPreparation.fn(ctx, conn)
if preparationErr != nil {
return 0, preparationErr
}
if capability != nil {
o.freshBootstrapHeal = &freshBootstrapHealRequest{
capability: capability,
endpoint: o.lockedPreparation.endpoint,
}
}
}
View on GitHub (pinned to 71377f2769)
Solutions
- Check errors.Is(err, ErrMigrationLockRelease) alongside the primary error to know cleanup was uncertain.
- The library discards the connection on release failure, so the server will free the session-scoped lock when the dead session is reaped — retry after a short delay.
- Fix the primary migration error first; the release failure is usually a symptom of the same connection problem.
- If locks appear stuck, verify no long-lived sessions hold GET_LOCK (SELECT * FROM performance_schema.metadata_locks or Dolt equivalents) and restart the sql-server if needed.
Example fix
// before
if err != nil { return err } // hides release failure
// after
if err != nil {
if errors.Is(err, schema.ErrMigrationLockRelease) {
// primary err is also wrapped; connection was discarded; safe to retry
}
return err
} Defensive patterns
Strategy: type-guard
Validate before calling
if err := conn.PingContext(ctx); err != nil { /* reacquire a healthy pinned conn before migrating */ } Type guard
func releaseAlsoFailed(err error) bool { return errors.Is(err, schema.ErrMigrationLockRelease) } Try / catch
applied, err := schema.MigrateUpWithLock(ctx, conn, db)
if err != nil {
if errors.Is(err, schema.ErrMigrationLockRelease) {
// release uncertain; conn was discarded; lock frees when session dies — retry after delay
}
// inspect primary error first: it is the leading %w
return err
} Prevention
- Keep migrations short so the session isn't reaped mid-run.
- Monitor server logs for dropped sessions on shared Dolt servers.
- Rely on errors.Is against both the primary error and ErrMigrationLockRelease.
- Retry after a delay; the discarded connection releases the session-scoped lock.
When it happens
Trigger: RELEASE_LOCK on the pinned conn errors (broken/timed-out connection, context issues) at the same time MigrateUp already returned an error — e.g. server dropped the session mid-migration.
Common situations: Dolt sql-server restart or network partition during a failing migration; idle-connection kill between GET_LOCK and RELEASE_LOCK; the migration lock left held until the session dies.
Related errors
- schema: acquire migration lock: %w: %w
- schema: acquire migration lock: %w: returned NULL
- ErrTransaction
- ErrQuery
- ErrScan
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/789187a744de2c8d.
Report an issue: GitHub.