Skip to content

Commit 982c518

Browse files
committed
fix #129: simplify support for multiple node processes
1 parent cfefd0a commit 982c518

File tree

14 files changed

+140
-117
lines changed

14 files changed

+140
-117
lines changed

Diff for: src/config/iisnode_schema.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/
2929
<sectionSchema name="system.webServer/iisnode">
3030
<attribute name="node_env" type="string" expanded="true" defaultValue="%node_env%"/>
3131
<attribute name="asyncCompletionThreadCount" type="uint" defaultValue="0"/>
32-
<attribute name="maxProcessCountPerApplication" type="uint" defaultValue="4"/>
32+
<attribute name="nodeProcessCountPerApplication" type="uint" defaultValue="1"/>
3333
<attribute name="nodeProcessCommandLine" type="string" expanded="true" defaultValue="&quot;%programfiles%\nodejs\node.exe&quot;"/>
3434
<attribute name="maxConcurrentRequestsPerProcess" type="uint" allowInfitnite="true" defaultValue="1024"/>
3535
<attribute name="maxNamedPipeConnectionRetry" type="uint" defaultValue="3"/>

Diff for: src/config/iisnode_schema_x64.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/
2929
<sectionSchema name="system.webServer/iisnode">
3030
<attribute name="node_env" type="string" expanded="true" defaultValue="%node_env%"/>
3131
<attribute name="asyncCompletionThreadCount" type="uint" defaultValue="0"/>
32-
<attribute name="maxProcessCountPerApplication" type="uint" defaultValue="4"/>
32+
<attribute name="nodeProcessCountPerApplication" type="uint" defaultValue="1"/>
3333
<attribute name="nodeProcessCommandLine" type="string" expanded="true" defaultValue="&quot;%programfiles(x86)%\nodejs\node.exe&quot;"/>
3434
<attribute name="maxConcurrentRequestsPerProcess" type="uint" allowInfitnite="true" defaultValue="1024"/>
3535
<attribute name="maxNamedPipeConnectionRetry" type="uint" defaultValue="3"/>

Diff for: src/iisnode/cmoduleconfiguration.cpp

+13-3
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ HRESULT CModuleConfiguration::GetConfig(IHttpContext* context, CModuleConfigurat
434434

435435
CheckError(GetConfigSection(context, &section));
436436
CheckError(GetDWORD(section, L"asyncCompletionThreadCount", &c->asyncCompletionThreadCount));
437-
CheckError(GetDWORD(section, L"maxProcessCountPerApplication", &c->maxProcessCountPerApplication));
437+
CheckError(GetDWORD(section, L"nodeProcessCountPerApplication", &c->nodeProcessCountPerApplication));
438438
CheckError(GetDWORD(section, L"maxConcurrentRequestsPerProcess", &c->maxConcurrentRequestsPerProcess));
439439
CheckError(GetDWORD(section, L"maxNamedPipeConnectionRetry", &c->maxNamedPipeConnectionRetry));
440440
CheckError(GetDWORD(section, L"namedPipeConnectionRetryDelay", &c->namedPipeConnectionRetryDelay));
@@ -474,6 +474,16 @@ HRESULT CModuleConfiguration::GetConfig(IHttpContext* context, CModuleConfigurat
474474
GetSystemInfo(&info);
475475
c->asyncCompletionThreadCount = 0 == info.dwNumberOfProcessors ? 4 : info.dwNumberOfProcessors;
476476
}
477+
478+
if (0 == c->nodeProcessCountPerApplication)
479+
{
480+
// default number of node.exe processes to create per node.js application
481+
482+
SYSTEM_INFO info;
483+
484+
GetSystemInfo(&info);
485+
c->nodeProcessCountPerApplication = 0 == info.dwNumberOfProcessors ? 1 : info.dwNumberOfProcessors;
486+
}
477487

478488
// CR: check for ERROR_ALREADY_ASSIGNED to detect a race in creation of this section
479489
// CR: refcounting may be needed if synchronous code paths proove too long (race with config changes)
@@ -521,9 +531,9 @@ DWORD CModuleConfiguration::GetAsyncCompletionThreadCount(IHttpContext* ctx)
521531
GETCONFIG(asyncCompletionThreadCount)
522532
}
523533

524-
DWORD CModuleConfiguration::GetMaxProcessCountPerApplication(IHttpContext* ctx)
534+
DWORD CModuleConfiguration::GetNodeProcessCountPerApplication(IHttpContext* ctx)
525535
{
526-
GETCONFIG(maxProcessCountPerApplication)
536+
GETCONFIG(nodeProcessCountPerApplication)
527537
}
528538

529539
LPCTSTR CModuleConfiguration::GetNodeProcessCommandLine(IHttpContext* ctx)

Diff for: src/iisnode/cmoduleconfiguration.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class CModuleConfiguration : public IHttpStoredContext
88
private:
99

1010
DWORD asyncCompletionThreadCount;
11-
DWORD maxProcessCountPerApplication;
11+
DWORD nodeProcessCountPerApplication;
1212
LPTSTR nodeProcessCommandLine;
1313
DWORD maxConcurrentRequestsPerProcess;
1414
DWORD maxNamedPipeConnectionRetry;
@@ -48,7 +48,7 @@ class CModuleConfiguration : public IHttpStoredContext
4848
static HRESULT Initialize(IHttpServer* server, HTTP_MODULE_ID moduleId);
4949

5050
static DWORD GetAsyncCompletionThreadCount(IHttpContext* ctx);
51-
static DWORD GetMaxProcessCountPerApplication(IHttpContext* ctx);
51+
static DWORD GetNodeProcessCountPerApplication(IHttpContext* ctx);
5252
static LPCTSTR GetNodeProcessCommandLine(IHttpContext* ctx);
5353
static DWORD GetMaxConcurrentRequestsPerProcess(IHttpContext* ctx);
5454
static DWORD GetMaxNamedPipeConnectionRetry(IHttpContext* ctx);

Diff for: src/iisnode/cnodeprocessmanager.cpp

+79-90
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
#include "precomp.h"
22

33
CNodeProcessManager::CNodeProcessManager(CNodeApplication* application, IHttpContext* context)
4-
: application(application), processes(NULL), processCount(0), currentProcess(0), isClosing(FALSE),
4+
: application(application), processes(NULL), currentProcess(0), isClosing(FALSE),
55
refCount(1)
66
{
77
if (this->GetApplication()->IsDebugMode())
88
{
9-
this->maxProcessCount = 1;
9+
this->processCount = 1;
1010
}
1111
else
1212
{
13-
this->maxProcessCount = CModuleConfiguration::GetMaxProcessCountPerApplication(context);
13+
this->processCount = CModuleConfiguration::GetNodeProcessCountPerApplication(context);
1414
}
1515

1616
// cache event provider since the application can be disposed prior to CNodeProcessManager
@@ -22,11 +22,19 @@ CNodeProcessManager::CNodeProcessManager(CNodeApplication* application, IHttpCon
2222

2323
CNodeProcessManager::~CNodeProcessManager()
2424
{
25-
if (NULL != processes)
25+
this->Cleanup();
26+
}
27+
28+
void CNodeProcessManager::Cleanup()
29+
{
30+
if (NULL != this->processes)
2631
{
2732
for (int i = 0; i < this->processCount; i++)
2833
{
29-
delete this->processes[i];
34+
if (this->processes[i])
35+
{
36+
delete this->processes[i];
37+
}
3038
}
3139

3240
delete[] this->processes;
@@ -43,74 +51,45 @@ HRESULT CNodeProcessManager::Initialize(IHttpContext* context)
4351
{
4452
HRESULT hr;
4553

46-
ErrorIf(NULL == (this->processes = new CNodeProcess* [this->maxProcessCount]), ERROR_NOT_ENOUGH_MEMORY);
47-
RtlZeroMemory(this->processes, this->maxProcessCount * sizeof(CNodeProcess*));
48-
if (this->GetApplication()->IsDebuggee())
54+
ErrorIf(NULL == (this->processes = new CNodeProcess* [this->processCount]), ERROR_NOT_ENOUGH_MEMORY);
55+
RtlZeroMemory(this->processes, this->processCount * sizeof(CNodeProcess*));
56+
for (int i = 0; i < this->processCount; i++)
4957
{
50-
// ensure the debugee process is started without activating message
51-
// this is to make sure it is available for the debugger to connect to
52-
53-
CheckError(this->AddOneProcess(NULL, context));
58+
CheckError(this->AddProcess(i, context));
5459
}
5560

5661
return S_OK;
5762
Error:
5863

59-
if (NULL != this->processes)
60-
{
61-
delete [] this->processes;
62-
this->processes = NULL;
63-
}
64+
this->Cleanup();
6465

6566
return hr;
6667
}
6768

68-
HRESULT CNodeProcessManager::AddOneProcessCore(CNodeProcess** process, IHttpContext* context)
69+
HRESULT CNodeProcessManager::AddProcess(int ordinal, IHttpContext* context)
6970
{
7071
HRESULT hr;
7172

72-
ErrorIf(this->processCount == this->maxProcessCount, ERROR_NOT_ENOUGH_QUOTA);
73-
ErrorIf(NULL == (this->processes[this->processCount] = new CNodeProcess(this, context, this->processCount)), ERROR_NOT_ENOUGH_MEMORY);
74-
CheckError(this->processes[this->processCount]->Initialize(context));
75-
if (NULL != process)
76-
{
77-
*process = this->processes[this->processCount];
78-
}
79-
this->processCount++;
73+
ErrorIf(NULL != this->processes[ordinal], ERROR_INVALID_PARAMETER);
74+
ErrorIf(NULL == (this->processes[ordinal] = new CNodeProcess(this, context, ordinal)), ERROR_NOT_ENOUGH_MEMORY);
75+
CheckError(this->processes[ordinal]->Initialize(context));
8076

8177
return S_OK;
8278
Error:
8379

84-
if (NULL != this->processes[this->processCount])
80+
if (NULL != this->processes[ordinal])
8581
{
86-
delete this->processes[this->processCount];
87-
this->processes[this->processCount] = NULL;
82+
delete this->processes[ordinal];
83+
this->processes[ordinal] = NULL;
8884
}
8985

9086
return hr;
9187
}
9288

93-
HRESULT CNodeProcessManager::AddOneProcess(CNodeProcess** process, IHttpContext* context)
94-
{
95-
HRESULT hr;
96-
97-
if (NULL != process)
98-
{
99-
*process = NULL;
100-
}
101-
102-
ErrorIf(this->processCount == this->maxProcessCount, ERROR_NOT_ENOUGH_QUOTA);
103-
CheckError(this->AddOneProcessCore(process, context));
104-
105-
return S_OK;
106-
Error:
107-
108-
return hr;
109-
}
110-
11189
HRESULT CNodeProcessManager::Dispatch(CNodeHttpStoredContext* request)
11290
{
11391
HRESULT hr;
92+
unsigned int tmpProcess, processToUse;
11493

11594
CheckNull(request);
11695

@@ -120,24 +99,48 @@ HRESULT CNodeProcessManager::Dispatch(CNodeHttpStoredContext* request)
12099
{
121100
ENTER_SRW_SHARED(this->srwlock)
122101

123-
if (!this->isClosing && this->TryRouteRequestToExistingProcess(request))
102+
if (!this->isClosing)
124103
{
125-
request = NULL;
104+
// employ a round robin routing logic to get a "ticket" to use a process with a specific ordinal number
105+
106+
if (1 == this->processCount)
107+
{
108+
processToUse = 0;
109+
}
110+
else
111+
{
112+
do
113+
{
114+
tmpProcess = this->currentProcess;
115+
processToUse = (tmpProcess + 1) % this->processCount;
116+
} while (tmpProcess != InterlockedCompareExchange(&this->currentProcess, processToUse, tmpProcess));
117+
}
118+
119+
// try dispatch to that process
120+
121+
if (NULL != this->processes[processToUse])
122+
{
123+
CheckError(this->processes[processToUse]->AcceptRequest(request));
124+
request = NULL;
125+
}
126126
}
127127

128128
LEAVE_SRW_SHARED(this->srwlock)
129129

130-
if (request && !this->isClosing)
130+
if (NULL != request)
131131
{
132-
// existing processes were unable to accept this request; create a new process to handle it
132+
// the process to dispatch to does not exist and must be recreated
133133

134134
ENTER_SRW_EXCLUSIVE(this->srwlock)
135135

136-
if (!this->isClosing && !this->TryRouteRequestToExistingProcess(request))
136+
if (!this->isClosing)
137137
{
138-
CNodeProcess* newProcess = NULL;
139-
CheckError(this->AddOneProcess(&newProcess, request->GetHttpContext()));
140-
CheckError(newProcess->AcceptRequest(request));
138+
if (NULL == this->processes[processToUse])
139+
{
140+
CheckError(this->AddProcess(processToUse, request->GetHttpContext()));
141+
}
142+
143+
CheckError(this->processes[processToUse]->AcceptRequest(request));
141144
}
142145

143146
LEAVE_SRW_EXCLUSIVE(this->srwlock)
@@ -162,29 +165,6 @@ HRESULT CNodeProcessManager::Dispatch(CNodeHttpStoredContext* request)
162165
return hr;
163166
}
164167

165-
BOOL CNodeProcessManager::TryRouteRequestToExistingProcess(CNodeHttpStoredContext* context)
166-
{
167-
if (this->processCount == 0)
168-
{
169-
return false;
170-
}
171-
172-
DWORD i = this->currentProcess;
173-
174-
do {
175-
176-
if (S_OK == this->processes[i]->AcceptRequest(context))
177-
{
178-
return true;
179-
}
180-
181-
i = (i + 1) % this->processCount;
182-
183-
} while (i != this->currentProcess);
184-
185-
return false;
186-
}
187-
188168
HRESULT CNodeProcessManager::RecycleProcess(CNodeProcess* process)
189169
{
190170
HRESULT hr;
@@ -221,13 +201,7 @@ HRESULT CNodeProcessManager::RecycleProcess(CNodeProcess* process)
221201
return S_OK;
222202
}
223203

224-
if (i < (this->processCount - 1))
225-
{
226-
memcpy(this->processes + i, this->processes + i + 1, sizeof (CNodeProcess*) * (this->processCount - i - 1));
227-
}
228-
229-
this->processCount--;
230-
this->currentProcess = 0;
204+
this->processes[i] = NULL;
231205

232206
gracefulRecycle = TRUE;
233207
}
@@ -277,7 +251,17 @@ HRESULT CNodeProcessManager::Recycle()
277251

278252
this->isClosing = TRUE;
279253

280-
if (0 < this->processCount)
254+
BOOL hasActiveProcess = FALSE;
255+
for (int i = 0; i < this->processCount; i++)
256+
{
257+
if (this->processes[i])
258+
{
259+
hasActiveProcess = TRUE;
260+
break;
261+
}
262+
}
263+
264+
if (hasActiveProcess)
281265
{
282266
// perform actual recycling on a diffrent thread to free up the file watcher thread
283267

@@ -321,23 +305,28 @@ unsigned int CNodeProcessManager::GracefulShutdown(void* arg)
321305
ProcessRecycleArgs* args = (ProcessRecycleArgs*)arg;
322306
HRESULT hr;
323307
HANDLE* drainHandles = NULL;
308+
DWORD drainHandleCount = 0;
324309

325310
// drain active requests
326311

327312
ErrorIf(NULL == (drainHandles = new HANDLE[args->count]), ERROR_NOT_ENOUGH_MEMORY);
328313
RtlZeroMemory(drainHandles, args->count * sizeof HANDLE);
329314
for (int i = 0; i < args->count; i++)
330315
{
331-
drainHandles[i] = CreateEvent(NULL, TRUE, FALSE, NULL);
332-
args->processes[i]->SignalWhenDrained(drainHandles[i]);
316+
if (args->processes[i])
317+
{
318+
drainHandles[drainHandleCount] = CreateEvent(NULL, TRUE, FALSE, NULL);
319+
args->processes[i]->SignalWhenDrained(drainHandles[drainHandleCount]);
320+
drainHandleCount++;
321+
}
333322
}
334323

335324
if (args->processManager->gracefulShutdownTimeout > 0)
336325
{
337-
WaitForMultipleObjects(args->count, drainHandles, TRUE, args->processManager->gracefulShutdownTimeout);
326+
WaitForMultipleObjects(drainHandleCount, drainHandles, TRUE, args->processManager->gracefulShutdownTimeout);
338327
}
339328

340-
for (int i = 0; i < args->count; i++)
329+
for (int i = 0; i < drainHandleCount; i++)
341330
{
342331
CloseHandle(drainHandles[i]);
343332
}

Diff for: src/iisnode/cnodeprocessmanager.h

+3-5
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,16 @@ class CNodeProcessManager
2222
CNodeApplication* application;
2323
CNodeProcess** processes;
2424
DWORD processCount;
25-
DWORD maxProcessCount;
26-
DWORD currentProcess;
25+
unsigned int currentProcess;
2726
SRWLOCK srwlock;
2827
DWORD gracefulShutdownTimeout;
2928
BOOL isClosing;
3029
long refCount;
3130
CNodeEventProvider* eventProvider;
3231

33-
HRESULT AddOneProcessCore(CNodeProcess** process, IHttpContext* context);
34-
HRESULT AddOneProcess(CNodeProcess** process, IHttpContext* context);
35-
BOOL TryRouteRequestToExistingProcess(CNodeHttpStoredContext* context);
32+
HRESULT AddProcess(int ordinal, IHttpContext* context);
3633
static unsigned int WINAPI GracefulShutdown(void* arg);
34+
void Cleanup();
3735

3836
public:
3937

0 commit comments

Comments
 (0)