[asp.net]"句柄无效"错误打开 SqlConnection 时

发布时间: 2017/3/26 3:38:56
注意事项: 本文中文内容可能为机器翻译,如要查看英文原文请点击上面连接.

此错误已开始发生零星,莫名其妙地,尤其是当连接到我们的会话状态数据库。这里是错误︰

   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. 可能会做什么来解决它?

解决方法 1:

事实证明,我们跟踪到 CancellationToken 与 Json.Net 反序列化错误。

根本问题时发生代码仍试图使用某个操作系统句柄的已被释放。当然,这可以发生时您的代码直接配合句柄。我们的代码不会不这样做,但事实证明这可以发生与 Json.Net。这里是如何︰

我们有一个类,如下所示︰

public class MyClass
{
   ...
}

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

有人将属性添加到类型 CancellationToken MyClass 时发生错误︰

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 )。它然后将继续填充 Handle 该标记的属性 WaitHandle 使用保存 IntPtr 值。一个明显的方式,这使得事情出错是,默认的 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"
赞助商