Fix websocket close handshake issue and race condition when websocket client disconnect without close handshake (#151)

This commit is contained in:
pan-wang 2017-09-11 18:44:15 -07:00 committed by GitHub
parent 509ddc6ced
commit a732b106f5
8 changed files with 76 additions and 32 deletions

View File

@ -136,6 +136,7 @@ public:
PVOID pvData
)
{
// best effort copy, ignore the failure
ENVIRONMENT_VAR_ENTRY * pNewEntry = new ENVIRONMENT_VAR_ENTRY();
if (pNewEntry != NULL)
{
@ -143,6 +144,7 @@ public:
ENVIRONMENT_VAR_HASH *pHash = static_cast<ENVIRONMENT_VAR_HASH *>(pvData);
DBG_ASSERT(pHash);
pHash->InsertRecord(pNewEntry);
// Need to dereference as InsertRecord references it now
pNewEntry->Dereference();
}
}

View File

@ -322,6 +322,7 @@ private:
BOOL m_fWebSocketUpgrade;
BOOL m_fFinishRequest;
BOOL m_fClientDisconnected;
BOOL m_fHasError;
DWORD m_msStartTime;
DWORD m_BytesToReceive;

View File

@ -313,10 +313,6 @@ private:
HANDLE m_hListeningProcessHandle;
HANDLE m_hProcessWaitHandle;
HANDLE m_hShutdownHandle;
//
// m_hChildProcessHandle is the handle to process created by
// m_hProcessHandle process if it does.
//
// m_hChildProcessHandle is the handle to process created by
// m_hProcessHandle process if it does.

View File

@ -206,6 +206,9 @@ private:
volatile
BOOL _fIndicateCompletionToIis;
volatile
BOOL _fReceivedCloseMsg;
static
LIST_ENTRY sm_RequestsListHead;

View File

@ -376,6 +376,7 @@ ASPNETCORE_CONFIG::Populate(
strExpandedEnvValue.Reset();
pEnvVar->Release();
pEnvVar = NULL;
pEntry->Dereference();
pEntry = NULL;
}

View File

@ -53,7 +53,8 @@ m_pAppOfflineHtm(NULL),
m_fErrorHandled(FALSE),
m_fWebSocketUpgrade(FALSE),
m_fFinishRequest(FALSE),
m_fClientDisconnected(FALSE)
m_fClientDisconnected(FALSE),
m_fHasError(FALSE)
{
#ifdef DEBUG
DBGPRINTF((DBG_CONTEXT,
@ -1171,6 +1172,7 @@ FORWARDING_HANDLER::OnExecuteRequestHandler(
FALSE
); // no need to check return hresult
m_fHasError = TRUE;
goto Finished;
}
@ -1368,6 +1370,7 @@ Failure:
// Reset status for consistency.
//
m_RequestStatus = FORWARDER_DONE;
m_fHasError = TRUE;
pResponse->DisableKernelCache();
pResponse->GetRawHttpResponse()->EntityChunkCount = 0;
@ -1592,7 +1595,10 @@ REQUEST_NOTIFICATION_STATUS
{
if (m_RequestStatus == FORWARDER_DONE && m_fFinishRequest)
{
retVal = RQ_NOTIFICATION_FINISH_REQUEST;
if (m_fHasError)
{
retVal = RQ_NOTIFICATION_FINISH_REQUEST;
}
goto Finished;
}
@ -1706,7 +1712,7 @@ Failure:
// Reset status for consistency.
//
m_RequestStatus = FORWARDER_DONE;
m_fHasError = TRUE;
//
// Do the right thing based on where the error originated from.
//
@ -1779,8 +1785,17 @@ Failure:
//
// Finish the request on failure.
// Let IIS pipeline continue only after receiving handle close callback
// from WinHttp. This ensures no more callback from WinHttp
//
retVal = RQ_NOTIFICATION_FINISH_REQUEST;
if (m_hRequest != NULL)
{
if (WinHttpCloseHandle(m_hRequest))
{
m_hRequest = NULL;
}
}
retVal = RQ_NOTIFICATION_PENDING;
Finished:
@ -2249,7 +2264,6 @@ None
fDerefForwardingHandler = m_fClientDisconnected && !m_fWebSocketUpgrade;
}
m_hRequest = NULL;
m_pWebSocket = NULL;
fAnotherCompletionExpected = FALSE;
break;
case WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED:
@ -2391,11 +2405,13 @@ Finished:
m_hRequest = NULL;
}
}
//
// If the request is a websocket request, initiate cleanup.
//
if (m_pWebSocket != NULL)
{
m_pWebSocket->TerminateRequest();
m_pWebSocket->Terminate();
m_pWebSocket = NULL;
}
if(fEndRequest)

View File

@ -2280,13 +2280,18 @@ SERVER_PROCESS::SendShutDownSignalInternal(
TerminateBackendProcess();
}
FreeConsole();
}
if (fFreeConsole)
if (fFreeConsole)
{
// IISExpress and hostedwebcore w3wp run as background process
// have to attach console back to ensure post app_offline scenario still works
AttachConsole(ATTACH_PARENT_PROCESS);
}
}
else
{
// IISExpress and hostedwebcore w3wp run as background process
// have to attach console back to ensure post app_offline scenario still works
AttachConsole(ATTACH_PARENT_PROCESS);
// terminate the backend process immediately instead of waiting for timeout
TerminateBackendProcess();
}
}

View File

@ -35,13 +35,15 @@ LIST_ENTRY WEBSOCKET_HANDLER::sm_RequestsListHead;
TRACE_LOG * WEBSOCKET_HANDLER::sm_pTraceLog;
WEBSOCKET_HANDLER::WEBSOCKET_HANDLER():
_pHttpContext ( NULL ),
_pWebSocketContext ( NULL ),
_pHandler ( NULL ),
_dwOutstandingIo ( 0 ),
_fCleanupInProgress ( FALSE ),
_fIndicateCompletionToIis ( FALSE )
WEBSOCKET_HANDLER::WEBSOCKET_HANDLER() :
_pHttpContext(NULL),
_pWebSocketContext(NULL),
_hWebSocketRequest(NULL),
_pHandler(NULL),
_dwOutstandingIo(0),
_fCleanupInProgress(FALSE),
_fIndicateCompletionToIis(FALSE),
_fReceivedCloseMsg(FALSE)
{
DebugPrintf (ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::WEBSOCKET_HANDLER");
@ -58,16 +60,20 @@ WEBSOCKET_HANDLER::Terminate(
DebugPrintf (ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::Terminate");
RemoveRequest();
_fCleanupInProgress = TRUE;
_pWebSocketContext = NULL;
_pHttpContext = NULL;
if (_pHttpContext != NULL)
{
_pHttpContext->CancelIo();
_pHttpContext = NULL;
}
if (_hWebSocketRequest)
{
WinHttpCloseHandle(_hWebSocketRequest);
_hWebSocketRequest = NULL;
}
_pWebSocketContext = NULL;
DeleteCriticalSection(&_RequestLock);
delete this;
@ -216,6 +222,9 @@ WEBSOCKET_HANDLER::IndicateCompletionToIIS(
// wait for handle close callback and then call IndicateCompletion
// otherwise we may release W3Context too early and cause AV
//_pHttpContext->IndicateCompletion(RQ_NOTIFICATION_PENDING);
// close Websocket handle. This will triger a WinHttp callback
// on handle close, then let IIS pipeline continue.
WinHttpCloseHandle(_hWebSocketRequest);
}
HRESULT
@ -498,6 +507,12 @@ Routine Description:
}
IncrementOutstandingIo();
//
// Backend end may start close hand shake first
// Need to inidcate no more receive should be called on WinHttp conneciton
//
_fReceivedCloseMsg = TRUE;
_fIndicateCompletionToIis = TRUE;
//
// Send close to IIS.
@ -947,14 +962,19 @@ Routine Description:
}
//
// Write Completed, initiate next read from backend server.
// Only call read if no close hand shake was received from backend
//
hr = DoWinHttpWebSocketReceive();
if (FAILED (hr))
if (!_fReceivedCloseMsg)
{
cleanupReason = ServerDisconnect;
goto Finished;
//
// Write Completed, initiate next read from backend server.
//
hr = DoWinHttpWebSocketReceive();
if (FAILED(hr))
{
cleanupReason = ServerDisconnect;
goto Finished;
}
}
Finished: