2015-07-01 35 views
8

Questo errore si è verificato in modo sporadico e inspiegabile, in particolare durante la connessione al nostro database di stato sessione. Ecco l'errore:Errore "handle non valido" all'apertura di SqlConnection

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() 

un errore possibilmente correlati appare a volte nelle finestre Visualizzatore eventi:

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() 

EDIT: un altro sapore dell'eccezione è la seguente:

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) 
Can

chiunque suggerire:

  1. Che cosa significa questo significare?
  2. Cosa potrebbe causare questo (questo è in un'applicazione che è in esecuzione da molto tempo in modo stabile, senza che si verifichino cambiamenti infrastrutturali importanti prima che questo iniziasse a comparire)?
  3. Cosa si può fare per risolverlo?
+0

Quanto è grande la vostra piscina di connessione al database? Puoi vedere il server DB per vedere quante connessioni attive ha? – Brian

+0

Mentre state attraversando il confine .Net, sarete in grado di ottenere il dump delle eccezioni usando adplus e analizzare i thread usando windbg, con i file pdb corretti spiegherebbe chiaramente l'origine dell'eccezione. Principalmente il problema è nel codice non gestito –

+0

@Brian ci sono ~ 260 connessioni al momento. Non facciamo nulla per sovrascrivere la dimensione del pool di connessioni, quindi suppongo di avere il valore predefinito? – ChaseMedallion

risposta

7

Come si è scoperto, abbiamo rintracciato l'errore fino a deserializzare un CancellationToken con Json.Net.

Il problema sottostante si verifica quando il codice tenta ancora di utilizzare un handle del sistema operativo che è stato liberato. Naturalmente, questo può accadere quando il codice funziona direttamente con gli handle. Il nostro codice non lo fa, ma si scopre che questo può accadere con Json.Net. Ecco come:

Abbiamo avuto una classe come segue:

public class MyClass 
{ 
    ... 
} 

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

Il problema si è verificato quando qualcuno ha aggiunto una proprietà da MyClass di tipo CancellationToken:

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

Ecco il problema. Quando serializzato, un CancellationToken assomiglia a questo:

{"IsCancellationRequested":false,"CanBeCanceled":true,"WaitHandle":{"Handle":{"value":1508},"SafeWaitHandle":{"IsInvalid":false,"IsClosed":false}}} 

Nota che facendo così pigro-crea proprietà WaitHandle del token e serializza il valore di esso sottostante maniglia OS (1508).

Quando deserializziamo il token, Json.Net inizierà con new CancellationToken() (equivalente a CancellationToken.None). Procede quindi a popolare la proprietà Handle di tale token WaitHandle utilizzando il valore salvato IntPtr. Un modo ovvio in cui ciò rende le cose andare storto è che WaitHandle di CancellationToken di default ora punta a un probabile handle non valido. Tuttavia, il problema più grande è che l'aggiornamento della maniglia dereferenzia l'originale SafeHandle di WaitHandle, consentendo in questo modo al garbage collector di eseguire il finalizzatore e ripulirlo.È quindi possibile cadere vittima la seguente serie di eventi:

  1. maniglia 123 è allocato ad una connessione al database in pool
  2. Un deserializzazione assegna gestire 123 per WaitHandle del token di cancellazione di default
  3. Un secondo deserializzazione assegna un nuovo gestire valore al WaitHandle del token di cancellazione di default
  4. le piste garbage collector e finalizza l'liberato 123 sicura valore di handle
  5. la connessione al database punta ora ad un handle non valido

Ecco un po 'di codice che replica deliberatamente il problema utilizzando un 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" 
+0

Appena arrivato qui dalla lettura dell'elenco di elementi JSON.Net (sono un osservatore di repository). Grazie per aver condiviso la soluzione :) È il debug di questo genere di cose: le connessioni di SQL Server si interrompono casualmente e ti capita di usare JSON.Net per deserializzare un CancellationToken - dove mantenere questo tipo di problema nella tasca posteriore può farti risparmiare ore infinite! –

0

Si potrebbe provare ad accedere al gestore di configurazione di SQL Server e disabilitare uno o più protocolli e vedere se questo aiuta. Se SQL Server e il client si trovano sullo stesso computer, è possibile che si stia utilizzando la memoria condivisa e il sistema ha esaurito la memoria heap per gli handle. Prova a forzare le connessioni per usare TCP, e vedi se ottieni lo stesso problema.

+0

In questo caso, SQL Server e il codice dell'applicazione si trovano su macchine diverse. Questo esclude questa possibilità? È possibile che una perdita di handle diversa causi questo errore? – ChaseMedallion

+0

Sì e no. Ho notato parte dell'errore menzionato un socket, che è l'interfaccia di Windows per TCP/IP. I socket sono simili ai file del sistema operativo, quindi potrebbe ancora essere un problema di heap del desktop. Forse provare a eseguire il pool di applicazioni IIS come un utente diverso. – Wonko

Problemi correlati