{"record":{"id":"14ce2f433ce31e36","repo":"HangfireIO/Hangfire","slug":"could-not-release-a-lock-on-the-resource-resourc","errorCode":null,"errorMessage":"Could not release a lock on the resource '{resource}': Server returned the '{releaseResult}' error.","messagePattern":"Could not release a lock on the resource '(.+?)': Server returned the '(.+?)' error\\.","errorType":"exception","errorClass":"SqlServerDistributedLockException","httpStatus":null,"severity":"error","filePath":"src/Hangfire.SqlServer/SqlServerDistributedLock.cs","lineNumber":233,"sourceCode":"                    throw new SqlServerDistributedLockException(\n                        $\"Could not place a lock on the resource '{resource}': {(LockErrorMessages.TryGetValue(lockResult, out var message) ? message : $\"Server returned the '{lockResult}' error.\")}.\");\n                }\n            } while (started.Elapsed < timeout);\n\n            throw new DistributedLockTimeoutException(resource);\n        }\n\n        internal static void Release(DbConnection connection, string resource)\n        {\n            using (var command = CreateReleaseCommand(connection, resource, out var resultParameter))\n            {\n                command.ExecuteNonQuery();\n\n                var releaseResult = (int)resultParameter.Value;\n\n                if (releaseResult < 0)\n                {\n                    throw new SqlServerDistributedLockException(\n                        $\"Could not release a lock on the resource '{resource}': Server returned the '{releaseResult}' error.\");\n                }\n            }\n        }\n\n        internal static DbCommand CreateReleaseCommand(\n            DbConnection connection,\n            string resource,\n            out DbParameter resultParameter)\n        {\n            return connection.Create(\"sp_releaseapplock\", CommandType.StoredProcedure)\n                .AddParameter(\"@Resource\", resource, DbType.String, size: 255)\n                .AddParameter(\"@LockOwner\", LockOwner, DbType.String, size: 32)\n                .AddReturnParameter(\"@Result\", out resultParameter, DbType.Int32);\n        }\n    }\n}\n","sourceCodeStart":215,"sourceCodeEnd":251,"githubUrl":"https://github.com/HangfireIO/Hangfire/blob/c236dd0f930f831ec151e436e138ddc429a02a72/src/Hangfire.SqlServer/SqlServerDistributedLock.cs#L215-L251","documentation":"SQL Server's sp_releaseapplock stored procedure returned a negative result code when Hangfire attempted to release a distributed lock. A negative return means the current session does not own the lock or it was already released. The exception type is SqlServerDistributedLockException.","triggerScenarios":"Calling Release on a lock resource that was never acquired in the current session; double-releasing a lock (the SqlServerDistributedLock.Dispose path already released it); the connection's session changed or was reset between acquire and release; a previous acquire failed but release is still attempted.","commonSituations":"Connection pooling returning a recycled SqlConnection whose session state differs from when the lock was acquired; exception in the using-block causing Dispose to run on a partially-initialized lock holder; SQL Azure connection-killing (idle termination) between acquire and release.","solutions":["Ensure the lock is acquired before releasing; never call Release without a successful prior acquire on the same connection/session.","Use the using/dispose pattern on SqlServerDistributedLock rather than calling Release directly to guarantee correct acquire-release pairing.","Enable connection resiliency ( SqlConnectionStringBuilder.ConnectRetryCount ) to survive transient session resets, or use Microsoft.Data.SqlClient which has better resiliency defaults."],"exampleFix":"// before — manual acquire/release can get unpaired\nSqlServerDistributedLock.Acquire(connection, \"res\", timeout);\n// ... exception here ...\nSqlServerDistributedLock.Release(connection, \"res\"); // release on session that may not hold it\n// after — use the IDisposable wrapper for guaranteed pairing\nusing (new SqlServerDistributedLock(storage, \"res\", timeout))\n{\n    DoWork();\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try\n{\n    using (new SqlServerDistributedLock(storage, resource, timeout))\n    {\n        DoWork(); // Dispose handles release; no manual Release call needed\n    }\n}\ncatch (SqlServerDistributedLockException ex) when (ex.Message.Contains(\"release\"))\n{\n    logger.Warning(ex, \"Lock release returned an error for {Resource}; may be benign if session reset\", resource);\n}","preventionTips":["Always use the IDisposable SqlServerDistributedLock wrapper instead of calling Acquire/Release directly to guarantee correct pairing.","Never call Release without a successful Acquire on the same connection and session.","Be aware that SQL Azure may terminate idle connections (30 min), which can invalidate session-owned locks."],"tags":["distributed-lock","sql-server","sp-releaseapplock","connection-pooling"],"backgroundTag":null,"analyzedSha":"c236dd0f930f831ec151e436e138ddc429a02a72","analyzedAt":"2026-08-13T20:27:11.027Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}