Skip to content

Commit

Permalink
fix #122: named pipe connection pooling
Browse files Browse the repository at this point in the history
  • Loading branch information
tjanczuk committed Dec 17, 2011
1 parent a304bda commit 18dc354
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 35 deletions.
91 changes: 91 additions & 0 deletions src/iisnode/cconnectionpool.cpp
@@ -0,0 +1,91 @@
#include "precomp.h"

CConnectionPool::CConnectionPool()
: count(0), list(NULL)
{
}

HRESULT CConnectionPool::Initialize(IHttpContext* ctx)
{
HRESULT hr;

ErrorIf(NULL == (this->list = (PSLIST_HEADER)_aligned_malloc(sizeof(SLIST_HEADER), MEMORY_ALLOCATION_ALIGNMENT)), ERROR_NOT_ENOUGH_MEMORY);
this->maxPoolSize = 512; // TODO, expose configuration for this
this->maxAge = 30000; // TODO, expose configuration for this

InitializeSListHead(this->list);

return S_OK;
Error:
return hr;
}

CConnectionPool::~CConnectionPool()
{
PSLIST_ENTRY entry;

if (this->list)
{
while (NULL != (entry = InterlockedPopEntrySList(this->list)))
{
CloseHandle(((PCONNECTION_ENTRY)entry)->connection);
_aligned_free(entry);
}

_aligned_free(this->list);
this->list = NULL;
}
}

HRESULT CConnectionPool::Return(HANDLE connection)
{
HRESULT hr;
PCONNECTION_ENTRY entry = NULL;

if (this->count >= this->maxPoolSize)
{
CloseHandle(connection);
}
else
{
ErrorIf(NULL == (entry = (PCONNECTION_ENTRY)_aligned_malloc(sizeof(CONNECTION_ENTRY), MEMORY_ALLOCATION_ALIGNMENT)), ERROR_NOT_ENOUGH_MEMORY);
entry->connection = connection;
entry->timestamp = GetTickCount();
InterlockedPushEntrySList(this->list, &(entry->listEntry));
InterlockedIncrement(&this->count);
}

return S_OK;
Error:

return hr;
}

HANDLE CConnectionPool::Take()
{
HANDLE result = INVALID_HANDLE_VALUE;
PCONNECTION_ENTRY entry;
DWORD now = GetTickCount();

while (NULL != (entry = (PCONNECTION_ENTRY)InterlockedPopEntrySList(this->list)))
{
InterlockedDecrement(&this->count);
if ((now - entry->timestamp) < this->maxAge)
{
result = entry->connection;
}
else
{
CloseHandle(entry->connection);
}

_aligned_free(entry);

if (INVALID_HANDLE_VALUE != result)
{
break;
}
}

return result;
}
30 changes: 30 additions & 0 deletions src/iisnode/cconnectionpool.h
@@ -0,0 +1,30 @@
#ifndef __CCONNECTIONPOOL_H__
#define __CCONNECTIONPOOL_H__

class CConnectionPool
{
private:

typedef struct _CONNECTION_ENTRY {
SLIST_ENTRY listEntry;
HANDLE connection;
DWORD timestamp;
} CONNECTION_ENTRY, *PCONNECTION_ENTRY;

PSLIST_HEADER list;
unsigned long count;
DWORD maxPoolSize;
DWORD maxAge;

public:

CConnectionPool();
~CConnectionPool();

HRESULT Initialize(IHttpContext* ctx);
HRESULT Return(HANDLE connection);
HANDLE Take();

};

#endif
12 changes: 11 additions & 1 deletion src/iisnode/cnodehttpstoredcontext.cpp
Expand Up @@ -4,7 +4,7 @@ CNodeHttpStoredContext::CNodeHttpStoredContext(CNodeApplication* nodeApplication
: nodeApplication(nodeApplication), context(context), process(NULL), buffer(NULL), bufferSize(0), dataSize(0), parsingOffset(0),
chunkLength(0), chunkTransmitted(0), isChunked(FALSE), pipe(INVALID_HANDLE_VALUE), result(S_OK), isLastChunk(FALSE),
requestNotificationStatus(RQ_NOTIFICATION_PENDING), connectionRetryCount(0), pendingAsyncOperationCount(1),
targetUrl(NULL), targetUrlLength(0), childContext(NULL)
targetUrl(NULL), targetUrlLength(0), childContext(NULL), isConnectionFromPool(FALSE)
{
IHttpTraceContext* tctx;
LPCGUID pguid;
Expand Down Expand Up @@ -253,3 +253,13 @@ IHttpContext* CNodeHttpStoredContext::GetChildContext()
{
return this->childContext;
}

BOOL CNodeHttpStoredContext::GetIsConnectionFromPool()
{
return this->isConnectionFromPool;
}

void CNodeHttpStoredContext::SetIsConnectionFromPool(BOOL fromPool)
{
this->isConnectionFromPool = fromPool;
}
3 changes: 3 additions & 0 deletions src/iisnode/cnodehttpstoredcontext.h
Expand Up @@ -29,6 +29,7 @@ class CNodeHttpStoredContext : public IHttpStoredContext
DWORD targetUrlLength;
IHttpContext* childContext;
BOOL isLastChunk;
BOOL isConnectionFromPool;

public:

Expand Down Expand Up @@ -79,6 +80,8 @@ class CNodeHttpStoredContext : public IHttpStoredContext
LPOVERLAPPED InitializeOverlapped();
long IncreasePendingAsyncOperationCount();
long DecreasePendingAsyncOperationCount();
BOOL GetIsConnectionFromPool();
void SetIsConnectionFromPool(BOOL fromPool);

static CNodeHttpStoredContext* Get(LPOVERLAPPED overlapped);

Expand Down
11 changes: 10 additions & 1 deletion src/iisnode/cnodeprocess.cpp
Expand Up @@ -70,6 +70,10 @@ HRESULT CNodeProcess::Initialize(IHttpContext* context)
RtlZeroMemory(&processInformation, sizeof processInformation);
RtlZeroMemory(&startupInfo, sizeof startupInfo);

// initialize connection pool

CheckError(this->connectionPool.Initialize(context));

// configure logging

if (TRUE == (this->loggingEnabled = CModuleConfiguration::GetLoggingEnabled(context)))
Expand Down Expand Up @@ -563,4 +567,9 @@ HRESULT CNodeProcess::CreateStdHandles(IHttpContext* context)
}

return hr;
}
}

CConnectionPool* CNodeProcess::GetConnectionPool()
{
return &this->connectionPool;
}
2 changes: 2 additions & 0 deletions src/iisnode/cnodeprocess.h
Expand Up @@ -24,6 +24,7 @@ class CNodeProcess
BOOL hasProcessExited;
OVERLAPPED overlapped;
BOOL truncatePending;
CConnectionPool connectionPool;

static unsigned int WINAPI ProcessWatcher(void* arg);
void OnProcessExited();
Expand All @@ -39,6 +40,7 @@ class CNodeProcess
HRESULT Initialize(IHttpContext* context);
CNodeProcessManager* GetProcessManager();
LPCTSTR GetNamedPipeName();
CConnectionPool* GetConnectionPool();
HRESULT AcceptRequest(CNodeHttpStoredContext* context);
void OnRequestCompleted(CNodeHttpStoredContext* context);
void SignalWhenDrained(HANDLE handle);
Expand Down
102 changes: 69 additions & 33 deletions src/iisnode/cprotocolbridge.cpp
Expand Up @@ -333,25 +333,42 @@ void WINAPI CProtocolBridge::CreateNamedPipeConnection(DWORD error, DWORD bytesT
{
HRESULT hr;
CNodeHttpStoredContext* ctx = CNodeHttpStoredContext::Get(overlapped);
HANDLE pipe;

ErrorIf(INVALID_HANDLE_VALUE == (pipe = CreateFile(
ctx->GetNodeProcess()->GetNamedPipeName(),
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL)),
GetLastError());

ErrorIf(!SetFileCompletionNotificationModes(
pipe,
FILE_SKIP_COMPLETION_PORT_ON_SUCCESS | FILE_SKIP_SET_EVENT_ON_HANDLE),
GetLastError());

ctx->SetPipe(pipe);
ctx->GetNodeApplication()->GetApplicationManager()->GetAsyncManager()->AddAsyncCompletionHandle(pipe);
HANDLE pipe = INVALID_HANDLE_VALUE;
DWORD retry = ctx->GetConnectionRetryCount();

if (0 == retry)
{
// only the first connection attempt uses connections from the pool

pipe = ctx->GetNodeProcess()->GetConnectionPool()->Take();
}

if (INVALID_HANDLE_VALUE == pipe)
{
ErrorIf(INVALID_HANDLE_VALUE == (pipe = CreateFile(
ctx->GetNodeProcess()->GetNamedPipeName(),
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL)),
GetLastError());

ErrorIf(!SetFileCompletionNotificationModes(
pipe,
FILE_SKIP_COMPLETION_PORT_ON_SUCCESS | FILE_SKIP_SET_EVENT_ON_HANDLE),
GetLastError());

ctx->SetIsConnectionFromPool(FALSE);
ctx->GetNodeApplication()->GetApplicationManager()->GetAsyncManager()->AddAsyncCompletionHandle(pipe);
}
else
{
ctx->SetIsConnectionFromPool(TRUE);
}

ctx->SetPipe(pipe);

ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(
L"iisnode created named pipe connection to the node.exe process", WINEVENT_LEVEL_VERBOSE, ctx->GetActivityId());
Expand All @@ -364,7 +381,6 @@ void WINAPI CProtocolBridge::CreateNamedPipeConnection(DWORD error, DWORD bytesT

if (INVALID_HANDLE_VALUE == pipe)
{
DWORD retry = ctx->GetConnectionRetryCount();
if (retry >= CModuleConfiguration::GetMaxNamedPipeConnectionRetry(ctx->GetHttpContext()))
{
if (hr == ERROR_PIPE_BUSY)
Expand Down Expand Up @@ -412,13 +428,11 @@ void CProtocolBridge::SendHttpRequestHeaders(CNodeHttpStoredContext* context)
GUID activityId;
memcpy(&activityId, context->GetActivityId(), sizeof GUID);

// request the named pipe to be closed by the server after the response is sent
// since we are not reusing named pipe connections anyway, requesting the server
// to close it after sending the response allows the module to avoid parsing chunked responses
// to detect end of response
// request the named pipe to be kept alive by the server after the response is sent
// to enable named pipe connection pooling

request = context->GetHttpContext()->GetRequest();
CheckError(request->SetHeader(HttpHeaderConnection, "close", 5, TRUE));
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
Expand Down Expand Up @@ -464,11 +478,22 @@ void CProtocolBridge::SendHttpRequestHeaders(CNodeHttpStoredContext* context)
{
// error

etw->Log(L"iisnode failed to initiate sending http request headers to the node.exe process",
WINEVENT_LEVEL_ERROR,
&activityId);
if (context->GetIsConnectionFromPool())
{
// communication over a connection from the connection pool failed
// try to create a brand new connection instead

CProtocolBridge::SendEmptyResponse(context, 500, _T("Internal Server Error"), hr);
context->SetConnectionRetryCount(1);
CProtocolBridge::CreateNamedPipeConnection(S_OK, 0, context->GetOverlapped());
}
else
{
etw->Log(L"iisnode failed to initiate sending http request headers to the node.exe process",
WINEVENT_LEVEL_ERROR,
&activityId);

CProtocolBridge::SendEmptyResponse(context, 500, _T("Internal Server Error"), hr);
}
}
}

Expand Down Expand Up @@ -498,9 +523,20 @@ void WINAPI CProtocolBridge::SendHttpRequestHeadersCompleted(DWORD error, DWORD
return;
Error:

ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(
L"iisnode failed to send http request headers to the node.exe process", WINEVENT_LEVEL_ERROR, ctx->GetActivityId());
CProtocolBridge::SendEmptyResponse(ctx, 500, _T("Internal Server Error"), hr);
if (ctx->GetIsConnectionFromPool())
{
// communication over a connection from the connection pool failed
// try to create a brand new connection instead

ctx->SetConnectionRetryCount(1);
CProtocolBridge::CreateNamedPipeConnection(S_OK, 0, ctx->GetOverlapped());
}
else
{
ctx->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(
L"iisnode failed to send http request headers to the node.exe process", WINEVENT_LEVEL_ERROR, ctx->GetActivityId());
CProtocolBridge::SendEmptyResponse(ctx, 500, _T("Internal Server Error"), hr);
}

return;
}
Expand Down Expand Up @@ -1078,7 +1114,7 @@ void CProtocolBridge::FinalizeResponse(CNodeHttpStoredContext* context)
context->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(
L"iisnode finished processing http request/response", WINEVENT_LEVEL_VERBOSE, context->GetActivityId());

CloseHandle(context->GetPipe());
context->GetNodeProcess()->GetConnectionPool()->Return(context->GetPipe());
context->SetPipe(INVALID_HANDLE_VALUE);
CProtocolBridge::FinalizeResponseCore(
context,
Expand Down
2 changes: 2 additions & 0 deletions src/iisnode/iisnode.vcxproj
Expand Up @@ -202,6 +202,7 @@ copy /y $(ProjectDir)\..\config\* $(ProjectDir)\..\..\build\$(Configuration)\$(P
<ItemGroup>
<ClCompile Include="cactiverequestpool.cpp" />
<ClCompile Include="casyncmanager.cpp" />
<ClCompile Include="cconnectionpool.cpp" />
<ClCompile Include="cfilewatcher.cpp" />
<ClCompile Include="chttpprotocol.cpp" />
<ClCompile Include="cmoduleconfiguration.cpp" />
Expand Down Expand Up @@ -325,6 +326,7 @@ copy /y $(ProjectDir)\..\config\* $(ProjectDir)\..\..\build\$(Configuration)\$(P
<ItemGroup>
<ClInclude Include="cactiverequestpool.h" />
<ClInclude Include="casyncmanager.h" />
<ClInclude Include="cconnectionpool.h" />
<ClInclude Include="cfilewatcher.h" />
<ClInclude Include="chttpprotocol.h" />
<ClInclude Include="cmoduleconfiguration.h" />
Expand Down
6 changes: 6 additions & 0 deletions src/iisnode/iisnode.vcxproj.filters
Expand Up @@ -164,6 +164,9 @@
<ClCompile Include="cnodedebugger.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="cconnectionpool.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="precomp.h">
Expand Down Expand Up @@ -223,6 +226,9 @@
<ClInclude Include="errors.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="cconnectionpool.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="iisnode.def">
Expand Down
1 change: 1 addition & 0 deletions src/iisnode/precomp.h
Expand Up @@ -24,6 +24,7 @@
// Project header files
#include "errors.h"
#include "utils.h"
#include "cconnectionpool.h"
#include "cnodeeventprovider.h"
#include "casyncmanager.h"
#include "cmoduleconfiguration.h"
Expand Down

0 comments on commit 18dc354

Please sign in to comment.