HangfireIO/Hangfire · error · SqlServerDistributedLockException
Could not place a lock on the resource '{resource}': {messag
Error message
Could not place a lock on the resource '{resource}': {message}. What it means
SQL Server's sp_getapplock stored procedure returned error code -999 when Hangfire tried to acquire a distributed lock, indicating a parameter-validation or call-level error. This is a hard, non-retryable failure — the lock request itself is malformed or the database principal lacks authorization. The exception type is SqlServerDistributedLockException.
Source
Thrown at src/Hangfire.SqlServer/SqlServerDistributedLock.cs:215
.AddParameter("@DbPrincipal", "public", DbType.String, size: 32)
.AddParameter("@LockMode", LockMode, DbType.String, size: 32)
.AddParameter("@LockOwner", LockOwner, DbType.String, size: 32)
.AddParameter("@LockTimeout", lockTimeout, DbType.Int32)
.AddReturnParameter("@Result", out var resultParameter, DbType.Int32);
command.ExecuteNonQuery();
var lockResult = (int)resultParameter.Value;
if (lockResult >= 0)
{
// The lock has been successfully obtained on the specified resource.
return;
}
if (lockResult == -999 /* Indicates a parameter validation or other call error. */)
{
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(View on GitHub (pinned to c236dd0f93)
Solutions
- Grant the connecting database user membership in the db_owner role (or at minimum EXECUTE on sp_getapplock) and retry.
- Verify the connection string's credentials map to a principal with permissions in the target database.
- Check SQL Server error logs for the detailed sp_getapplock failure around the time of the exception.
- Ensure the Hangfire schema is installed in the correct database and the resource name does not exceed 255 characters.
Example fix
// before
var storage = new SqlServerStorage("Server=.;Database=Hangfire;User Id=appuser;Password=...;");
// after — grant db_owner or EXECUTE on sp_getapplock to appuser, e.g.:
// ALTER ROLE db_owner ADD MEMBER appuser;
var storage = new SqlServerStorage("Server=.;Database=Hangfire;User Id=appuser;Password=...;"); Defensive patterns
Strategy: try-catch
Validate before calling
// No purely client-side check prevents a DB-side parameter-validation error. // Pre-validate that the database user has db_owner or EXECUTE on sp_getapplock: using var cmd = connection.CreateCommand(); cmd.CommandText = "SELECT HAS_PERMS_BY_NAME(DB_NAME(), 'DATABASE', 'EXECUTE')"; var hasPermission = (int)cmd.ExecuteScalar() == 1;
Try / catch
try
{
using (storage.GetConnection().AcquireDistributedLock(resource, timeout))
{
DoWork();
}
}
catch (SqlServerDistributedLockException ex) when (ex.Message.Contains("-999"))
{
// Hard parameter-validation error: check DB permissions and schema.
logger.Error(ex, "Distributed lock acquisition failed with a call error for {Resource}", resource);
throw;
} Prevention
- Grant the Hangfire database user db_owner role or EXECUTE permission on sp_getapplock.
- Use Hangfire's built-in lock APIs (AcquireDistributedLock) rather than calling sp_getapplock directly.
- Run the Hangfire schema installer (PrepareSchemaIfNecessary) to ensure required objects exist.
When it happens
Trigger: Any Hangfire server operation that acquires a distributed lock (ExpirationManager, CountersAggregator, recurring/delayed job scheduling) when the @DbPrincipal parameter 'public' is not authorized, the @LockMode/@LockOwner values are rejected by the SQL Server version, or the connection's database principal lacks the required permissions.
Common situations: Restricted database user accounts that lack EXECUTE permission on sp_getapplock; connecting to a database where the calling principal is not mapped correctly; SQL Server edition/version differences in applock behavior; custom lock resource names that exceed the 255-character parameter size.
Related errors
- Timeout expired. The timeout elapsed prior to obtaining a di
- Could not release a lock on the resource '{resource}': Serve
- Timeout argument value should be greater than zero.
- Can not release a distributed lock: it was not acquired.
- Connection must be open before acquiring a distributed lock.
AI-assisted analysis of HangfireIO/Hangfire@c236dd0f93 (2026-08-13).
Data as JSON: /api/errors/931fd9b450a22c03.
Report an issue: GitHub.