Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/config/iisnode_dev_x64.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/
<attribute name="node_env" type="string" expanded="true" defaultValue="%node_env%"/>
<attribute name="asyncCompletionThreadCount" type="uint" defaultValue="0"/>
<attribute name="nodeProcessCountPerApplication" type="uint" defaultValue="1"/>
<attribute name="nodeProcessStickySessions" type="bool" defaultValue="false"/>
<attribute name="nodeProcessCommandLine" type="string" expanded="true" defaultValue="node.exe"/>
<attribute name="interceptor" type="string" expanded="true" defaultValue="&quot;%programfiles(x86)%\iisnode-dev\release\x64\interceptor.js&quot;" />
<attribute name="maxConcurrentRequestsPerProcess" type="uint" allowInfitnite="true" defaultValue="1024"/>
Expand Down
1 change: 1 addition & 0 deletions src/config/iisnode_dev_x86_on_x64.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/
<attribute name="node_env" type="string" expanded="true" defaultValue="%node_env%"/>
<attribute name="asyncCompletionThreadCount" type="uint" defaultValue="0"/>
<attribute name="nodeProcessCountPerApplication" type="uint" defaultValue="1"/>
<attribute name="nodeProcessStickySessions" type="bool" defaultValue="false"/>
<attribute name="nodeProcessCommandLine" type="string" expanded="true" defaultValue="node.exe"/>
<attribute name="interceptor" type="string" expanded="true" defaultValue="&quot;%programfiles(x86)%\iisnode-dev\release\x86\interceptor.js&quot;" />
<attribute name="maxConcurrentRequestsPerProcess" type="uint" allowInfitnite="true" defaultValue="1024"/>
Expand Down
1 change: 1 addition & 0 deletions src/config/iisnode_dev_x86_on_x86.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/
<attribute name="node_env" type="string" expanded="true" defaultValue="%node_env%"/>
<attribute name="asyncCompletionThreadCount" type="uint" defaultValue="0"/>
<attribute name="nodeProcessCountPerApplication" type="uint" defaultValue="1"/>
<attribute name="nodeProcessStickySessions" type="bool" defaultValue="false"/>
<attribute name="nodeProcessCommandLine" type="string" expanded="true" defaultValue="node.exe"/>
<attribute name="interceptor" type="string" expanded="true" defaultValue="&quot;%programfiles%\iisnode-dev\release\x86\interceptor.js&quot;" />
<attribute name="maxConcurrentRequestsPerProcess" type="uint" allowInfitnite="true" defaultValue="1024"/>
Expand Down
1 change: 1 addition & 0 deletions src/config/iisnode_express_schema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/
<attribute name="node_env" type="string" expanded="true" defaultValue="%node_env%"/>
<attribute name="asyncCompletionThreadCount" type="uint" defaultValue="0"/>
<attribute name="nodeProcessCountPerApplication" type="uint" defaultValue="1"/>
<attribute name="nodeProcessStickySessions" type="bool" defaultValue="false"/>
<attribute name="nodeProcessCommandLine" type="string" expanded="true" defaultValue="node.exe"/>
<attribute name="interceptor" type="string" expanded="true" defaultValue="&quot;%programfiles%\iisnode-express\interceptor.js&quot;" />
<attribute name="maxConcurrentRequestsPerProcess" type="uint" allowInfitnite="true" defaultValue="1024"/>
Expand Down
1 change: 1 addition & 0 deletions src/config/iisnode_express_schema_x64.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/
<attribute name="node_env" type="string" expanded="true" defaultValue="%node_env%"/>
<attribute name="asyncCompletionThreadCount" type="uint" defaultValue="0"/>
<attribute name="nodeProcessCountPerApplication" type="uint" defaultValue="1"/>
<attribute name="nodeProcessStickySessions" type="bool" defaultValue="false"/>
<attribute name="nodeProcessCommandLine" type="string" expanded="true" defaultValue="node.exe"/>
<attribute name="interceptor" type="string" expanded="true" defaultValue="&quot;%programfiles(x86)%\iisnode-express\interceptor.js&quot;" />
<attribute name="maxConcurrentRequestsPerProcess" type="uint" allowInfitnite="true" defaultValue="1024"/>
Expand Down
1 change: 1 addition & 0 deletions src/config/iisnode_schema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/
<attribute name="node_env" type="string" expanded="true" defaultValue="%node_env%"/>
<attribute name="asyncCompletionThreadCount" type="uint" defaultValue="0"/>
<attribute name="nodeProcessCountPerApplication" type="uint" defaultValue="1"/>
<attribute name="nodeProcessStickySessions" type="bool" defaultValue="false"/>
<attribute name="nodeProcessCommandLine" type="string" expanded="true" defaultValue="node.exe"/>
<attribute name="interceptor" type="string" expanded="true" defaultValue="&quot;%programfiles%\iisnode\interceptor.js&quot;" />
<attribute name="maxConcurrentRequestsPerProcess" type="uint" allowInfitnite="true" defaultValue="1024"/>
Expand Down
1 change: 1 addition & 0 deletions src/config/iisnode_schema_x64.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/
<attribute name="node_env" type="string" expanded="true" defaultValue="%node_env%"/>
<attribute name="asyncCompletionThreadCount" type="uint" defaultValue="0"/>
<attribute name="nodeProcessCountPerApplication" type="uint" defaultValue="1"/>
<attribute name="nodeProcessStickySessions" type="bool" defaultValue="false"/>
<attribute name="nodeProcessCommandLine" type="string" expanded="true" defaultValue="node.exe"/>
<attribute name="interceptor" type="string" expanded="true" defaultValue="&quot;%programfiles%\iisnode\interceptor.js&quot;" />
<attribute name="maxConcurrentRequestsPerProcess" type="uint" allowInfitnite="true" defaultValue="1024"/>
Expand Down
10 changes: 10 additions & 0 deletions src/iisnode/cmoduleconfiguration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,10 @@ HRESULT CModuleConfiguration::ApplyConfigOverrideKeyValue(IHttpContext* context,
{
CheckError(GetDWORD(valueStart, &config->maxConcurrentRequestsPerProcess));
}
else if (0 == strcmpi(keyStart, "nodeProcessStickySessions"))
{
CheckError(GetDWORD(valueStart, &config->nodeProcessStickySessions));
}
else if (0 == strcmpi(keyStart, "maxNamedPipeConnectionRetry"))
{
CheckError(GetDWORD(valueStart, &config->maxNamedPipeConnectionRetry));
Expand Down Expand Up @@ -1216,6 +1220,7 @@ HRESULT CModuleConfiguration::GetConfig(IHttpContext* context, CModuleConfigurat
CheckError(GetConfigSection(context, &section));
CheckError(GetDWORD(section, L"asyncCompletionThreadCount", &c->asyncCompletionThreadCount));
CheckError(GetDWORD(section, L"nodeProcessCountPerApplication", &c->nodeProcessCountPerApplication));
CheckError(GetDWORD(section, L"nodeProcessStickySessions", &c->nodeProcessStickySessions));
CheckError(GetDWORD(section, L"maxConcurrentRequestsPerProcess", &c->maxConcurrentRequestsPerProcess));
CheckError(GetDWORD(section, L"maxNamedPipeConnectionRetry", &c->maxNamedPipeConnectionRetry));
CheckError(GetDWORD(section, L"namedPipeConnectionRetryDelay", &c->namedPipeConnectionRetryDelay));
Expand Down Expand Up @@ -1318,6 +1323,11 @@ DWORD CModuleConfiguration::GetNodeProcessCountPerApplication(IHttpContext* ctx)
GETCONFIG(nodeProcessCountPerApplication)
}

DWORD CModuleConfiguration::GetProcessStickySessions(IHttpContext* ctx)
{
GETCONFIG(nodeProcessStickySessions)
}

LPWSTR CModuleConfiguration::GetNodeProcessCommandLine(IHttpContext* ctx)
{
GETCONFIG(nodeProcessCommandLine)
Expand Down
2 changes: 2 additions & 0 deletions src/iisnode/cmoduleconfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class CModuleConfiguration : public IHttpStoredContext

DWORD asyncCompletionThreadCount;
DWORD nodeProcessCountPerApplication;
DWORD nodeProcessStickySessions;
LPWSTR nodeProcessCommandLine;
LPWSTR interceptor;
DWORD maxConcurrentRequestsPerProcess;
Expand Down Expand Up @@ -94,6 +95,7 @@ class CModuleConfiguration : public IHttpStoredContext
static DWORD GetIdlePageOutTimePeriod(IHttpContext* ctx);
static DWORD GetAsyncCompletionThreadCount(IHttpContext* ctx);
static DWORD GetNodeProcessCountPerApplication(IHttpContext* ctx);
static DWORD GetProcessStickySessions(IHttpContext* ctx);
static LPWSTR GetNodeProcessCommandLine(IHttpContext* ctx);
static LPWSTR GetInterceptor(IHttpContext* ctx);
static DWORD GetMaxConcurrentRequestsPerProcess(IHttpContext* ctx);
Expand Down
89 changes: 78 additions & 11 deletions src/iisnode/cnodeprocessmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ CNodeProcessManager::CNodeProcessManager(CNodeApplication* application, IHttpCon
if (this->GetApplication()->IsDebugMode())
{
this->processCount = 1;
this->stickySessions = false;
}
else
{
this->processCount = CModuleConfiguration::GetNodeProcessCountPerApplication(context);
this->stickySessions = CModuleConfiguration::GetProcessStickySessions(context);
}

// cache event provider since the application can be disposed prior to CNodeProcessManager
Expand Down Expand Up @@ -92,7 +94,7 @@ HRESULT CNodeProcessManager::AddProcess(int ordinal, IHttpContext* context)
HRESULT hr;

ErrorIf(NULL != this->processes[ordinal], ERROR_INVALID_PARAMETER);
ErrorIf(NULL == (this->processes[ordinal] = new CNodeProcess(this, context)), ERROR_NOT_ENOUGH_MEMORY);
ErrorIf(NULL == (this->processes[ordinal] = new CNodeProcess(this, context)), ERROR_NOT_ENOUGH_MEMORY);
CheckError(this->processes[ordinal]->Initialize(context));

return S_OK;
Expand All @@ -107,10 +109,41 @@ HRESULT CNodeProcessManager::AddProcess(int ordinal, IHttpContext* context)
return hr;
}

int CNodeProcessManager::ExtractStickySessionsProcess( PCSTR pszCookie )
{
const char* pszKey = "iisnode.p";
const char* pszDivider = "=";
const char* pszNext = ";";
char acProcess[10]; // characters needed for MAXDWORD64
memset(acProcess, 0, sizeof(acProcess));

const char* pStart = strstr(pszCookie, pszKey);
const char* pEnd = NULL;

if(pStart)
{
pStart = strstr(pStart, pszDivider);
pEnd = strstr(pStart, pszNext);
if(!pEnd)
{
pEnd = pStart;
while (*pEnd) /* Works because end-of-string and FALSE are identical. */
{
pEnd++;
}
}
memcpy(acProcess, pStart, pEnd - pStart); // copy result
return atoi(acProcess);
}

return -1;
}

HRESULT CNodeProcessManager::Dispatch(CNodeHttpStoredContext* request)
{
HRESULT hr;
unsigned int tmpProcess, processToUse;
int processInCookie = -1;

CheckNull(request);

Expand All @@ -122,19 +155,48 @@ HRESULT CNodeProcessManager::Dispatch(CNodeHttpStoredContext* request)

if (!this->isClosing)
{
// employ a round robin routing logic to get a "ticket" to use a process with a specific ordinal number
if(this->stickySessions) // sticky sessions
{
IHttpRequest *httpRequest;
PCSTR pszCookieHeader = NULL;

httpRequest = request->GetHttpContext()->GetRequest();

pszCookieHeader = httpRequest->GetHeader(HttpHeaderCookie);
if(pszCookieHeader != NULL) // There might be a sticky session
{
processInCookie = ExtractStickySessionsProcess(pszCookieHeader);
}
}

if (1 == this->processCount)
if( processInCookie < 0) // employ a round robin routing logic to get a "ticket" to use a process with a specific ordinal number
{
processToUse = 0;
if (1 == this->processCount)
{
processToUse = 0;
}
else
{
do
{
tmpProcess = this->currentProcess;
processToUse = (tmpProcess + 1) % this->processCount;
} while (tmpProcess != InterlockedCompareExchange(&this->currentProcess, processToUse, tmpProcess));
}
}
else
{
do
{
tmpProcess = this->currentProcess;
processToUse = (tmpProcess + 1) % this->processCount;
} while (tmpProcess != InterlockedCompareExchange(&this->currentProcess, processToUse, tmpProcess));
processToUse = (unsigned int)processInCookie % this->processCount; // ensure the cookie did not carry a value outside of the possible processes
}

if(this->stickySessions && (processInCookie < 0))
{
// Set cookie for sticky session with selected process
char buffer [255];
int l = sprintf (buffer, "iisnode.p=%d", processToUse);
IHttpResponse *httpResponse;
httpResponse = request->GetHttpContext()->GetResponse();
CheckError(httpResponse->SetHeader(HttpHeaderSetCookie, buffer, l, FALSE));
}

// try dispatch to that process
Expand Down Expand Up @@ -287,7 +349,7 @@ HRESULT CNodeProcessManager::Recycle()

ENTER_SRW_EXCLUSIVE(this->srwlock)

this->isClosing = TRUE;
this->isClosing = TRUE;

// perform actual recycling on a diffrent thread to free up the file watcher thread

Expand Down Expand Up @@ -358,7 +420,7 @@ unsigned int CNodeProcessManager::GracefulShutdown(void* arg)
{
CloseHandle(drainHandles[i]);
}
delete[] drainHandles;
delete[] drainHandles;
drainHandles = NULL;

if (args->disposeApplication)
Expand Down Expand Up @@ -451,4 +513,9 @@ DWORD CNodeProcessManager::GetActiveRequestCount()
DWORD CNodeProcessManager::GetProcessCount()
{
return this->processCount;
}

DWORD CNodeProcessManager::GetProcessStickySessions()
{
return this->stickySessions;
}
3 changes: 3 additions & 0 deletions src/iisnode/cnodeprocessmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class CNodeProcessManager
CNodeApplication* application;
CNodeProcess** processes;
DWORD processCount;
BOOL stickySessions;
unsigned int currentProcess;
SRWLOCK srwlock;
DWORD gracefulShutdownTimeout;
Expand Down Expand Up @@ -51,6 +52,8 @@ class CNodeProcessManager
long DecRef();
DWORD GetActiveRequestCount();
DWORD GetProcessCount();
DWORD GetProcessStickySessions();
int ExtractStickySessionsProcess( PCSTR pszCookie );
};

#endif
5 changes: 3 additions & 2 deletions src/iisnode/cprotocolbridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -717,13 +717,14 @@ void CProtocolBridge::SendHttpRequestHeaders(CNodeHttpStoredContext* context)
memcpy(&activityId, context->GetActivityId(), sizeof GUID);

// request the named pipe to be kept alive by the server after the response is sent
// to enable named pipe connection pooling
// to enable named pipe connection pooling.
// but leave upgrade request untouched

request = context->GetHttpContext()->GetRequest();

pszConnectionHeader = request->GetHeader(HttpHeaderConnection);
if( pszConnectionHeader == NULL ||
(pszConnectionHeader != NULL && stricmp(pszConnectionHeader, "upgrade") != 0))
(pszConnectionHeader != NULL && strstr(pszConnectionHeader, "Upgrade") == 0))
{
CheckError(request->SetHeader(HttpHeaderConnection, "keep-alive", 10, TRUE));
}
Expand Down
Loading