310 lines
9.2 KiB
C++
310 lines
9.2 KiB
C++
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved
|
|
|
|
#include "Service.h"
|
|
#include "assert.h"
|
|
|
|
CChannelManager::CChannelManager(
|
|
_In_ CFileRep* server,
|
|
_In_ long minIdleChannels,
|
|
_In_ long maxIdleChannels,
|
|
_In_ long maxTotalChannels)
|
|
{
|
|
this->minIdleChannels = minIdleChannels;
|
|
this->maxIdleChannels = maxIdleChannels;
|
|
this->maxTotalChannels = maxTotalChannels;
|
|
|
|
assert(this->maxIdleChannels >= minIdleChannels);
|
|
assert(this->minIdleChannels <= maxTotalChannels);
|
|
assert(NULL != server);
|
|
|
|
idleChannels = 0;
|
|
activeChannels = 0;
|
|
totalChannels = 0;
|
|
this->server = server;
|
|
|
|
stopEvent = NULL;
|
|
running = true;
|
|
#if (DBG || _DEBUG)
|
|
initialized = false;
|
|
#endif
|
|
}
|
|
|
|
HRESULT CChannelManager::Initialize()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
stopEvent = CreateEvent(NULL, true, false, NULL);
|
|
|
|
if (NULL == stopEvent)
|
|
{
|
|
server->PrintError(L"CChannelManager::Initialize", true);
|
|
server->PrintError(L"Initialization of event failed. Aborting service startup.", true);
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
#if (DBG || _DEBUG)
|
|
else
|
|
{
|
|
initialized = true;
|
|
}
|
|
#endif
|
|
|
|
return hr;
|
|
}
|
|
|
|
CChannelManager::~CChannelManager()
|
|
{
|
|
// At this point, no outstanding channels should be left.
|
|
assert(0 == idleChannels);
|
|
assert(0 == activeChannels);
|
|
assert(0 == totalChannels);
|
|
|
|
if (NULL != stopEvent)
|
|
{
|
|
CloseHandle(stopEvent);
|
|
stopEvent = NULL;
|
|
}
|
|
}
|
|
|
|
void CChannelManager::ChannelCreated()
|
|
{
|
|
InterlockedIncrement(&idleChannels);
|
|
InterlockedIncrement(&totalChannels);
|
|
}
|
|
|
|
// Channel is processing a request.
|
|
void CChannelManager::ChannelInUse()
|
|
{
|
|
InterlockedIncrement(&activeChannels);
|
|
|
|
assert(idleChannels > 0);
|
|
InterlockedDecrement(&idleChannels);
|
|
|
|
// See if we fell below the threshold for available channels.
|
|
// Ignore return value as the failure to create a new channel should not impact the existing channel.
|
|
(void) CreateChannels();
|
|
}
|
|
|
|
// Channel and associated data structures are freed.
|
|
void CChannelManager::ChannelFreed()
|
|
{
|
|
assert(idleChannels > 0);
|
|
InterlockedDecrement(&idleChannels);
|
|
|
|
assert(totalChannels > 0);
|
|
long totalChannelsChannelFreed = InterlockedDecrement(&totalChannels);
|
|
|
|
if (0 == totalChannelsChannelFreed)
|
|
{
|
|
// We only destroy superfluous channels so it should never hit 0
|
|
// unless we are shutting down.
|
|
assert(!IsRunning());
|
|
#if (DBG || _DEBUG)
|
|
BOOL eventReturn =
|
|
#endif
|
|
SetEvent(stopEvent);
|
|
assert(eventReturn);
|
|
}
|
|
}
|
|
|
|
// Channel is done processing a request and is ready to accept more work.
|
|
void CChannelManager::ChannelIdle()
|
|
{
|
|
assert(activeChannels > 0);
|
|
InterlockedDecrement(&activeChannels);
|
|
|
|
InterlockedIncrement(&idleChannels);
|
|
}
|
|
|
|
// Creates new channels if we are below the threshold for minumum available channels and if we are not at the channel cap.
|
|
HRESULT CChannelManager::CreateChannels()
|
|
{
|
|
CRequest* request = NULL;
|
|
HRESULT hr = S_OK;
|
|
server->PrintVerbose(L"Entering CChannelManager::CreateChannels");
|
|
|
|
if (idleChannels >= minIdleChannels || idleChannels + activeChannels >= maxTotalChannels)
|
|
{
|
|
server->PrintVerbose(L"Leaving CChannelManager::CreateChannels");
|
|
return S_OK;
|
|
}
|
|
|
|
long newChannels = minIdleChannels - idleChannels;
|
|
if (newChannels > maxTotalChannels - idleChannels - activeChannels)
|
|
{
|
|
newChannels = maxTotalChannels - idleChannels - activeChannels;
|
|
}
|
|
|
|
for (long i = 0; i < newChannels; i++)
|
|
{
|
|
// Even though our main request processing loop is asynchronous, there is enough
|
|
// synchronous work done (eg state creartion) to warrant farming this out to work items. Also,
|
|
// WsAsyncExecute can return synchronously if the request ends before the first asynchronous
|
|
// function is called and in that case we dont want to get stuck here by doing this synchronously.
|
|
|
|
// This is done here to prevent a race condition. If QueueUserWorkItem fails during startup the
|
|
// service will get torn down. But there could be other work items out there waiting to be executed.
|
|
// So to make sure the shutdown waits until those have been scheduled we increment the channel count here.
|
|
|
|
if (!IsRunning())
|
|
{
|
|
break;
|
|
}
|
|
|
|
// This object contains all request-specific state and the common message processing methods.
|
|
#pragma prefast(suppress: 28197)
|
|
request = new CRequest(server);
|
|
IfNullExit(request);
|
|
|
|
// Preallocate as much state as possible.
|
|
if (FAILED(request->Initialize()))
|
|
{
|
|
delete request;
|
|
EXIT_FUNCTION
|
|
}
|
|
|
|
ChannelCreated();
|
|
if (!::QueueUserWorkItem(CChannelManager::CreateChannelWorkItem, request, WT_EXECUTELONGFUNCTION))
|
|
{
|
|
// If this fails we are in bad shape, so don't try again.
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
server->PrintError(L"CChannelManager::CreateChannels", true);
|
|
server->PrintError(hr, NULL, true);
|
|
delete request;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
EXIT
|
|
|
|
server->PrintVerbose(L"Leaving CChannelManager::CreateChannels");
|
|
return hr;
|
|
}
|
|
|
|
ULONG WINAPI CChannelManager::CreateChannelWorkItem(
|
|
_In_ void* state)
|
|
{
|
|
assert(NULL != state);
|
|
|
|
CRequest* request = (CRequest*) state;
|
|
|
|
request->GetServer()->GetChannelManager()->CreateChannel(request);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// A channel goes through a series of states involving the channel manager.
|
|
// It starts its life here. We call into CFileRep::CreateOrResetChannel to create the actual channel state.
|
|
// After creation, the channel waits for an incoming request and once it gets one processes it.
|
|
// Once it is done, we call back into the channel manager which checks if the service is still running
|
|
// and if the channel is still needed. That happens in CChannelManager::RequestComplete
|
|
// If it is not needed, the channel and related data structures are freed.
|
|
// If it is still needed, the loop repeats as we call into CFileRep::CreateOrResetChannel again.
|
|
// Only this time it reuses the channel data structures instead of creating them.
|
|
void CChannelManager::CreateChannel(
|
|
_In_ CRequest* request)
|
|
{
|
|
PrintVerbose(L"Entering CChannelManager::CreateChannel");
|
|
|
|
WS_ERROR* error = request->GetError();
|
|
|
|
WS_ASYNC_CONTEXT asyncContext;
|
|
HRESULT hr = S_OK;
|
|
|
|
if (!IsRunning())
|
|
{
|
|
PrintVerbose(L"Leaving CChannelManager::CreateChannel");
|
|
return;
|
|
}
|
|
|
|
// We do nothing in the callback
|
|
asyncContext.callback = CleanupCallback;
|
|
asyncContext.callbackState = request;
|
|
|
|
// Start the message processing loop asynchronously.
|
|
// Use long callbacks since we are going to do significant work in there.
|
|
IfFailedExit(WsAsyncExecute(&request->asyncState, CRequest::AcceptChannelCallback, WS_LONG_CALLBACK,
|
|
request, &asyncContext, error));
|
|
|
|
// In the sync case, the cleanup callback is never called so we have to do it here.
|
|
// We only get here after we are done with the channel for good so it is safe to do this.
|
|
if (WS_S_ASYNC != hr)
|
|
{
|
|
delete request;
|
|
}
|
|
|
|
PrintVerbose(L"Leaving CChannelManager::CreateChannel");
|
|
return;
|
|
|
|
ERROR_EXIT
|
|
|
|
server->PrintError(L"CChannelManager::CreateChannel", true);
|
|
server->PrintError(hr, error, true);
|
|
if (NULL != request)
|
|
{
|
|
// Cleans up all the state associated with a request.
|
|
delete request;
|
|
}
|
|
|
|
PrintVerbose(L"Leaving CChannelManager::CreateChannel");
|
|
}
|
|
|
|
#pragma warning(disable : 4100) // The callbacks doesn't use all parameters.
|
|
|
|
// This is called at the end of an async execution chain. Here we can clean up.
|
|
void CALLBACK CChannelManager::CleanupCallback(
|
|
_In_ HRESULT hr,
|
|
_In_ WS_CALLBACK_MODEL callbackModel,
|
|
_In_ void* state)
|
|
{
|
|
assert(NULL != state);
|
|
|
|
CRequest* request = (CRequest*) state;
|
|
delete request;
|
|
}
|
|
|
|
#pragma warning(default : 4100)
|
|
|
|
// Sets state to stopped, which prevents new requests from being processed.
|
|
void CChannelManager::Stop()
|
|
{
|
|
server->PrintVerbose(L"Entering CChannelManager::Stop");
|
|
|
|
assert(initialized);
|
|
|
|
// This is a special case. As we never destroy all channels, there should
|
|
// only be zero channels before shutdown if the creation of all channels failed.
|
|
// In that case we stop here as the regular stop will not be hit.
|
|
if (0 == totalChannels)
|
|
{
|
|
#if (DBG || _DEBUG)
|
|
BOOL eventReturn =
|
|
#endif
|
|
SetEvent(stopEvent);
|
|
assert(eventReturn);
|
|
}
|
|
|
|
running = false;
|
|
|
|
server->PrintVerbose(L"Leaving CChannelManager::Stop");
|
|
}
|
|
|
|
// Wait for all outstanding requests to drain.
|
|
void CChannelManager::WaitForCleanup()
|
|
{
|
|
server->PrintVerbose(L"Entering CChannelManager::WaitForCleanup");
|
|
|
|
assert(!IsRunning());
|
|
assert(initialized);
|
|
|
|
WaitForSingleObject(stopEvent, INFINITE);
|
|
|
|
server->PrintVerbose(L"Leaving CChannelManager::WaitForCleanup");
|
|
}
|