HangfireIO/Hangfire · error · SqlServerDistributedLockException

Could not release a lock on the resource '{resource}': Serve

Error message

Could not release a lock on the resource '{resource}': Server returned the '{releaseResult}' error.

What it means

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.

Source

Thrown at src/Hangfire.SqlServer/SqlServerDistributedLock.cs:233

                    throw new SqlServerDistributedLockException(
                        $"Could not place a lock on the resource '{resource}': {(LockErrorMessages.TryGetValue(lockResult, out var message) ? message : $"Server returned the '{lockResult}' error.")}.");
                }
            } while (started.Elapsed < timeout);

            throw new DistributedLockTimeoutException(resource);
        }

        internal static void Release(DbConnection connection, string resource)
        {
            using (var command = CreateReleaseCommand(connection, resource, out var resultParameter))
            {
                command.ExecuteNonQuery();

                var releaseResult = (int)resultParameter.Value;

                if (releaseResult < 0)
                {
                    throw new SqlServerDistributedLockException(
                        $"Could not release a lock on the resource '{resource}': Server returned the '{releaseResult}' error.");
                }
            }
        }

        internal static DbCommand CreateReleaseCommand(
            DbConnection connection,
            string resource,
            out DbParameter resultParameter)
        {
            return connection.Create("sp_releaseapplock", CommandType.StoredProcedure)
                .AddParameter("@Resource", resource, DbType.String, size: 255)
                .AddParameter("@LockOwner", LockOwner, DbType.String, size: 32)
                .AddReturnParameter("@Result", out resultParameter, DbType.Int32);
        }
    }
}

View on GitHub (pinned to c236dd0f93)

Solutions

  1. Ensure the lock is acquired before releasing; never call Release without a successful prior acquire on the same connection/session.
  2. Use the using/dispose pattern on SqlServerDistributedLock rather than calling Release directly to guarantee correct acquire-release pairing.
  3. Enable connection resiliency ( SqlConnectionStringBuilder.ConnectRetryCount ) to survive transient session resets, or use Microsoft.Data.SqlClient which has better resiliency defaults.

Example fix

// before — manual acquire/release can get unpaired
SqlServerDistributedLock.Acquire(connection, "res", timeout);
// ... exception here ...
SqlServerDistributedLock.Release(connection, "res"); // release on session that may not hold it
// after — use the IDisposable wrapper for guaranteed pairing
using (new SqlServerDistributedLock(storage, "res", timeout))
{
    DoWork();
}
Defensive patterns

Strategy: try-catch

Try / catch

try
{
    using (new SqlServerDistributedLock(storage, resource, timeout))
    {
        DoWork(); // Dispose handles release; no manual Release call needed
    }
}
catch (SqlServerDistributedLockException ex) when (ex.Message.Contains("release"))
{
    logger.Warning(ex, "Lock release returned an error for {Resource}; may be benign if session reset", resource);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of HangfireIO/Hangfire@c236dd0f93 (2026-08-13). Data as JSON: /api/errors/14ce2f433ce31e36. Report an issue: GitHub.