Skip to content

Commit

Permalink
add support for job objects to clean up node processes when iis worke…
Browse files Browse the repository at this point in the history
…r process terminates
  • Loading branch information
tjanczuk committed Aug 16, 2011
1 parent a2bbb58 commit 784f538
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 4 deletions.
76 changes: 75 additions & 1 deletion src/iisnode/cnodeapplicationmanager.cpp
@@ -1,21 +1,79 @@
#include "precomp.h"

CNodeApplicationManager::CNodeApplicationManager(IHttpServer* server, HTTP_MODULE_ID moduleId)
: server(server), moduleId(moduleId), applications(NULL), asyncManager(NULL)
: server(server), moduleId(moduleId), applications(NULL), asyncManager(NULL), jobObject(NULL),
breakAwayFromJobObject(FALSE)
{
InitializeCriticalSection(&this->syncRoot);
}

HRESULT CNodeApplicationManager::Initialize()
{
HRESULT hr;
BOOL isInJob, createJob;
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo;

ErrorIf(NULL != this->asyncManager, ERROR_INVALID_OPERATION);
ErrorIf(NULL == (this->asyncManager = new CAsyncManager()), ERROR_NOT_ENOUGH_MEMORY);
CheckError(this->asyncManager->Initialize());

// determine whether node processes should be created in a new job object
// or whether current job object is adequate; the goal is to kill node processes when
// the IIS worker process is killed while preserving current job limits, if any

ErrorIf(!IsProcessInJob(GetCurrentProcess(), NULL, &isInJob), HRESULT_FROM_WIN32(GetLastError()));
if (!isInJob)
{
createJob = TRUE;
}
else
{
ErrorIf(!QueryInformationJobObject(NULL, JobObjectExtendedLimitInformation, &jobInfo, sizeof jobInfo, NULL),
HRESULT_FROM_WIN32(GetLastError()));

if (jobInfo.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK )
{
createJob = TRUE;
}
else if(jobInfo.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE )
{
createJob = FALSE;
}
else if(jobInfo.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_BREAKAWAY_OK )
{
createJob = TRUE;
this->breakAwayFromJobObject = TRUE;
}
else
{
createJob = TRUE;
}
}

if (createJob)
{
ErrorIf(NULL == (this->jobObject = CreateJobObject(NULL, NULL)), HRESULT_FROM_WIN32(GetLastError()));
RtlZeroMemory(&jobInfo, sizeof jobInfo);
jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
ErrorIf(!SetInformationJobObject(this->jobObject, JobObjectExtendedLimitInformation, &jobInfo, sizeof jobInfo),
HRESULT_FROM_WIN32(GetLastError()));
}

return S_OK;
Error:

if (NULL != this->asyncManager)
{
delete this->asyncManager;
this->asyncManager = NULL;
}

if (NULL != this->jobObject)
{
CloseHandle(this->jobObject);
this->jobObject = NULL;
}

return hr;
}

Expand All @@ -35,6 +93,12 @@ CNodeApplicationManager::~CNodeApplicationManager()
this->asyncManager = NULL;
}

if (NULL != this->jobObject)
{
CloseHandle(this->jobObject);
this->jobObject = NULL;
}

DeleteCriticalSection(&this->syncRoot);
}

Expand Down Expand Up @@ -138,4 +202,14 @@ CNodeApplication* CNodeApplicationManager::TryGetExistingNodeApplication(PCWSTR
}

return application;
}

HANDLE CNodeApplicationManager::GetJobObject()
{
return this->jobObject;
}

BOOL CNodeApplicationManager::GetBreakAwayFromJobObject()
{
return this->breakAwayFromJobObject;
}
4 changes: 4 additions & 0 deletions src/iisnode/cnodeapplicationmanager.h
Expand Up @@ -18,6 +18,8 @@ class CNodeApplicationManager
NodeApplicationEntry* applications;
CRITICAL_SECTION syncRoot;
CAsyncManager* asyncManager;
HANDLE jobObject;
BOOL breakAwayFromJobObject;

HRESULT GetOrCreateNodeApplication(IHttpContext* context, CNodeApplication** application);
HRESULT GetOrCreateNodeApplicationCore(PCWSTR physicalPath, CNodeApplication** application);
Expand All @@ -31,6 +33,8 @@ class CNodeApplicationManager
IHttpServer* GetHttpServer();
HTTP_MODULE_ID GetModuleId();
CAsyncManager* GetAsyncManager();
HANDLE GetJobObject();
BOOL GetBreakAwayFromJobObject();

HRESULT Initialize();
HRESULT Dispatch(IHttpContext* context, IHttpEventProvider* pProvider);
Expand Down
23 changes: 20 additions & 3 deletions src/iisnode/cnodeprocess.cpp
Expand Up @@ -32,6 +32,8 @@ HRESULT CNodeProcess::Initialize()
LPCH currentEnvironment = NULL;
LPCH newEnvironment = NULL;
DWORD environmentSize;
DWORD flags;
HANDLE job;

// generate the name for the named pipe to communicate with the node.js process

Expand Down Expand Up @@ -76,7 +78,13 @@ HRESULT CNodeProcess::Initialize()
startupInfo.dwFlags = 0; //STARTF_USESTDHANDLES;
startupInfo.hStdError = startupInfo.hStdInput = startupInfo.hStdOutput = INVALID_HANDLE_VALUE;

// create the node.js process
// create the node.exe process

flags = DETACHED_PROCESS | CREATE_SUSPENDED;
if (this->GetProcessManager()->GetApplication()->GetApplicationManager()->GetBreakAwayFromJobObject())
{
flags |= CREATE_BREAKAWAY_FROM_JOB;
}

RtlZeroMemory(&processInformation, sizeof processInformation);
ErrorIf(FALSE == CreateProcess(
Expand All @@ -85,17 +93,26 @@ HRESULT CNodeProcess::Initialize()
NULL,
NULL,
TRUE,
DETACHED_PROCESS | CREATE_SUSPENDED,
flags,
newEnvironment,
NULL,
&startupInfo,
&processInformation
), GetLastError());

ErrorIf((DWORD) -1 == ResumeThread(processInformation.hThread), GetLastError());
// join a job object if needed, then resume the process

job = this->GetProcessManager()->GetApplication()->GetApplicationManager()->GetJobObject();
if (NULL != job)
{
ErrorIf(!AssignProcessToJobObject(job, processInformation.hProcess), HRESULT_FROM_WIN32(GetLastError()));
}

ErrorIf((DWORD) -1 == ResumeThread(processInformation.hThread), GetLastError());
ErrorIf(GetExitCodeProcess(processInformation.hProcess, &exitCode) && STILL_ACTIVE != exitCode, exitCode);

// clean up

delete [] newEnvironment;
newEnvironment = NULL;
delete [] fullCommandLine;
Expand Down

0 comments on commit 784f538

Please sign in to comment.