打开SqlConnection时出现“句柄无效”错误

14

这个错误开始偶发且无法解释地出现,尤其是在连接到我们的会话状态数据库时。以下是错误信息:

This error has started occurring sporadically and inexplicably, particularly when connecting to our session state database. Here's the error:

   Exception type: COMException 
    Exception message: The handle is invalid. (Exception from HRESULT: 0x80070006 (E_HANDLE))
   at System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo)
   at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)
   at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection)
   at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)
   at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
   at System.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry)
   at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)
   at System.Data.SqlClient.SqlConnection.Open()

有时候,在Windows事件查看器中会出现一个可能相关的错误:

Application: w3wp.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.Threading.SemaphoreFullException
Stack:
   at System.Threading.Semaphore.Release(Int32)
   at System.Data.ProviderBase.DbConnectionPool.CleanupCallback(System.Object)
   at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   at System.Threading.TimerQueueTimer.CallCallback()
   at System.Threading.TimerQueueTimer.Fire()
   at System.Threading.TimerQueue.FireNextTimers()

编辑:异常的另一种类型如下:

Exception Type: System.ComponentModel.Win32Exception
Error message: An operation was attempted on something that is not a socket
No Stack Trace Available
Exception Type: System.Data.SqlClient.SqlException
Error message: A transport-level error has occurred when sending the request to the server. (provider: TCP Provider, error: 0 - An operation was attempted on something that is not a socket.)
at System.Data.SqlClient.TdsParser.TdsExecuteRPC(_SqlRPC[] rpcArray, Int32 timeout, Boolean inSchema, SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj, Boolean isCommandProc, Boolean sync, TaskCompletionSource`1 completion, Int32 startRpc, Int32 startParam)
at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, SqlDataReader ds)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean asyncWrite)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior)
at System.Web.SessionState.SqlSessionStateStore.SqlExecuteReaderWithRetry(SqlCommand cmd, CommandBehavior cmdBehavior)
Exception Type: System.Web.HttpException
Error message: Unable to connect to SQL Server session database.
at System.Web.SessionState.SqlSessionStateStore.SqlExecuteReaderWithRetry(SqlCommand cmd, CommandBehavior cmdBehavior)
at System.Web.SessionState.SqlSessionStateStore.DoGet(HttpContext context, String id, Boolean getExclusive, Boolean& locked, TimeSpan& lockAge, Object& lockId, SessionStateActions& actionFlags)
at System.Web.SessionState.SqlSessionStateStore.GetItem(HttpContext context, String id, Boolean& locked, TimeSpan& lockAge, Object& lockId, SessionStateActions& actionFlags)
at System.Web.SessionState.SessionStateModule.GetSessionStateItem()
at System.Web.SessionState.SessionStateModule.BeginAcquireState(Object source, EventArgs e, AsyncCallback cb, Object extraData)
at System.Web.HttpApplication.AsyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

有人能提出建议吗:

  1. 这是什么意思?
  2. 可能是什么原因造成的(这是一个长时间非常稳定运行的应用程序,在这个问题出现之前没有发生任何重大基础架构变化)?
  3. 可能采取什么措施来解决它?

你的数据库连接池有多大? 你能查看数据库服务器以确定它有多少活动连接吗? - Brian
由于您正在跨越 .Net 边界,您是否能够使用 adplus 获取异常转储,并使用 windbg 分析线程,在正确的 pdb 文件下,它将清楚地解释异常的起源。大多数情况下,问题在非托管代码中。 - Mrinal Kamboj
@Brian 目前有大约260个连接。我们没有做任何覆盖连接池大小的操作,所以我猜我们使用的是默认值? - ChaseMedallion
4个回答

15

事实证明,我们追踪到错误是由于使用Json.Net对CancellationToken进行反序列化。

潜在问题出现在代码仍然试图使用已释放的操作系统句柄时。这当然可能发生在您的代码直接使用句柄时。我们的代码并没有这样做,但结果表明,Json.Net可能会出现这种情况。具体原因如下:

我们有一个类如下:

public class MyClass
{
   ...
}

// in one part of the code, this class was serialized & deserialized using Json.Net:
JsonConvert.SerializeObject(...);
JsonConvert.DeserializeObject<MyClass>(...);

当某人向MyClass添加了一个类型为CancellationToken的属性时,出现了问题:

public class MyClass
{
    ...
    public CancellationToken Token { get; set; }
}

问题在于,当 CancellationToken 序列化时,它长成这样:

{"IsCancellationRequested":false,"CanBeCanceled":true,"WaitHandle":{"Handle":{"value":1508},"SafeWaitHandle":{"IsInvalid":false,"IsClosed":false}}}
注意,这样做会惰性创建标记的WaitHandle属性,并序列化其底层操作系统句柄的值(1508)。
当我们反序列化令牌时,Json.Net将从`new CancellationToken()`(等效于`CancellationToken.None`)开始。然后,它将使用保存的IntPtr值填充该令牌的WaitHandle的Handle属性。这种方法明显出错的一个明显方式是默认的CancellationToken的WaitHandle现在指向可能无效的句柄。但是,更大的问题是更新句柄会对WaitHandle的原始SafeHandle进行解除引用,从而允许垃圾回收器运行其终结器并清理它。然后,您可能会遭受以下一系列事件:
1. 将句柄123分配给池化数据库连接 2. 反序列化将句柄123分配给默认取消令牌的WaitHandle 3. 第二次反序列化将新的句柄值分配给默认取消令牌的WaitHandle 4. 垃圾回收器运行并完成释放了句柄123的安全句柄值 5. 数据库连接现在指向无效句柄
下面是一些故意使用FileStream复制问题的代码:
// serialize 2 tokens
var source = new CancellationTokenSource();
var serialized = JsonConvert.SerializeObject(source.Token);
var serialized2 = JsonConvert.SerializeObject(new CancellationTokenSource().Token);
var handle = source.Token.WaitHandle.Handle;
source.Dispose(); // releases source's handle

// spin until the OS gives us back that same handle as
// a file handle
FileStream fileStream;
while (true)
{
    fileStream = new FileStream(Path.GetTempFileName(), FileMode.OpenOrCreate);
    if (fileStream.Handle == handle) { break; }
}

// deserialize both tokens, thus releasing the conflicting handle
var deserialized = JsonConvert.DeserializeObject<CancellationToken>(serialized);
var deserialized2 = JsonConvert.DeserializeObject<CancellationToken>(serialized2);

GC.Collect();
GC.WaitForPendingFinalizers();

fileStream.WriteByte(1);
fileStream.Flush(); // fails with IOException "The handle is invalid"

刚刚从阅读 JSON.Net 项目列表(我是一个仓库观察者)过来。感谢分享这个解决方案 :) 当你遇到 SQL Server 连接随机断开并且恰好使用 JSON.Net 反序列化 CancellationToken 进行调试时,把这种问题放在你的口袋里可以节省无数小时的时间! - Ian Yates
天啊,你真是个天才。 - Andrew Richesson

7

在经过一段时间的搜索后,没有找到答案,最终唯一解决错误的方法是iisreset


1
你看了我的(被接受的)答案吗?感觉很有可能在你的应用程序中有这个或其他实例的处理程序损坏。IIS重置最多只能是一个临时解决方案。 - ChaseMedallion
这个答案指引了我正确的方向。我遇到了同样的问题,但是在IISExpress中从VS运行。停止站点并退出IISExpress解决了我的问题。 - maets

0

在IIS中,从应用程序池的高级设置中将32位应用程序更改为false

1/ 点击应用程序池 2/ 选择您的应用程序池并右键单击它 3/ 选择高级设置... 4/ 启用32位应用程序 = false


0
你可以尝试进入SQL Server配置管理器,禁用一个或多个协议,看看是否有帮助。如果SQL Server和客户端在同一台机器上,您可能正在使用共享内存,并且系统已经耗尽了句柄的堆内存。尝试强制连接使用TCP,并查看是否出现相同的问题。

在这种情况下,SQL Server和应用程序代码位于不同的机器上。这是否排除了这种可能性?另一个句柄泄漏是否会导致此错误? - ChaseMedallion
是的和不是。我注意到错误的一部分提到了套接字,这是Windows对TCP/IP的接口。套接字在操作系统中看起来类似于文件,所以它仍然可能是桌面堆问题。也许尝试将IIS应用程序池作为不同的用户运行。 - Wonko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接