diff --git a/src/AspNetCore/Inc/resource.h b/src/AspNetCore/Inc/resource.h index 8b2b9f9844..a7f1b30543 100644 --- a/src/AspNetCore/Inc/resource.h +++ b/src/AspNetCore/Inc/resource.h @@ -16,3 +16,5 @@ #define ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG L"Application '%s' with physical root '%s' created process with commandline '%s' but either crashed or did not reponse or did not listen on the given port '%d', ErrorCode = '0x%x'" #define ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG L"Warning: Could not create stdoutLogFile %s, ErrorCode = %d." #define ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG L"Failed to gracefully shutdown process '%d'." +#define ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST_MSG L"Sent shutdown HTTP message to process '%d' and received http status '%d'." + diff --git a/src/AspNetCore/Inc/serverprocess.h b/src/AspNetCore/Inc/serverprocess.h index 8d3f27cb33..e83c5ecef0 100644 --- a/src/AspNetCore/Inc/serverprocess.h +++ b/src/AspNetCore/Inc/serverprocess.h @@ -252,6 +252,27 @@ private: return digits; } + static + VOID + SendShutDownSignal( + LPVOID lpParam + ); + + VOID + SendShutDownSignalInternal( + VOID + ); + + HRESULT + SendShutdownHttpMessage( + VOID + ); + + VOID + TerminateBackendProcess( + VOID + ); + FORWARDER_CONNECTION *m_pForwarderConnection; BOOL m_fStdoutLogEnabled; BOOL m_fWindowsAuthEnabled; @@ -291,6 +312,11 @@ private: HANDLE m_hProcessHandle; 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. diff --git a/src/AspNetCore/Src/aspnetcore_msg.mc b/src/AspNetCore/Src/aspnetcore_msg.mc index 6e009c8b50..09cc4615e0 100644 --- a/src/AspNetCore/Src/aspnetcore_msg.mc +++ b/src/AspNetCore/Src/aspnetcore_msg.mc @@ -61,6 +61,12 @@ Language=English %1 . +Messageid=1006 +SymbolicName=ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST +Language=English +%1 +. + ; ;#endif // _ASPNETCORE_MODULE_MSG_H_ ; \ No newline at end of file diff --git a/src/AspNetCore/Src/serverprocess.cxx b/src/AspNetCore/Src/serverprocess.cxx index 7a20383f01..0d2346bbae 100644 --- a/src/AspNetCore/Src/serverprocess.cxx +++ b/src/AspNetCore/Src/serverprocess.cxx @@ -647,7 +647,6 @@ SERVER_PROCESS::SetupCommandLine( goto Finished; } - Finished: if (pszFullPath != NULL) { @@ -1056,7 +1055,7 @@ Finished: pHashTable = NULL; } - if ( FAILED(hr) ) + if (FAILED(hr)) { if (strEventMsg.IsEmpty()) { @@ -1093,7 +1092,7 @@ Finished: } } - if (FAILED( hr ) || m_fReady == FALSE) + if (FAILED(hr) || m_fReady == FALSE) { if (m_hStdoutHandle != NULL) { @@ -1448,93 +1447,70 @@ Finished: return hr; } -// send ctrl-c signnal to the process to let it gracefully shutdown +// send signal to the process to let it gracefully shutdown // if the process cannot shutdown within given time, terminate it -// todo: allow user to config this shutdown timeout - VOID SERVER_PROCESS::SendSignal( VOID ) { - HANDLE hProc = INVALID_HANDLE_VALUE; - BOOL fIsSuccess = FALSE; - BOOL fFreeConsole = FALSE; - LPCWSTR apsz[1]; - STACK_STRU(strEventMsg, 256); + HRESULT hr = S_OK; + HANDLE hThread = NULL; ReferenceServerProcess(); - hProc = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, m_dwProcessId); - if (hProc != INVALID_HANDLE_VALUE) + m_hShutdownHandle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, m_dwProcessId); + + if (m_hShutdownHandle == NULL) { - // free current console first, as we migh have one, e.g., hostedwebcore case - fFreeConsole = FreeConsole(); - - if (AttachConsole(m_dwProcessId)) - { - // call ctrl-break instead of ctrl-c as child process may ignore ctrl-c - fIsSuccess = GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, m_dwProcessId); - FreeConsole(); - } - - if(fFreeConsole) - { - // IISExpress and hostedwebcore w3wp run as background process - // have to attach console back to ensure post app_offline scenario still work - AttachConsole(ATTACH_PARENT_PROCESS); - } - } - - if (!fIsSuccess || (WaitForSingleObject(hProc, m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0)) - { - if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) - { - //Backend process will be terminated, remove the waitcallback - if (m_hProcessWaitHandle != NULL) - { - UnregisterWait(m_hProcessWaitHandle); - m_hProcessWaitHandle = NULL; - } - - // cannot gracefully shutdown or timeout, terminate the process - TerminateProcess(m_hProcessHandle, 0); - - // as we skipped process exit callback (ProcessHandleCallback), - // need to dereference the object otherwise memory leak - DereferenceServerProcess(); - - // log a warning for ungraceful shutdown - if (SUCCEEDED(strEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG, - m_dwProcessId))) - { - apsz[0] = strEventMsg.QueryStr(); - if (FORWARDING_HANDLER::QueryEventLog() != NULL) - { - ReportEventW(FORWARDING_HANDLER::QueryEventLog(), - EVENTLOG_WARNING_TYPE, - 0, - ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE, - NULL, - 1, - 0, - apsz, - NULL); - } - } - } + // since we cannot open the process. let's terminate the process + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; } - if (hProc != INVALID_HANDLE_VALUE) + hThread = CreateThread( + NULL, // default security attributes + 0, // default stack size + (LPTHREAD_START_ROUTINE)SendShutDownSignal, + this, // thread function arguments + 0, // default creation flags + NULL); // receive thread identifier + + if (hThread == NULL) { - CloseHandle(hProc); - hProc = INVALID_HANDLE_VALUE; + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; } - if (m_hProcessHandle != INVALID_HANDLE_VALUE) + + if (WaitForSingleObject(m_hShutdownHandle, m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0) { - CloseHandle(m_hProcessHandle); - m_hProcessHandle = INVALID_HANDLE_VALUE; + hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); + goto Finished; + } + + +Finished: + if (hThread != NULL) + { + // if the send shutdown message thread is still running, terminate it + DWORD dwThreadStatus = 0; + if (GetExitCodeThread(hThread, &dwThreadStatus)!= 0 && dwThreadStatus == STILL_ACTIVE) + { + TerminateThread(hThread, STATUS_CONTROL_C_EXIT); + } + CloseHandle(hThread); + hThread = NULL; + } + + if (FAILED(hr)) + { + TerminateBackendProcess(); + } + + if (m_hShutdownHandle != NULL && m_hShutdownHandle != INVALID_HANDLE_VALUE) + { + CloseHandle(m_hShutdownHandle); + m_hShutdownHandle = NULL; } DereferenceServerProcess(); @@ -1609,8 +1585,8 @@ SERVER_PROCESS::IsDebuggerIsAttached( } processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc( - GetProcessHeap(), - 0, + GetProcessHeap(), + 0, cbNumBytes ); if (processList == NULL) @@ -1619,14 +1595,14 @@ SERVER_PROCESS::IsDebuggerIsAttached( goto Finished; } - RtlZeroMemory( processList, cbNumBytes ); + RtlZeroMemory(processList, cbNumBytes); if (!QueryInformationJobObject( - m_hJobObject, - JobObjectBasicProcessIdList, - processList, - cbNumBytes, - NULL) ) + m_hJobObject, + JobObjectBasicProcessIdList, + processList, + cbNumBytes, + NULL)) { dwError = GetLastError(); if (dwError != ERROR_MORE_DATA) @@ -1639,7 +1615,7 @@ SERVER_PROCESS::IsDebuggerIsAttached( } while (dwRetries++ < 5 && processList != NULL && (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || - processList->NumberOfProcessIdsInList == 0 ) ); + processList->NumberOfProcessIdsInList == 0)); if (dwError == ERROR_MORE_DATA) { @@ -1669,11 +1645,11 @@ SERVER_PROCESS::IsDebuggerIsAttached( if (dwPid != dwWorkerProcessPid) { HANDLE hProcess = OpenProcess( - PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, - FALSE, - dwPid); + PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, + FALSE, + dwPid); - BOOL returnValue = CheckRemoteDebuggerPresent( hProcess, &fDebuggerPresent ); + BOOL returnValue = CheckRemoteDebuggerPresent(hProcess, &fDebuggerPresent); if (!returnValue) { goto Finished; @@ -1725,8 +1701,8 @@ SERVER_PROCESS::GetChildProcessHandles( } processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc( - GetProcessHeap(), - 0, + GetProcessHeap(), + 0, cbNumBytes ); if (processList == NULL) @@ -1735,13 +1711,13 @@ SERVER_PROCESS::GetChildProcessHandles( goto Finished; } - RtlZeroMemory( processList, cbNumBytes ); + RtlZeroMemory(processList, cbNumBytes); if (!QueryInformationJobObject( - m_hJobObject, - JobObjectBasicProcessIdList, - processList, - cbNumBytes, + m_hJobObject, + JobObjectBasicProcessIdList, + processList, + cbNumBytes, NULL)) { dwError = GetLastError(); @@ -1752,9 +1728,9 @@ SERVER_PROCESS::GetChildProcessHandles( } } - } while(dwRetries++ < 5 && - processList != NULL && - (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)); + } while (dwRetries++ < 5 && + processList != NULL && + (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)); if (dwError == ERROR_MORE_DATA) { @@ -1780,9 +1756,9 @@ SERVER_PROCESS::GetChildProcessHandles( { dwPid = (DWORD)processList->ProcessIdList[i]; if (dwPid != m_dwProcessId && - dwPid != dwWorkerProcessPid) + dwPid != dwWorkerProcessPid ) { - m_hChildProcessHandles[m_cChildProcess] = OpenProcess( + m_hChildProcessHandles[m_cChildProcess] = OpenProcess( PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, FALSE, dwPid @@ -1828,23 +1804,23 @@ SERVER_PROCESS::StopAllProcessesInJobObject( } processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc( - GetProcessHeap(), - 0, + GetProcessHeap(), + 0, cbNumBytes ); - if( processList == NULL ) + if (processList == NULL) { hr = E_OUTOFMEMORY; goto Finished; } - RtlZeroMemory( processList, cbNumBytes ); + RtlZeroMemory(processList, cbNumBytes); if (!QueryInformationJobObject( - m_hJobObject, - JobObjectBasicProcessIdList, - processList, - cbNumBytes, + m_hJobObject, + JobObjectBasicProcessIdList, + processList, + cbNumBytes, NULL)) { DWORD dwError = GetLastError(); @@ -1855,8 +1831,8 @@ SERVER_PROCESS::StopAllProcessesInJobObject( } } - } while (dwRetries++ < 5 && - processList != NULL && + } while (dwRetries++ < 5 && + processList != NULL && (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)); if (processList == NULL || (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0)) @@ -1916,12 +1892,13 @@ SERVER_PROCESS::SERVER_PROCESS() : m_hJobObject(NULL), m_pForwarderConnection(NULL), m_dwListeningProcessId(0), - m_hListeningProcessHandle(NULL) + m_hListeningProcessHandle(NULL), + m_hShutdownHandle(NULL) { InterlockedIncrement(&g_dwActiveServerProcesses); srand(GetTickCount()); - for(INT i=0;i 1) + { + // app path size is 1 means site root, i.e., "/" + // we don't want to add duplicated '/' to the request url + // otherwise the request will fail + strUrl.Copy(m_struAppPath); + } + strUrl.Append(L"/iisintegration"); + + hRequest = WinHttpOpenRequest(hConnect, + L"POST", + strUrl.QueryStr(), + NULL, + WINHTTP_NO_REFERER, + NULL, + 0); + + if (hRequest == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + // set timeout + if (!WinHttpSetTimeouts(hRequest, + m_dwShutdownTimeLimitInMS, // dwResolveTimeout + m_dwShutdownTimeLimitInMS, // dwConnectTimeout + m_dwShutdownTimeLimitInMS, // dwSendTimeout + m_dwShutdownTimeLimitInMS)) // dwReceiveTimeout + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + // set up the shutdown headers + if (FAILED(hr = strHeaders.Append(L"MS-ASPNETCORE-EVENT:shutdown \r\n")) || + FAILED(hr = strAppToken.Append(L"MS-ASPNETCORE-TOKEN:")) || + FAILED(hr = strAppToken.AppendA(m_straGuid.QueryStr())) || + FAILED(hr = strHeaders.Append(strAppToken.QueryStr()))) + { + goto Finished; + } + + if (!WinHttpSendRequest(hRequest, + strHeaders.QueryStr(), // pwszHeaders + strHeaders.QueryCCH(), // dwHeadersLength + WINHTTP_NO_REQUEST_DATA, + 0, // dwOptionalLength + 0, // dwTotalLength + 0)) // dwContext + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (!WinHttpReceiveResponse(hRequest , NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (!WinHttpQueryHeaders(hRequest, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &dwStatusCode, + &dwSize, + WINHTTP_NO_HEADER_INDEX)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (dwStatusCode != 202) + { + // not expected http status + hr = E_FAIL; + } + + // log + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST_MSG, + m_dwProcessId, + dwStatusCode))) + { + apsz[0] = strEventMsg.QueryStr(); + if (FORWARDING_HANDLER::QueryEventLog() != NULL) + { + ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + EVENTLOG_INFORMATION_TYPE, + 0, + ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST, + NULL, + 1, + 0, + apsz, + NULL); + } + } + +Finished: + if (hRequest) + { + WinHttpCloseHandle(hRequest); + hRequest = NULL; + } + if (hConnect) + { + WinHttpCloseHandle(hConnect); + hConnect = NULL; + } + if (hSession) + { + WinHttpCloseHandle(hSession); + hSession = NULL; + } + return hr; +} + +//static +VOID +SERVER_PROCESS::SendShutDownSignal( + LPVOID lpParam +) +{ + SERVER_PROCESS* pThis = static_cast(lpParam); + DBG_ASSERT(pThis); + pThis->SendShutDownSignalInternal(); +} + +// +// send shutdown message first, if fail then send +// ctrl-c to the backend process to let it gracefully shutdown +// +VOID +SERVER_PROCESS::SendShutDownSignalInternal( + VOID +) +{ + ReferenceServerProcess(); + + if (FAILED(SendShutdownHttpMessage())) + { + // + // failed to send shutdown http message + // try send ctrl signal + // + HWND hCurrentConsole = NULL; + BOOL fFreeConsole = FALSE; + hCurrentConsole = GetConsoleWindow(); + if (hCurrentConsole) + { + // free current console first, as we may have one, e.g., hostedwebcore case + fFreeConsole = FreeConsole(); + } + + if (AttachConsole(m_dwProcessId)) + { + // call ctrl-break instead of ctrl-c as child process may ignore ctrl-c + if (!GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, m_dwProcessId)) + { + // failed to send the ctrl signal. terminate the backend process immediately instead of waiting for timeout + TerminateBackendProcess(); + } + FreeConsole(); + } + + 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); + } + } + + DereferenceServerProcess(); +} + +VOID +SERVER_PROCESS::TerminateBackendProcess( + VOID +) +{ + LPCWSTR apsz[1]; + STACK_STRU(strEventMsg, 256); + + if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) + { + // backend process will be terminated, remove the waitcallback + if (m_hProcessWaitHandle != NULL) + { + UnregisterWait(m_hProcessWaitHandle); + m_hProcessWaitHandle = NULL; + } + + // cannot gracefully shutdown or timeout, terminate the process + if (m_hProcessHandle != NULL && m_hProcessHandle != INVALID_HANDLE_VALUE) + { + TerminateProcess(m_hProcessHandle, 0); + m_hProcessHandle = NULL; + } + + // as we skipped process exit callback (ProcessHandleCallback), + // need to dereference the object otherwise memory leak + DereferenceServerProcess(); + + // log a warning for ungraceful shutdown + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG, + m_dwProcessId))) + { + apsz[0] = strEventMsg.QueryStr(); + if (FORWARDING_HANDLER::QueryEventLog() != NULL) + { + ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + EVENTLOG_WARNING_TYPE, + 0, + ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE, + NULL, + 1, + 0, + apsz, + NULL); + } + } + } } \ No newline at end of file