aspnetcore/src/RequestHandler/inprocess/inprocessapplication.cpp

609 lines
19 KiB
C++

#include "..\precomp.hxx"
IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL;
IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(
IHttpServer* pHttpServer,
ASPNETCORE_CONFIG* pConfig) :
APPLICATION(pHttpServer, pConfig),
m_ProcessExitCode(0),
m_fManagedAppLoaded(FALSE),
m_fLoadManagedAppError(FALSE),
m_fInitialized(FALSE),
m_fRecycleProcessCalled(FALSE),
m_hLogFileHandle(INVALID_HANDLE_VALUE),
m_fDoneStdRedirect(FALSE)
{
// is it guaranteed that we have already checked app offline at this point?
// If so, I don't think there is much to do here.
DBG_ASSERT(pHttpServer != NULL);
DBG_ASSERT(pConfig != NULL);
InitializeSRWLock(&m_srwLock);
// TODO we can probably initialized as I believe we are the only ones calling recycle.
m_fInitialized = TRUE;
m_status = APPLICATION_STATUS::RUNNING;
}
IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION()
{
Recycle();
}
__override
VOID
IN_PROCESS_APPLICATION::ShutDown()
{
//todo
}
// This is the same function as before, TODO configrm if we need to change anything for configuration.
VOID
IN_PROCESS_APPLICATION::Recycle(
VOID
)
{
if (m_fInitialized)
{
DWORD dwThreadStatus = 0;
DWORD dwTimeout = m_pConfig->QueryShutdownTimeLimitInMS();
HANDLE handle = NULL;
WIN32_FIND_DATA fileData;
AcquireSRWLockExclusive(&m_srwLock);
if (!m_pHttpServer->IsCommandLineLaunch() &&
!m_fRecycleProcessCalled &&
(m_pHttpServer->GetAdminManager() != NULL))
{
// IIS scenario.
// notify IIS first so that new request will be routed to new worker process
m_pHttpServer->RecycleProcess(L"AspNetCore Recycle Process on Demand");
}
m_fRecycleProcessCalled = TRUE;
// First call into the managed server and shutdown
if (m_ShutdownHandler != NULL)
{
m_ShutdownHandler(m_ShutdownHandlerContext);
m_ShutdownHandler = NULL;
}
if (m_hThread != NULL &&
GetExitCodeThread(m_hThread, &dwThreadStatus) != 0 &&
dwThreadStatus == STILL_ACTIVE)
{
// wait for gracefullshut down, i.e., the exit of the background thread or timeout
if (WaitForSingleObject(m_hThread, dwTimeout) != WAIT_OBJECT_0)
{
// if the thread is still running, we need kill it first before exit to avoid AV
if (GetExitCodeThread(m_hThread, &dwThreadStatus) != 0 && dwThreadStatus == STILL_ACTIVE)
{
TerminateThread(m_hThread, STATUS_CONTROL_C_EXIT);
}
}
}
CloseHandle(m_hThread);
m_hThread = NULL;
s_Application = NULL;
ReleaseSRWLockExclusive(&m_srwLock);
if (m_pStdFile != NULL)
{
fflush(stdout);
fflush(stderr);
fclose(m_pStdFile);
}
if (m_hLogFileHandle != INVALID_HANDLE_VALUE)
{
m_Timer.CancelTimer();
CloseHandle(m_hLogFileHandle);
m_hLogFileHandle = INVALID_HANDLE_VALUE;
}
// delete empty log file, if logging is not enabled
handle = FindFirstFile(m_struLogFilePath.QueryStr(), &fileData);
if (handle != INVALID_HANDLE_VALUE &&
fileData.nFileSizeHigh == 0 &&
fileData.nFileSizeLow == 0) // skip check of nFileSizeHigh
{
FindClose(handle);
// no need to check whether the deletion succeeds
// as nothing can be done
DeleteFile(m_struLogFilePath.QueryStr());
}
if (m_pHttpServer && m_pHttpServer->IsCommandLineLaunch())
{
// IISExpress scenario
// Can only call exit to terminate current process
exit(0);
}
}
}
REQUEST_NOTIFICATION_STATUS
IN_PROCESS_APPLICATION::OnAsyncCompletion(
DWORD cbCompletion,
HRESULT hrCompletionStatus,
IN_PROCESS_HANDLER* pInProcessHandler
)
{
REQUEST_NOTIFICATION_STATUS dwRequestNotificationStatus = RQ_NOTIFICATION_CONTINUE;
if (pInProcessHandler->QueryIsManagedRequestComplete())
{
// means PostCompletion has been called and this is the associated callback.
dwRequestNotificationStatus = pInProcessHandler->QueryAsyncCompletionStatus();
// TODO cleanup whatever disconnect listener there is
return dwRequestNotificationStatus;
}
else
{
// Call the managed handler for async completion.
return m_AsyncCompletionHandler(pInProcessHandler->QueryManagedHttpContext(), hrCompletionStatus, cbCompletion);
}
}
REQUEST_NOTIFICATION_STATUS
IN_PROCESS_APPLICATION::OnExecuteRequest(
_In_ IHttpContext* pHttpContext,
_In_ IN_PROCESS_HANDLER* pInProcessHandler
)
{
if (m_RequestHandler != NULL)
{
return m_RequestHandler(pInProcessHandler, m_RequestHandlerContext);
}
//
// return error as the application did not register callback
//
if (ANCMEvents::ANCM_EXECUTE_REQUEST_FAIL::IsEnabled(pHttpContext->GetTraceContext()))
{
ANCMEvents::ANCM_EXECUTE_REQUEST_FAIL::RaiseEvent(pHttpContext->GetTraceContext(),
NULL,
(ULONG)E_APPLICATION_ACTIVATION_EXEC_FAILURE);
}
pHttpContext->GetResponse()->SetStatus(500,
"Internal Server Error",
0,
(ULONG)E_APPLICATION_ACTIVATION_EXEC_FAILURE);
return RQ_NOTIFICATION_FINISH_REQUEST;
}
VOID
IN_PROCESS_APPLICATION::SetCallbackHandles(
_In_ PFN_REQUEST_HANDLER request_handler,
_In_ PFN_SHUTDOWN_HANDLER shutdown_handler,
_In_ PFN_MANAGED_CONTEXT_HANDLER async_completion_handler,
_In_ VOID* pvRequstHandlerContext,
_In_ VOID* pvShutdownHandlerContext
)
{
m_RequestHandler = request_handler;
m_RequestHandlerContext = pvRequstHandlerContext;
m_ShutdownHandler = shutdown_handler;
m_ShutdownHandlerContext = pvShutdownHandlerContext;
m_AsyncCompletionHandler = async_completion_handler;
// Initialization complete
SetEvent(m_pInitalizeEvent);
}
VOID
IN_PROCESS_APPLICATION::SetStdOut(
VOID
)
{
HRESULT hr = S_OK;
BOOL fLocked = FALSE;
STRU struPath;
SYSTEMTIME systemTime;
SECURITY_ATTRIBUTES saAttr = { 0 };
if (!m_fDoneStdRedirect)
{
// Have not set stdout yet, redirect stdout to log file
AcquireSRWLockExclusive(&m_srwLock);
fLocked = TRUE;
if (!m_fDoneStdRedirect)
{
hr = UTILITY::ConvertPathToFullPath(
m_pConfig->QueryStdoutLogFile()->QueryStr(),
m_pConfig->QueryApplicationPhysicalPath()->QueryStr(),
&struPath);
if (FAILED(hr))
{
goto Finished;
}
hr = UTILITY::EnsureDirectoryPathExist(struPath.QueryStr());
if (FAILED(hr))
{
goto Finished;
}
GetSystemTime(&systemTime);
hr = m_struLogFilePath.SafeSnwprintf(L"%s_%d%02d%02d%02d%02d%02d_%d.log",
struPath.QueryStr(),
systemTime.wYear,
systemTime.wMonth,
systemTime.wDay,
systemTime.wHour,
systemTime.wMinute,
systemTime.wSecond,
GetCurrentProcessId());
if (FAILED(hr))
{
goto Finished;
}
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
m_hLogFileHandle = CreateFileW(m_struLogFilePath.QueryStr(),
FILE_WRITE_DATA,
FILE_SHARE_READ,
&saAttr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (m_hLogFileHandle == INVALID_HANDLE_VALUE)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
//
// best effort
// no need to capture the error code as nothing we can do here
// in case mamanged layer exits abnormally, may not be able to capture the log content as it is buffered.
//
if (!GetConsoleWindow())
{
//
// SetStdHandle works as w3wp does not have Console
// Current process does not have a console
//
SetStdHandle(STD_ERROR_HANDLE, m_hLogFileHandle);
if (m_pConfig->QueryStdoutLogEnabled())
{
SetStdHandle(STD_OUTPUT_HANDLE, m_hLogFileHandle);
// not work
// AllocConsole() does not help
// *stdout = *m_pStdFile;
// *stderr = *m_pStdFile;
// _dup2(_fileno(m_pStdFile), _fileno(stdout));
// _dup2(_fileno(m_pStdFile), _fileno(stderr));
// this one cannot capture the process start failure
// _wfreopen_s(&m_pStdFile, struLogFileName.QueryStr(), L"w", stdout);
// Periodically flush the log content to file
m_Timer.InitializeTimer(STTIMER::TimerCallback, &m_struLogFilePath, 3000, 3000);
}
}
else
{
// The process has console, e.g., IIS Express scenario
CloseHandle(m_hLogFileHandle);
m_hLogFileHandle = INVALID_HANDLE_VALUE;
if (m_pConfig->QueryStdoutLogEnabled())
{
if (_wfopen_s(&m_pStdFile, m_struLogFilePath.QueryStr(), L"w") == 0)
{
// known issue: error info may not be capture when process crashes during buffering
// even we disabled FILE buffering
setvbuf(m_pStdFile, NULL, _IONBF, 0);
_dup2(_fileno(m_pStdFile), _fileno(stdout));
_dup2(_fileno(m_pStdFile), _fileno(stderr));
}
// not work for console scenario
// close and AllocConsole does not help
//_wfreopen_s(&m_pStdFile, struLogFileName.QueryStr(), L"w", stdout);
// SetStdHandle(STD_ERROR_HANDLE, m_hLogFileHandle);
// SetStdHandle(STD_OUTPUT_HANDLE, m_hLogFileHandle);
//*stdout = *m_pStdFile;
//*stderr = *m_pStdFile;
}
else
{
// delete the file as log is disabled
WIN32_FIND_DATA fileData;
HANDLE handle = FindFirstFile(m_struLogFilePath.QueryStr(), &fileData);
if (handle != INVALID_HANDLE_VALUE &&
fileData.nFileSizeHigh == 0 &&
fileData.nFileSizeLow == 0)
{
FindClose(handle);
// no need to check whether the deletion succeeds
// as nothing can be done
DeleteFile(m_struLogFilePath.QueryStr());
}
}
}
}
}
Finished:
m_fDoneStdRedirect = TRUE;
if (fLocked)
{
ReleaseSRWLockExclusive(&m_srwLock);
}
if (FAILED(hr) && m_pConfig->QueryStdoutLogEnabled())
{
STRU strEventMsg;
if (SUCCEEDED(strEventMsg.SafeSnwprintf(
ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG,
m_struLogFilePath.QueryStr(),
hr)))
{
UTILITY::LogEvent(g_hEventLog,
EVENTLOG_WARNING_TYPE,
ASPNETCORE_EVENT_CONFIG_ERROR,
strEventMsg.QueryStr());
}
}
}
// Will be called by the inprocesshandler
HRESULT
IN_PROCESS_APPLICATION::LoadManagedApplication
(
VOID
)
{
HRESULT hr = S_OK;
DWORD dwTimeout;
DWORD dwResult;
BOOL fLocked = FALSE;
if (m_fManagedAppLoaded || m_fLoadManagedAppError)
{
// Core CLR has already been loaded.
// Cannot load more than once even there was a failure
if (m_fLoadManagedAppError)
{
hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE;
}
goto Finished;
}
// Set up stdout redirect
SetStdOut();
AcquireSRWLockExclusive(&m_srwLock);
fLocked = TRUE;
if (m_fManagedAppLoaded || m_fLoadManagedAppError)
{
if (m_fLoadManagedAppError)
{
hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE;
}
goto Finished;
}
m_hThread = CreateThread(
NULL, // default security attributes
0, // default stack size
(LPTHREAD_START_ROUTINE)ExecuteAspNetCoreProcess,
this, // thread function arguments
0, // default creation flags
NULL); // receive thread identifier
if (m_hThread == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
m_pInitalizeEvent = CreateEvent(
NULL, // default security attributes
TRUE, // manual reset event
FALSE, // not set
NULL); // name
if (m_pInitalizeEvent == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
// If the debugger is attached, never timeout
if (IsDebuggerPresent())
{
dwTimeout = INFINITE;
}
else
{
dwTimeout = m_pConfig->QueryStartupTimeLimitInMS();
}
const HANDLE pHandles[2]{ m_hThread, m_pInitalizeEvent };
// Wait on either the thread to complete or the event to be set
dwResult = WaitForMultipleObjects(2, pHandles, FALSE, dwTimeout);
// It all timed out
if (dwResult == WAIT_TIMEOUT)
{
// kill the backend thread as loading dotnet timedout
TerminateThread(m_hThread, 0);
hr = HRESULT_FROM_WIN32(dwResult);
goto Finished;
}
else if (dwResult == WAIT_FAILED)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
// The thread ended it means that something failed
if (dwResult == WAIT_OBJECT_0)
{
hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE;
goto Finished;
}
m_fManagedAppLoaded = TRUE;
Finished:
if (FAILED(hr))
{
STACK_STRU(strEventMsg, 256);
// Question: in case of application loading failure, should we allow retry on
// following request or block the activation at all
m_fLoadManagedAppError = TRUE; // m_hThread != NULL ?
if (SUCCEEDED(strEventMsg.SafeSnwprintf(
ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG,
m_pConfig->QueryApplicationPath()->QueryStr(),
m_pConfig->QueryApplicationPhysicalPath()->QueryStr(),
hr)))
{
UTILITY::LogEvent(g_hEventLog,
EVENTLOG_ERROR_TYPE,
ASPNETCORE_EVENT_LOAD_CLR_FALIURE,
strEventMsg.QueryStr());
}
}
if (fLocked)
{
ReleaseSRWLockExclusive(&m_srwLock);
}
return hr;
}
// static
VOID
IN_PROCESS_APPLICATION::ExecuteAspNetCoreProcess(
_In_ LPVOID pContext
)
{
HRESULT hr = S_OK;
IN_PROCESS_APPLICATION *pApplication = (IN_PROCESS_APPLICATION*)pContext;
DBG_ASSERT(pApplication != NULL);
hr = pApplication->ExecuteApplication();
//
// no need to log the error here as if error happened, the thread will exit
// the error will ba catched by caller LoadManagedApplication which will log an error
//
}
HRESULT
IN_PROCESS_APPLICATION::ExecuteApplication(
VOID
)
{
HRESULT hr = S_OK;
HMODULE hModule;
hostfxr_main_fn pProc;
// should be a redudant call here, but we will be safe and call it twice.
// TODO AV here on m_pHostFxrParameters being null
hModule = LoadLibraryW(m_pConfig->QueryHostFxrFullPath());
if (hModule == NULL)
{
// .NET Core not installed (we can log a more detailed error message here)
hr = ERROR_BAD_ENVIRONMENT;
goto Finished;
}
// Get the entry point for main
pProc = (hostfxr_main_fn)GetProcAddress(hModule, "hostfxr_main");
if (pProc == NULL)
{
hr = ERROR_BAD_ENVIRONMENT;
goto Finished;
}
// There can only ever be a single instance of .NET Core
// loaded in the process but we need to get config information to boot it up in the
// first place. This is happening in an execute request handler and everyone waits
// until this initialization is done.
// We set a static so that managed code can call back into this instance and
// set the callbacks
s_Application = this;
RunDotnetApplication(m_pConfig->QueryHostFxrArgCount(), m_pConfig->QueryHostFxrArguments(), pProc);
Finished:
//
// this method is called by the background thread and should never exit unless shutdown
//
if (!m_fRecycleProcessCalled)
{
STRU strEventMsg;
if (SUCCEEDED(strEventMsg.SafeSnwprintf(
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG,
m_pConfig->QueryApplicationPath()->QueryStr(),
m_pConfig->QueryApplicationPhysicalPath()->QueryStr(),
m_ProcessExitCode)))
{
UTILITY::LogEvent(g_hEventLog,
EVENTLOG_ERROR_TYPE,
ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT,
strEventMsg.QueryStr());
}
// error. the thread exits after application started
// Question: should we shutdown current worker process or keep the application in failure state?
// for now, we reccylce to keep the same behavior as that of out-of-process
if (m_fManagedAppLoaded)
{
Recycle();
}
}
return hr;
}
//
// Calls hostfxr_main with the hostfxr and application as arguments.
// Method should be called with only
// Need to have __try / __except in methods that require unwinding.
// Note, this will not
//
HRESULT
IN_PROCESS_APPLICATION::RunDotnetApplication(DWORD argc, CONST PCWSTR* argv, hostfxr_main_fn pProc)
{
HRESULT hr = S_OK;
__try
{
m_ProcessExitCode = pProc(argc, argv);
}
__except (FilterException(GetExceptionCode(), GetExceptionInformation()))
{
// TODO Log error message here.
hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE;
}
return hr;
}
// static
INT
IN_PROCESS_APPLICATION::FilterException(unsigned int, struct _EXCEPTION_POINTERS*)
{
// We assume that any exception is a failure as the applicaiton didn't start or there was a startup error.
// TODO, log error based on exception code.
return EXCEPTION_EXECUTE_HANDLER;
}