{"record":{"id":"99de179eed370746","repo":"HangfireIO/Hangfire","slug":"connection-must-be-open-before-acquiring-a-distrib","errorCode":null,"errorMessage":"Connection must be open before acquiring a distributed lock.","messagePattern":"Connection must be open before acquiring a distributed lock\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Hangfire.SqlServer/SqlServerDistributedLock.cs","lineNumber":176,"sourceCode":"                    // OBSOLETE. This class is not used anymore by the SqlServerConnection\n                    // class. The problem above was solved there by establishing a\n                    // dedicated connection, when there is at least one acquired lock.\n                    // Since the acquisition, all the commands and transactions are routed\n                    // through that connection to ensure all the locks are still active.\n                }\n            }\n        }\n\n        internal static void Acquire(DbConnection connection, string resource, TimeSpan timeout)\n        {\n            if (connection.State != ConnectionState.Open)\n            {\n                // When we are passing a closed connection to Dapper's Execute method,\n                // it kindly opens it for us, but after command execution, it will be closed\n                // automatically, and our just-acquired application lock will immediately\n                // be released. This is not behavior we want to achieve, so let's throw an\n                // exception instead.\n                throw new InvalidOperationException(\"Connection must be open before acquiring a distributed lock.\");\n            }\n\n            var started = Stopwatch.StartNew();\n\n            // We can't pass our timeout directly to the sp_getapplock stored procedure, because\n            // high values, such as minute or more, may cause SQL Server's thread pool starvation,\n            // when the number of connections that try to acquire a lock is more than the number of \n            // available threads in SQL Server. In this case a deadlock will occur, when SQL Server \n            // tries to schedule some more work for a connection that acquired a lock, but all the \n            // available threads in a pool waiting for that lock to be released.\n            //\n            // So we are trying to acquire a lock multiple times instead, with timeout that's equal\n            // to seconds, not minutes.\n            var lockTimeout = (long) Math.Min(LockTimeout.TotalMilliseconds, timeout.TotalMilliseconds);\n\n            do\n            {\n                using var command = connection","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/HangfireIO/Hangfire/blob/c236dd0f930f831ec151e436e138ddc429a02a72/src/Hangfire.SqlServer/SqlServerDistributedLock.cs#L158-L194","documentation":"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.","triggerScenarios":"Calling SqlServerDistributedLock.Acquire (directly or via a distributed lock acquisition path) with a DbConnection whose State != Open.","commonSituations":"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.","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."],"exampleFix":"// before\nusing var conn = new SqlConnection(cs);\nSqlServerDistributedLock.Acquire(conn, resource, timeout); // conn closed -> throws\n\n// after\nusing var conn = new SqlConnection(cs);\nconn.Open();\nSqlServerDistributedLock.Acquire(conn, resource, timeout);","handlingStrategy":"validation","validationCode":"static DbConnection EnsureOpen(DbConnection conn)\n{\n    if (conn.State != ConnectionState.Open) conn.Open();\n    return conn;\n}","typeGuard":"static bool IsConnectionOpen(DbConnection conn) => conn.State == ConnectionState.Open;","tryCatchPattern":null,"preventionTips":["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."],"tags":["sqlserver","distributed-lock","connection"],"backgroundTag":null,"analyzedSha":"c236dd0f930f831ec151e436e138ddc429a02a72","analyzedAt":"2026-08-13T20:27:11.027Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}