{"record":{"id":"f146782de7509895","repo":"gofr-dev/gofr","slug":"failed-to-release-migration-lock","errorCode":null,"errorMessage":"failed to release migration lock","messagePattern":"failed to release migration lock","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/gofr/migration/migration.go","lineNumber":20,"sourceCode":"\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"reflect\"\n\t\"sort\"\n\t\"time\"\n\n\t\"github.com/gogo/protobuf/sortkeys\"\n\t\"github.com/google/uuid\"\n\tgoRedis \"github.com/redis/go-redis/v9\"\n\n\t\"gofr.dev/pkg/gofr/container\"\n\tgofrSql \"gofr.dev/pkg/gofr/datasource/sql\"\n)\n\nvar (\n\terrLockAcquisitionFailed = errors.New(\"failed to acquire migration lock\")\n\terrLockReleaseFailed     = errors.New(\"failed to release migration lock\")\n)\n\nconst (\n\t// lockKey is the key used for distributed locking.\n\tlockKey = \"gofr_migrations_lock\"\n\n\t// Default values for configuration.\n\tdefaultRetry = 500 * time.Millisecond\n\t// defaultLockTTL is the duration for which the migration lock is valid.\n\t// It is kept at 15 seconds to provide a safety margin for network jitters or transient failures.\n\tdefaultLockTTL = 15 * time.Second\n\t// defaultRefresh is the interval at which the migration lock is renewed.\n\t// A 5-second interval allows for up to 2 failed refresh attempts before the 15-second TTL expires,\n\t// ensuring the lock stays robust while still allowing fairly quick recovery if a process crashes.\n\tdefaultRefresh = 5 * time.Second\n)\n\ntype MigrateFunc func(d Datasource) error","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/gofr-dev/gofr/blob/187eb24962502e91f1fee856230670958b66e89c/pkg/gofr/migration/migration.go#L2-L38","documentation":"errLockReleaseFailed is returned by unlock() when the migration lock cannot be released after migrations finish. Subsequent migrations will be blocked until the lock expires or is removed manually. It signals a Redis error or that the lock was already lost to another holder.","triggerScenarios":"Redis DEL (or compare-and-delete) on gofr_migrations_lock fails: client error, connection loss, or the key no longer matches this holder.","commonSituations":"Redis restarted mid-migration losing key semantics; network partition at unlock time; lock TTL expired during a very long migration and another node took it.","solutions":["Manually delete gofr_migrations_lock if no migration is running","Verify Redis connectivity/auth and inspect client errors in logs","Increase lock TTL or use heartbeat renewal for long migrations","Re-run the next migration attempt once the lock is free"],"exampleFix":"// before\n// after failed unlock: redis-cli EXISTS gofr_migrations_lock shows stale key\n// after\nredis-cli DEL gofr_migrations_lock","handlingStrategy":"try-catch","validationCode":"if redisClient == nil || redisClient.Ping(ctx).Err() != nil {\n    return errors.New(\"redis unavailable; lock release would fail\")\n}","typeGuard":null,"tryCatchPattern":"err := migrator.Run(c)\nif errors.Is(err, migration.ErrLockReleaseFailed) {\n    log.Warn(\"migration succeeded but lock release failed; clear gofr_migrations_lock manually\")\n    // cleanup: redis-cli DEL gofr_migrations_lock\n}","preventionTips":["Delete the lock key manually if migrations completed but unlock failed","Use heartbeat renewal / adequate TTL for long migrations","Alert on unlock failures so stale locks are caught early","Keep the Redis session alive across the whole migration (no idle timeouts)"],"tags":["migration","distributed-lock","redis"],"backgroundTag":"lock-release-failed","analyzedSha":"187eb24962502e91f1fee856230670958b66e89c","analyzedAt":"2026-09-01T20:34:54.554Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}