diff --git a/src/config/iisnode_dev_x64.xml b/src/config/iisnode_dev_x64.xml
index 71f1d133..cab240cf 100644
--- a/src/config/iisnode_dev_x64.xml
+++ b/src/config/iisnode_dev_x64.xml
@@ -59,5 +59,6 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/
+
diff --git a/src/config/iisnode_dev_x86_on_x64.xml b/src/config/iisnode_dev_x86_on_x64.xml
index 0516dbdc..aaa0bbc9 100644
--- a/src/config/iisnode_dev_x86_on_x64.xml
+++ b/src/config/iisnode_dev_x86_on_x64.xml
@@ -59,5 +59,6 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/
+
diff --git a/src/config/iisnode_dev_x86_on_x86.xml b/src/config/iisnode_dev_x86_on_x86.xml
index 77804a6e..9606a25e 100644
--- a/src/config/iisnode_dev_x86_on_x86.xml
+++ b/src/config/iisnode_dev_x86_on_x86.xml
@@ -59,5 +59,6 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/
+
diff --git a/src/config/iisnode_express_schema.xml b/src/config/iisnode_express_schema.xml
index e6786b7b..2ad9eddc 100644
--- a/src/config/iisnode_express_schema.xml
+++ b/src/config/iisnode_express_schema.xml
@@ -59,5 +59,6 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/
+
diff --git a/src/config/iisnode_express_schema_x64.xml b/src/config/iisnode_express_schema_x64.xml
index 084df609..ddf6368a 100644
--- a/src/config/iisnode_express_schema_x64.xml
+++ b/src/config/iisnode_express_schema_x64.xml
@@ -59,5 +59,6 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/
+
diff --git a/src/config/iisnode_schema.xml b/src/config/iisnode_schema.xml
index 59f4e0d4..4088c009 100644
--- a/src/config/iisnode_schema.xml
+++ b/src/config/iisnode_schema.xml
@@ -59,5 +59,6 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/
+
diff --git a/src/config/iisnode_schema_x64.xml b/src/config/iisnode_schema_x64.xml
index 873ba749..8d454eae 100644
--- a/src/config/iisnode_schema_x64.xml
+++ b/src/config/iisnode_schema_x64.xml
@@ -59,5 +59,6 @@ Details at http://learn.iis.net/page.aspx/241/configuration-extensibility/
+
diff --git a/src/iisnode/cmoduleconfiguration.cpp b/src/iisnode/cmoduleconfiguration.cpp
index 5526cd92..c74d935e 100644
--- a/src/iisnode/cmoduleconfiguration.cpp
+++ b/src/iisnode/cmoduleconfiguration.cpp
@@ -32,7 +32,8 @@ CModuleConfiguration::CModuleConfiguration()
debuggerVirtualDirLength(0),
debuggerVirtualDirPhysicalPath(NULL),
recycleSignalEnabled(FALSE),
- debuggerExtensionDll(NULL)
+ debuggerExtensionDll(NULL),
+ idlePageOutTimePeriod(0)
{
InitializeSRWLock(&this->srwlock);
}
@@ -865,6 +866,10 @@ HRESULT CModuleConfiguration::ApplyConfigOverrideKeyValue(IHttpContext* context,
{
CheckError(GetString(valueStart, &config->interceptor, TRUE));
}
+ else if(0 == stricmp(keyStart, "idlePageOutTimePeriod"))
+ {
+ CheckError(GetDWORD(valueStart, &config->idlePageOutTimePeriod));
+ }
return S_OK;
Error:
@@ -1223,6 +1228,7 @@ HRESULT CModuleConfiguration::GetConfig(IHttpContext* context, CModuleConfigurat
CheckError(GetString(section, L"configOverrides", &c->configOverrides));
CheckError(GetString(section, L"nodeProcessCommandLine", &c->nodeProcessCommandLine));
CheckError(GetString(section, L"interceptor", &c->interceptor));
+ CheckError(GetDWORD(section, L"idlePageOutTimePeriod", &c->idlePageOutTimePeriod));
// debuggerPathSegment
@@ -1299,6 +1305,11 @@ LPWSTR CModuleConfiguration::GetNodeProcessCommandLine(IHttpContext* ctx)
GETCONFIG(nodeProcessCommandLine)
}
+DWORD CModuleConfiguration::GetIdlePageOutTimePeriod(IHttpContext* ctx)
+{
+ GETCONFIG(idlePageOutTimePeriod)
+}
+
LPWSTR CModuleConfiguration::GetInterceptor(IHttpContext* ctx)
{
GETCONFIG(interceptor)
diff --git a/src/iisnode/cmoduleconfiguration.h b/src/iisnode/cmoduleconfiguration.h
index 70ea22e7..c951fca3 100644
--- a/src/iisnode/cmoduleconfiguration.h
+++ b/src/iisnode/cmoduleconfiguration.h
@@ -28,6 +28,7 @@ class CModuleConfiguration : public IHttpStoredContext
DWORD maxLogFiles;
BOOL loggingEnabled;
BOOL debuggingEnabled;
+ DWORD idlePageOutTimePeriod;
PWSTR debuggerExtensionDll;
BOOL debugHeaderEnabled;
BOOL recycleSignalEnabled;
@@ -90,6 +91,7 @@ class CModuleConfiguration : public IHttpStoredContext
static HRESULT GetConfig(IHttpContext* context, CModuleConfiguration** config);
+ static DWORD GetIdlePageOutTimePeriod(IHttpContext* ctx);
static DWORD GetAsyncCompletionThreadCount(IHttpContext* ctx);
static DWORD GetNodeProcessCountPerApplication(IHttpContext* ctx);
static LPWSTR GetNodeProcessCommandLine(IHttpContext* ctx);
diff --git a/src/iisnode/cnodeapplication.cpp b/src/iisnode/cnodeapplication.cpp
index 5a4e079b..d5dfc573 100644
--- a/src/iisnode/cnodeapplication.cpp
+++ b/src/iisnode/cnodeapplication.cpp
@@ -1,10 +1,50 @@
#include "precomp.h"
+volatile DWORD CNodeApplication::dwInternalAppId = 0;
+
CNodeApplication::CNodeApplication(CNodeApplicationManager* applicationManager, BOOL isDebugger, NodeDebugCommand debugCommand, DWORD debugPort)
: applicationManager(applicationManager), scriptName(NULL), processManager(NULL),
isDebugger(isDebugger), peerApplication(NULL), debugCommand(debugCommand),
- debugPort(debugPort), needsRecycling(FALSE), configPath(NULL)
+ debugPort(debugPort), needsRecycling(FALSE), configPath(NULL), pIdleTimer(NULL),
+ fIdleCallbackInProgress(FALSE), fRequestsProcessedInLastIdleTimeoutPeriod(FALSE),
+ fEmptyWorkingSetAtStart(FALSE), dwIdlePageOutTimePeriod(0)
+{
+}
+
+VOID
+CALLBACK
+CNodeApplication::IdleTimerCallback(
+ IN PTP_CALLBACK_INSTANCE Instance,
+ IN PVOID Context,
+ IN PTP_TIMER Timer
+ )
{
+ CNodeApplication* pApplication = (CNodeApplication*) Context;
+ if(pApplication != NULL)
+ {
+ if(!pApplication->fIdleCallbackInProgress)
+ {
+ pApplication->fIdleCallbackInProgress = TRUE;
+
+ if(!pApplication->fRequestsProcessedInLastIdleTimeoutPeriod || !pApplication->fEmptyWorkingSetAtStart)
+ {
+ pApplication->fEmptyWorkingSetAtStart = TRUE;
+ pApplication->EmptyWorkingSet();
+ if( pApplication->fEmptyW3WPWorkingSet )
+ {
+ SetProcessWorkingSetSize(GetCurrentProcess(), -1, -1);
+ }
+ }
+
+ pApplication->fRequestsProcessedInLastIdleTimeoutPeriod = FALSE;
+
+ if(pApplication->pIdleTimer != NULL)
+ {
+ pApplication->pIdleTimer->SetTimer(pApplication->dwIdlePageOutTimePeriod, 0);
+ }
+ }
+ pApplication->fIdleCallbackInProgress = FALSE;
+ }
}
CNodeApplication::~CNodeApplication()
@@ -14,6 +54,13 @@ CNodeApplication::~CNodeApplication()
void CNodeApplication::Cleanup()
{
+ if(this->pIdleTimer != NULL)
+ {
+ this->pIdleTimer->CancelTimer();
+ delete this->pIdleTimer;
+ this->pIdleTimer = NULL;
+ }
+
this->GetApplicationManager()->GetFileWatcher()->RemoveWatch(this);
if (NULL != this->scriptName)
@@ -41,6 +88,26 @@ HRESULT CNodeApplication::Initialize(PCWSTR scriptName, IHttpContext* context)
CheckNull(scriptName);
+ if(CModuleConfiguration::GetIdlePageOutTimePeriod(context) > 0)
+ {
+ dwIdlePageOutTimePeriod = CModuleConfiguration::GetIdlePageOutTimePeriod(context);
+ if(dwInternalAppId == 0)
+ {
+ dwInternalAppId ++;
+ this->fEmptyW3WPWorkingSet = TRUE;
+ }
+ if(pIdleTimer == NULL)
+ {
+ pIdleTimer = new STTIMER;
+ ErrorIf(pIdleTimer == NULL, E_OUTOFMEMORY);
+ CheckError(pIdleTimer->InitializeTimer( CNodeApplication::IdleTimerCallback, this, 10000, 0));
+ }
+ else
+ {
+ pIdleTimer->SetTimer(10000, 0);
+ }
+ }
+
DWORD len = wcslen(scriptName) + 1;
ErrorIf(NULL == (this->scriptName = new WCHAR[len]), ERROR_NOT_ENOUGH_MEMORY);
wcscpy(this->scriptName, scriptName);
@@ -74,6 +141,11 @@ PCWSTR CNodeApplication::GetConfigPath()
return this->configPath;
}
+HRESULT CNodeApplication::EmptyWorkingSet()
+{
+ return this->processManager->EmptyWorkingSet();
+}
+
HRESULT CNodeApplication::SetConfigPath(IHttpContext * context)
{
HRESULT hr = S_OK;
@@ -109,6 +181,8 @@ HRESULT CNodeApplication::Dispatch(IHttpContext* context, IHttpEventProvider* pP
CheckNull(context);
CheckNull(pProvider);
+ fRequestsProcessedInLastIdleTimeoutPeriod = TRUE;
+
ErrorIf(NULL == (*ctx = new CNodeHttpStoredContext(this, this->GetApplicationManager()->GetEventProvider(), context)), ERROR_NOT_ENOUGH_MEMORY);
IHttpModuleContextContainer* moduleContextContainer = context->GetModuleContextContainer();
moduleContextContainer->SetModuleContext(*ctx, this->GetApplicationManager()->GetModuleId());
diff --git a/src/iisnode/cnodeapplication.h b/src/iisnode/cnodeapplication.h
index 8b3190bd..035fcaba 100644
--- a/src/iisnode/cnodeapplication.h
+++ b/src/iisnode/cnodeapplication.h
@@ -10,6 +10,7 @@ class CNodeApplication
{
private:
+ volatile static DWORD dwInternalAppId;
PWSTR scriptName;
PWSTR configPath;
CNodeApplicationManager* applicationManager;
@@ -19,6 +20,12 @@ class CNodeApplication
NodeDebugCommand debugCommand;
DWORD debugPort;
BOOL needsRecycling;
+ STTIMER *pIdleTimer;
+ BOOL fIdleCallbackInProgress;
+ BOOL fRequestsProcessedInLastIdleTimeoutPeriod;
+ BOOL fEmptyW3WPWorkingSet; // flag to indicate that this application is responsible for emptying w3wp working set (only one app should be responsible).
+ BOOL fEmptyWorkingSetAtStart;
+ volatile DWORD dwIdlePageOutTimePeriod;
void Cleanup();
@@ -27,6 +34,16 @@ class CNodeApplication
CNodeApplication(CNodeApplicationManager* applicationManager, BOOL isDebugger, NodeDebugCommand debugCommand, DWORD debugPort);
~CNodeApplication();
+ static
+ VOID
+ CALLBACK
+ IdleTimerCallback(
+ IN PTP_CALLBACK_INSTANCE Instance,
+ IN PVOID Context,
+ IN PTP_TIMER Timer
+ );
+
+ HRESULT EmptyWorkingSet();
HRESULT Initialize(PCWSTR scriptName, IHttpContext* context);
PCWSTR GetScriptName();
PCWSTR GetConfigPath();
diff --git a/src/iisnode/cnodeapplicationmanager.h b/src/iisnode/cnodeapplicationmanager.h
index a308d469..b99305d8 100644
--- a/src/iisnode/cnodeapplicationmanager.h
+++ b/src/iisnode/cnodeapplicationmanager.h
@@ -73,7 +73,6 @@ class CNodeApplicationManager
CNodeApplicationManager(IHttpServer* server, HTTP_MODULE_ID moduleId);
~CNodeApplicationManager();
-
IHttpServer* GetHttpServer();
HTTP_MODULE_ID GetModuleId();
CAsyncManager* GetAsyncManager();
diff --git a/src/iisnode/cnodehttpmodulefactory.h b/src/iisnode/cnodehttpmodulefactory.h
index 2a00554e..8fe84e2b 100644
--- a/src/iisnode/cnodehttpmodulefactory.h
+++ b/src/iisnode/cnodehttpmodulefactory.h
@@ -5,7 +5,6 @@ class CNodeHttpModuleFactory : public IHttpModuleFactory
{
CNodeApplicationManager* applicationManager;
-
public:
CNodeHttpModuleFactory();
diff --git a/src/iisnode/cnodeprocessmanager.cpp b/src/iisnode/cnodeprocessmanager.cpp
index 3bf18f14..cb6df5d6 100644
--- a/src/iisnode/cnodeprocessmanager.cpp
+++ b/src/iisnode/cnodeprocessmanager.cpp
@@ -51,6 +51,23 @@ CNodeApplication* CNodeProcessManager::GetApplication()
return this->application;
}
+HRESULT CNodeProcessManager::EmptyWorkingSet()
+{
+ ENTER_SRW_EXCLUSIVE(this->srwlock)
+
+ for (int i = 0; i < this->processCount; i++)
+ {
+ if(this->processes[i] != NULL && !this->processes[i]->HasProcessExited())
+ {
+ SetProcessWorkingSetSize(this->processes[i]->GetProcess(), -1, -1);
+ }
+ }
+
+ LEAVE_SRW_EXCLUSIVE(this->srwlock)
+
+ return S_OK;
+}
+
HRESULT CNodeProcessManager::Initialize(IHttpContext* context)
{
HRESULT hr;
diff --git a/src/iisnode/cnodeprocessmanager.h b/src/iisnode/cnodeprocessmanager.h
index e3c46cd0..0c41737e 100644
--- a/src/iisnode/cnodeprocessmanager.h
+++ b/src/iisnode/cnodeprocessmanager.h
@@ -40,6 +40,7 @@ class CNodeProcessManager
CNodeProcessManager(CNodeApplication* application, IHttpContext* context);
~CNodeProcessManager();
+ HRESULT EmptyWorkingSet();
CNodeApplication* GetApplication();
HRESULT Initialize(IHttpContext* context);
HRESULT RecycleProcess(CNodeProcess* process);
diff --git a/src/iisnode/cprotocolbridge.cpp b/src/iisnode/cprotocolbridge.cpp
index 9071a639..17d16533 100644
--- a/src/iisnode/cprotocolbridge.cpp
+++ b/src/iisnode/cprotocolbridge.cpp
@@ -719,7 +719,11 @@ void CProtocolBridge::SendHttpRequestHeaders(CNodeHttpStoredContext* context)
// to enable named pipe connection pooling
request = context->GetHttpContext()->GetRequest();
- CheckError(request->SetHeader(HttpHeaderConnection, "keep-alive", 10, TRUE));
+
+ if(stricmp(request->GetHeader(HttpHeaderConnection), "upgrade") != 0)
+ {
+ CheckError(request->SetHeader(HttpHeaderConnection, "keep-alive", 10, TRUE));
+ }
// Expect: 100-continue has been processed by IIS - do not propagate it up to node.js since node will
// attempt to process it again
diff --git a/src/iisnode/precomp.h b/src/iisnode/precomp.h
index 19f3e810..a2b6dd7b 100644
--- a/src/iisnode/precomp.h
+++ b/src/iisnode/precomp.h
@@ -25,6 +25,7 @@
// Project header files
#include "errors.h"
#include "utils.h"
+#include "sttimer.h"
#include "cconnectionpool.h"
#include "cnodeeventprovider.h"
#include "cnodeconstants.h"
diff --git a/src/iisnode/sttimer.h b/src/iisnode/sttimer.h
new file mode 100644
index 00000000..7e12d957
--- /dev/null
+++ b/src/iisnode/sttimer.h
@@ -0,0 +1,229 @@
+#ifndef _STTIMER_H
+#define _STTIMER_H
+
+class STTIMER
+{
+public:
+
+ STTIMER()
+ : _pTimer( NULL )
+ {
+ }
+
+ virtual
+ ~STTIMER()
+ {
+ if ( _pTimer )
+ {
+ CancelTimer();
+
+ CloseThreadpoolTimer( _pTimer );
+
+ _pTimer = NULL;
+ }
+ }
+
+ HRESULT
+ InitializeTimer(
+ PTP_TIMER_CALLBACK pfnCallback,
+ VOID * pContext,
+ DWORD dwInitialWait = 0,
+ DWORD dwPeriod = 0
+ )
+ {
+ _pTimer = CreateThreadpoolTimer( pfnCallback,
+ pContext,
+ NULL );
+
+ if ( !_pTimer )
+ {
+ return HRESULT_FROM_WIN32( GetLastError() );
+ }
+
+ if ( dwInitialWait )
+ {
+ SetTimer( dwInitialWait,
+ dwPeriod );
+ }
+
+ return S_OK;
+ }
+
+ VOID
+ SetTimer(
+ DWORD dwInitialWait,
+ DWORD dwPeriod = 0
+ )
+ {
+ FILETIME ftInitialWait;
+
+ if ( dwInitialWait == 0 && dwPeriod == 0 )
+ {
+ //
+ // Special case. We are preventing new callbacks
+ // from being queued. Any existing callbacks in the
+ // queue will still run.
+ //
+ // This effectively disables the timer. It can be
+ // re-enabled by setting non-zero initial wait or
+ // period values.
+ //
+
+ SetThreadpoolTimer( _pTimer, NULL, 0, 0 );
+ return;
+ }
+
+ InitializeRelativeFileTime( &ftInitialWait, dwInitialWait );
+
+ SetThreadpoolTimer( _pTimer,
+ &ftInitialWait,
+ dwPeriod,
+ 0 );
+ }
+
+ VOID
+ CancelTimer()
+ {
+ //
+ // Disable the timer
+ //
+
+ SetTimer( 0 );
+
+ //
+ // Wait until any callbacks queued prior to disabling
+ // have completed.
+ //
+
+ WaitForThreadpoolTimerCallbacks( _pTimer, TRUE );
+ }
+
+private:
+
+ VOID
+ InitializeRelativeFileTime(
+ FILETIME * pft,
+ DWORD dwMilliseconds
+ )
+ {
+ LARGE_INTEGER li;
+
+ //
+ // The pftDueTime parameter expects the time to be
+ // expressed as the number of 100 nanosecond intervals
+ // times -1.
+ //
+ // To convert from milliseconds, we'll multiply by
+ // -10000
+ //
+
+ li.QuadPart = (LONGLONG)dwMilliseconds * -10000;
+
+ pft->dwHighDateTime = li.HighPart;
+ pft->dwLowDateTime = li.LowPart;
+ };
+
+ TP_TIMER * _pTimer;
+};
+
+class STELAPSED
+{
+public:
+
+ STELAPSED()
+ : _dwInitTime( 0 ),
+ _dwInitTickCount( 0 ),
+ _dwPerfCountsPerMillisecond( 0 ),
+ _fUsingHighResolution( FALSE )
+ {
+ LARGE_INTEGER li;
+ BOOL fResult;
+
+ _dwInitTickCount = GetTickCount64();
+
+ fResult = QueryPerformanceFrequency( &li );
+
+ if ( !fResult )
+ {
+ goto Finished;
+ }
+
+ _dwPerfCountsPerMillisecond = li.QuadPart / 1000;
+
+ fResult = QueryPerformanceCounter( &li );
+
+ if ( !fResult )
+ {
+ goto Finished;
+ }
+
+ _dwInitTime = li.QuadPart / _dwPerfCountsPerMillisecond;
+
+ _fUsingHighResolution = TRUE;
+
+Finished:
+
+ return;
+ }
+
+ virtual
+ ~STELAPSED()
+ {
+ }
+
+ LONGLONG
+ QueryElapsedTime()
+ {
+ LARGE_INTEGER li;
+
+ if ( _fUsingHighResolution && QueryPerformanceCounter( &li ) )
+ {
+ DWORD64 dwCurrentTime = li.QuadPart / _dwPerfCountsPerMillisecond;
+
+ if ( dwCurrentTime < _dwInitTime )
+ {
+ //
+ // It's theoretically possible that QueryPerformanceCounter
+ // may return slightly different values on different CPUs.
+ // In this case, we don't want to return an unexpected value
+ // so we'll return zero. This is acceptable because
+ // presumably such a case would only happen for a very short
+ // time window.
+ //
+ // It would be possible to prevent this by ensuring processor
+ // affinity for all calls to QueryPerformanceCounter, but that
+ // would be undesirable in the general case because it could
+ // introduce unnecessary context switches and potentially a
+ // CPU bottleneck.
+ //
+ // Note that this issue also applies to callers doing rapid
+ // calls to this function. If a caller wants to mitigate
+ // that, they could enforce the affinitization, or they
+ // could implement a similar sanity check when comparing
+ // returned values from this function.
+ //
+
+ return 0;
+ }
+
+ return dwCurrentTime - _dwInitTime;
+ }
+
+ return GetTickCount64() - _dwInitTickCount;
+ }
+
+ BOOL
+ QueryUsingHighResolution()
+ {
+ return _fUsingHighResolution;
+ }
+
+private:
+
+ DWORD64 _dwInitTime;
+ DWORD64 _dwInitTickCount;
+ DWORD64 _dwPerfCountsPerMillisecond;
+ BOOL _fUsingHighResolution;
+};
+
+#endif // _STTIMER_H
diff --git a/src/version.txt b/src/version.txt
index ad8696b4..4bf518a9 100644
--- a/src/version.txt
+++ b/src/version.txt
@@ -1 +1 @@
-0.2.15
+0.2.16