HangfireIO/Hangfire · error · InvalidOperationException
Connection must be open before acquiring a distributed lock.
Error message
Connection must be open before acquiring a distributed lock.
What it means
InvalidOperationException thrown by SqlServerDistributedLock.Acquire when the supplied DbConnection is not in the Open state. Hangfire acquires an application lock (sp_getapplock) on the connection and must keep it for the lock's lifetime; Dapper would auto-close a closed connection after the command and release the lock immediately, so Hangfire refuses to run on a closed connection.
Source
Thrown at src/Hangfire.SqlServer/SqlServerDistributedLock.cs:176
// OBSOLETE. This class is not used anymore by the SqlServerConnection
// class. The problem above was solved there by establishing a
// dedicated connection, when there is at least one acquired lock.
// Since the acquisition, all the commands and transactions are routed
// through that connection to ensure all the locks are still active.
}
}
}
internal static void Acquire(DbConnection connection, string resource, TimeSpan timeout)
{
if (connection.State != ConnectionState.Open)
{
// When we are passing a closed connection to Dapper's Execute method,
// it kindly opens it for us, but after command execution, it will be closed
// automatically, and our just-acquired application lock will immediately
// be released. This is not behavior we want to achieve, so let's throw an
// exception instead.
throw new InvalidOperationException("Connection must be open before acquiring a distributed lock.");
}
var started = Stopwatch.StartNew();
// We can't pass our timeout directly to the sp_getapplock stored procedure, because
// high values, such as minute or more, may cause SQL Server's thread pool starvation,
// when the number of connections that try to acquire a lock is more than the number of
// available threads in SQL Server. In this case a deadlock will occur, when SQL Server
// tries to schedule some more work for a connection that acquired a lock, but all the
// available threads in a pool waiting for that lock to be released.
//
// So we are trying to acquire a lock multiple times instead, with timeout that's equal
// to seconds, not minutes.
var lockTimeout = (long) Math.Min(LockTimeout.TotalMilliseconds, timeout.TotalMilliseconds);
do
{
using var command = connectionView on GitHub (pinned to c236dd0f93)
Solutions
- Open the connection explicitly before acquiring the distributed lock.
- Ensure the connection stays open for the entire duration the lock is held, then dispose it.
- Acquire connections from SqlServerStorage's UseConnection helper when possible so open/close is managed for you.
Example fix
// before using var conn = new SqlConnection(cs); SqlServerDistributedLock.Acquire(conn, resource, timeout); // conn closed -> throws // after using var conn = new SqlConnection(cs); conn.Open(); SqlServerDistributedLock.Acquire(conn, resource, timeout);
Defensive patterns
Strategy: validation
Validate before calling
static DbConnection EnsureOpen(DbConnection conn)
{
if (conn.State != ConnectionState.Open) conn.Open();
return conn;
} Type guard
static bool IsConnectionOpen(DbConnection conn) => conn.State == ConnectionState.Open;
Prevention
- Always call connection.Open() before acquiring a distributed lock, and keep it open for the lock lifetime.
- Prefer storage-managed connection scopes (UseConnection) when available.
- Avoid reusing pooled connections that may close between calls around lock acquisition.
When it happens
Trigger: Calling SqlServerDistributedLock.Acquire (directly or via a distributed lock acquisition path) with a DbConnection whose State != Open.
Common situations: Forgetting connection.Open() before acquiring a lock; passing a connection from a scope/pool that closes between uses; relying on Dapper's auto-open behavior; an exception path that left a connection closed.
Related errors
- Could not release a lock on the resource '{lockCommand.Item3
- Timeout argument value should be greater than zero.
- Can not release a distributed lock: it was not acquired.
- Timeout value should be equal to or greater than zero.
- .NET Core version of the System.Data.SqlClient package below
AI-assisted analysis of HangfireIO/Hangfire@c236dd0f93 (2026-08-13).
Data as JSON: /api/errors/99de179eed370746.
Report an issue: GitHub.