{"record":{"id":"789187a744de2c8d","repo":"gastownhall/beads","slug":"w-lock-release-also-failed-w","errorCode":null,"errorMessage":"%w (lock release also failed: %w)","messagePattern":"%w \\(lock release also failed: %w\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/schema/lock.go","lineNumber":260,"sourceCode":"\t\t\t// mode that leaves the GET_LOCK saturation this exists to remove\n\t\t\t// looking like a mystery. Say so where BD_DEBUG/-v can see it.\n\t\t\tdebug.Logf(\"schema: convergence fast path unavailable for %q, taking the migration lock: %v\\n\",\n\t\t\t\tdatabaseName, convergedErr)\n\t\tcase converged:\n\t\t\treturn 0, nil\n\t\t}\n\t}\n\n\tlockName := MigrationLockName(databaseName)\n\tif err := AcquireMigrationLock(ctx, conn, lockName); err != nil {\n\t\treturn 0, err\n\t}\n\tdefer func() {\n\t\tif releaseErr := ReleaseMigrationLock(conn, lockName); releaseErr != nil {\n\t\t\tif err != nil {\n\t\t\t\t// Two %w verbs keep errors.Is/As working for both errors\n\t\t\t\t// without errors.Join's separator newline, primary first.\n\t\t\t\terr = fmt.Errorf(\"%w (lock release also failed: %w)\", err, releaseErr)\n\t\t\t} else {\n\t\t\t\terr = errors.Join(err, releaseErr)\n\t\t\t}\n\t\t}\n\t}()\n\tif o.lockedPreparation != nil && o.lockedPreparation.fn != nil {\n\t\tcapability, preparationErr := o.lockedPreparation.fn(ctx, conn)\n\t\tif preparationErr != nil {\n\t\t\treturn 0, preparationErr\n\t\t}\n\t\tif capability != nil {\n\t\t\to.freshBootstrapHeal = &freshBootstrapHealRequest{\n\t\t\t\tcapability: capability,\n\t\t\t\tendpoint:   o.lockedPreparation.endpoint,\n\t\t\t}\n\t\t}\n\t}\n","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/lock.go#L242-L278","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nif err != nil { return err } // hides release failure\n// after\nif err != nil {\n    if errors.Is(err, schema.ErrMigrationLockRelease) {\n        // primary err is also wrapped; connection was discarded; safe to retry\n    }\n    return err\n}","handlingStrategy":"type-guard","validationCode":"if err := conn.PingContext(ctx); err != nil { /* reacquire a healthy pinned conn before migrating */ }","typeGuard":"func releaseAlsoFailed(err error) bool { return errors.Is(err, schema.ErrMigrationLockRelease) }","tryCatchPattern":"applied, err := schema.MigrateUpWithLock(ctx, conn, db)\nif err != nil {\n    if errors.Is(err, schema.ErrMigrationLockRelease) {\n        // release uncertain; conn was discarded; lock frees when session dies — retry after delay\n    }\n    // inspect primary error first: it is the leading %w\n    return err\n}","preventionTips":["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."],"tags":["go","database","dolt","locking","error-handling"],"backgroundTag":"lock-release-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}