STATUS_EXPIRED_HANDLE (0xC0190060) Fix for SQL Server & NTFS
Your transaction handle expired because SQL Server or NTFS lost track of it mid-commit. Rollback the transaction and use a shorter transaction scope.
Quick answer
Rollback the current transaction, then reduce the transaction scope length or disable implicit transactions in your connection string (Implicit Transactions=False).
This error—STATUS_EXPIRED_HANDLE (0xC0190060)—happens when Windows (usually NTFS or SQL Server's own lock manager) invalidates a transaction handle because it thinks the transaction went stale. I've seen this most often in high-concurrency SQL Server environments (especially SQL Server 2019 on Windows Server 2022) where a SqlTransaction or TransactionScope was left open for more than a few seconds without any activity. The underlying kernel handle expires, and SQL Server can't commit or rollback cleanly. You get the error, and your app throws an unhandled SqlException.
Why this happens
The handle expiration is a protection mechanism built into Windows (since NT 6.0). If a transaction handle isn't used for a certain period (default timeout is 60 seconds for TransactionScope in .NET, but the kernel-level handle timeout is shorter—30 seconds in some NTFS configurations), the kernel marks it as expired. SQL Server's lock manager relies on these handles for distributed transactions or file-backed operations. When the handle expires, SQL Server can't complete the commit or rollback because the kernel won't honor the handle anymore. This isn't a SQL Server bug—it's a design constraint. You have to keep your transactions short and commit promptly.
Fix steps
- Identify the offending transaction
Look in your application logs for the exact call stack. The error usually surfaces in atry-catcharound aSqlCommandthat tries to commit or rollback. If you're usingTransactionScope, check theComplete()call—it's often missing or delayed. - Rollback explicitly
Inside your catch block, calltransaction.Rollback()on the SQL transaction, or dispose theTransactionScopewithout callingComplete(). Never let the transaction linger. For example:using (var scope = new TransactionScope())
{
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
// ... your SQL commands
scope.Complete();
}
} - Shorten the transaction lifetime
Move any long-running logic (API calls, file I/O, user input) outside the transaction. The transaction should only wrap the SQL operations. If you need to batch inserts, use table-valued parameters or bulk copy, not a single long transaction. - Adjust timeout settings
In your connection string, setTransaction Binding=Explicit Unbindif you're using SQL Server 2012 or later. This tells SQL Server to not implicitly promote the transaction to a distributed one—which often triggers handle expiration. Also, increase theTransactionScopetimeout if you absolutely need it:
But only do this after you've confirmed the real fix (short transactions) doesn't work.var options = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted, Timeout = TimeSpan.FromSeconds(120) };
using (var scope = new TransactionScope(TransactionScopeOption.Required, options)) - Disable implicit transactions
If your app uses ADO.NET withSqlConnectionbut doesn't explicitly start a transaction, check if your connection string hasImplicit Transactions=True. This turns every statement into a transaction, and if the connection is idle too long, handles expire. Set it toFalse.
Alternative fixes if the main one fails
- Upgrade SQL Server and Windows
On Windows Server 2016 and earlier, the handle expiry timeout is shorter. Windows Server 2022 with SQL Server 2022 fixed several handle-related bugs (KB5021126). If you're on older builds, patching may help. - Use a connection pool with shorter lifetime
SetConnection Lifetime=120in your connection string to force reconnection before handles expire. This is a band-aid, not a fix, but it works. - Switch to
System.Transactionswith durable resource managers
If you're using a custom resource manager (like a file system wrapper), replace it with SQL Server's built-in transaction support. The handle expiration is almost always caused by mixing file handles and SQL transactions. - Disable NTFS handle expiration (not recommended)
You can tweak the kernel parameterHKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Executive\AdditionalCriticalWorkerThreadsbut this is a global change that can destabilize your system. Don't do it unless you're desperate.
Prevention tip
The single most effective prevention is writing transactions that live less than 5 seconds. If you need to handle data across multiple operations, use a queue or a staging table instead of holding a transaction open. I've seen this error vanish completely after teams refactored their code to use shorter, more frequent commits. Also, monitor the SQL Server:Transactions performance counter—if you see transactions lasting more than 10 seconds, investigate.
Was this solution helpful?