windows-nt/Source/XPSP1/NT/inetsrv/iis/svcs/infocomm/atq/atqbmon.cxx
2020-09-26 16:20:57 +08:00

1300 lines
23 KiB
C++

/*++
Copyright (c) 1998 Microsoft Corporation
Module Name:
atqbmon.cxx
Abstract:
ATQ Backlog Monitor
Author:
01-Dec-1998 MCourage
Revision History:
--*/
#include "isatq.hxx"
#include "atqbmon.hxx"
ATQ_BACKLOG_MONITOR * g_pAtqBacklogMonitor = NULL;
ATQ_BACKLOG_MONITOR::ATQ_BACKLOG_MONITOR(
VOID
)
/*++
Routine Description:
Constructor sets up the list and the lock.
Arguments:
None
--*/
{
INITIALIZE_CRITICAL_SECTION( &m_csLock );
InitializeListHead( &m_ListHead );
}
ATQ_BACKLOG_MONITOR::~ATQ_BACKLOG_MONITOR(
VOID
)
/*++
Routine Description:
Destructor cleans up the list and the lock.
Arguments:
None
--*/
{
LIST_ENTRY * pEntry;
ATQ_BMON_SET * pBmonSet;
//
// traverse the list and remove all sets
//
while (!IsListEmpty( &m_ListHead )) {
pEntry = RemoveHeadList( &m_ListHead );
pBmonSet = CONTAINING_RECORD( pEntry, ATQ_BMON_SET, m_SetList );
DBG_ASSERT( pBmonSet->IsEmpty() );
DBG_REQUIRE( pBmonSet->Cleanup() );
}
DeleteCriticalSection( &m_csLock );
}
BOOL
ATQ_BACKLOG_MONITOR::AddEntry(
ATQ_BMON_ENTRY * pBmonEntry
)
/*++
Routine Description:
Adds an entry to the next available ATQ_BMON_SET.
Makes a new set if all current sets are full.
Arguments:
pBmonEntry - The entry to be added
Return Values:
TRUE on success
--*/
{
LIST_ENTRY * pCurrent;
ATQ_BMON_SET * pBmonSet;
BOOL bRetval;
if (!pBmonEntry) {
return TRUE;
}
bRetval = FALSE;
Lock();
for (pCurrent = m_ListHead.Flink;
pCurrent != &m_ListHead;
pCurrent = pCurrent->Flink) {
pBmonSet = CONTAINING_RECORD( pCurrent, ATQ_BMON_SET, m_SetList );
if (pBmonSet->IsNotFull() && pBmonSet->AddEntry(pBmonEntry)) {
bRetval = TRUE;
break;
}
}
if (!bRetval) {
//
// Couldn't find a set with space
// so try to make a new one.
//
pBmonSet = new ATQ_BMON_SET;
if (pBmonSet && pBmonSet->Initialize()) {
InsertHeadList( &m_ListHead, &pBmonSet->m_SetList );
bRetval = pBmonSet->AddEntry(pBmonEntry);
}
}
Unlock();
return bRetval;
}
BOOL
ATQ_BACKLOG_MONITOR::RemoveEntry(
ATQ_BMON_ENTRY * pBmonEntry
)
/*++
Routine Description:
Removes an entry from its containing ATQ_BMON_SET.
If the set is empty it is removed from the list
of sets.
Arguments:
pBmonEntry - The entry to be removed
Return Values:
TRUE on success
--*/
{
ATQ_BMON_SET * pBmonSet;
if (!pBmonEntry) {
return TRUE;
}
DBG_ASSERT( pBmonEntry && pBmonEntry->CheckSignature() );
pBmonSet = pBmonEntry->GetContainingBmonSet();
DBG_ASSERT( pBmonSet );
return pBmonSet->RemoveEntry(pBmonEntry);
}
BOOL
ATQ_BACKLOG_MONITOR::PauseEntry(
ATQ_BMON_ENTRY * pBmonEntry
)
/*++
Routine Description:
Pauses an entry in its containing ATQ_BMON_SET.
The entry will remain in the set, but will
not send notifications.
Arguments:
pBmonEntry - The entry to be paused
Return Values:
TRUE on success
--*/
{
ATQ_BMON_SET * pBmonSet;
if (!pBmonEntry) {
return TRUE;
}
DBG_ASSERT( pBmonEntry && pBmonEntry->CheckSignature() );
pBmonSet = pBmonEntry->GetContainingBmonSet();
DBG_ASSERT( pBmonSet );
return pBmonSet->PauseEntry(pBmonEntry);
}
BOOL
ATQ_BACKLOG_MONITOR::ResumeEntry(
ATQ_BMON_ENTRY * pBmonEntry
)
/*++
Routine Description:
Undoes PauseEntry. The entry in question
will get notifications again
Arguments:
pBmonEntry - The entry to be resumed
Return Values:
TRUE on success
--*/
{
ATQ_BMON_SET * pBmonSet;
if (!pBmonEntry) {
return TRUE;
}
DBG_ASSERT( pBmonEntry && pBmonEntry->CheckSignature() );
pBmonSet = pBmonEntry->GetContainingBmonSet();
DBG_ASSERT( pBmonSet );
return pBmonSet->ResumeEntry(pBmonEntry);
}
ATQ_BMON_ENTRY::ATQ_BMON_ENTRY(
SOCKET s
)
/*++
Routine Description:
Constructor sets up signature etc.
Arguments:
None
--*/
{
DBG_ASSERT( s );
m_Signature = ATQ_BMON_ENTRY_SIGNATURE;
m_Socket = s;
m_hAddRemoveEvent = NULL;
m_BmonOpcode = BMON_INVALID;
m_pBmonSet = NULL;
m_dwErr = NO_ERROR;
}
ATQ_BMON_ENTRY::~ATQ_BMON_ENTRY(
VOID
)
/*++
Routine Description:
Destructor sets up signature and closes event.
Arguments:
None
--*/
{
DBG_ASSERT( CheckSignature() );
DBG_ASSERT( m_Socket );
m_Signature = ATQ_FREE_BMON_ENTRY_SIGNATURE;
DBG_REQUIRE( CloseHandle( m_hAddRemoveEvent ) );
}
BOOL
ATQ_BMON_ENTRY::InitEvent(
VOID
)
/*++
Routine Description:
Sets up the AddRemove event that we use
to synchronise adding and removing of entries.
Arguments:
None
Return Values:
TRUE on success
--*/
{
DBG_ASSERT( CheckSignature() );
DBG_ASSERT( m_hAddRemoveEvent == NULL );
m_hAddRemoveEvent = CreateEvent(
NULL, // default security
FALSE, // do auto-reset
FALSE, // init state false
NULL // no name
);
if (m_hAddRemoveEvent == NULL) {
m_dwErr = GetLastError();
}
return (m_hAddRemoveEvent != NULL);
}
VOID
ATQ_BMON_ENTRY::SignalAddRemove(
DWORD dwError
)
/*++
Routine Description:
Signals the AddRemove event. Clients adding or
removing the entry will be blocked on its event.
Arguments:
None
Return Values:
None
--*/
{
DBG_ASSERT( CheckSignature() );
DBG_ASSERT( m_hAddRemoveEvent != NULL );
m_dwErr = dwError;
DBG_REQUIRE( SetEvent(m_hAddRemoveEvent) );
}
BOOL
ATQ_BMON_ENTRY::WaitForAddRemove(
VOID
)
/*++
Routine Description:
Call this function while waiting for
the entry to be added to or removed from a set.
Arguments:
None
Return Values:
TRUE on success
--*/
{
DWORD dwResult;
DBG_ASSERT( CheckSignature() );
DBG_ASSERT( m_hAddRemoveEvent != NULL );
//
// If the monitor thread dies, don't bother waiting for notification from
// it regarding add/removal.
//
Retry:
if ( m_pBmonSet && !m_pBmonSet->ThreadFinished() )
{
dwResult = WaitForSingleObject(m_hAddRemoveEvent, 10000 );
if ( dwResult == WAIT_TIMEOUT )
{
goto Retry;
}
}
else
{
dwResult = WAIT_OBJECT_0;
}
DBG_ASSERT( dwResult == WAIT_OBJECT_0 );
return (dwResult == WAIT_OBJECT_0);
}
BOOL
BMON_WAKEUP_ENTRY::Callback(
VOID
)
/*++
Routine Description:
The sole purpose of the BMON_WAKEUP_ENTRY is to
get the main thread to wake up and call
SynchronizeSets. All we have to do here is
do a read so that we can wake up again in
the future.
Arguments:
None
Return Values:
TRUE if successful, else FALSE
--*/
{
INT err;
DWORD dwBuff;
ATQ_BMON_SET * pBmonSet;
DBG_ASSERT( CheckSignature() );
//
// do a read to clear the wakeup signal
//
err = recvfrom(
GetSocket(), // our socket
(PCHAR) &dwBuff, // read buffer
sizeof(dwBuff), // buffer len
0, // flags
NULL, // src addr
NULL // src addr len
);
if ( err == SOCKET_ERROR ) {
DBGPRINTF((DBG_CONTEXT,
"Error %d in recvfrom\n", WSAGetLastError()));
return FALSE;
} else {
DBG_ASSERT(dwBuff == ATQ_BMON_WAKEUP_MESSAGE);
return TRUE;
}
}
ATQ_BMON_SET::ATQ_BMON_SET(
VOID
)
/*++
Routine Description:
Constructor sets up critsec and sets.
Arguments:
None
--*/
{
INITIALIZE_CRITICAL_SECTION( &m_csLock );
memset(m_apEntrySet, 0, sizeof(m_apEntrySet));
FD_ZERO( &m_ListenSet );
m_SetSize = 0;
m_pWakeupEntry = NULL;
m_fCleanup = FALSE;
m_fDoSleep = FALSE;
m_fThreadFinished = FALSE;
m_dwError = NO_ERROR;
}
ATQ_BMON_SET::~ATQ_BMON_SET(
VOID
)
/*++
Routine Description:
Destructor cleans up critsec.
Arguments:
None
--*/
{
DBG_ASSERT( m_SetSize == 0 );
//
// get rid of the wakeup entry
//
DBG_REQUIRE( 0 == closesocket(m_pWakeupEntry->GetSocket()) );
delete m_pWakeupEntry;
DeleteCriticalSection(&m_csLock);
}
BOOL
ATQ_BMON_SET::Initialize(
VOID
)
/*++
Routine Description:
Sets up the Wakeup entry and starts
our thread.
Arguments:
None
Return Values:
TRUE on success
--*/
{
BOOL bRetval;
SOCKET s;
SOCKADDR_IN sockAddr;
INT err;
BMON_WAKEUP_ENTRY * pWakeup;
HANDLE hThread;
DWORD dwError = NO_ERROR;
WORD wPort = ATQ_BMON_WAKEUP_PORT;
bRetval = FALSE;
//
// set up wakeup entry
//
s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (s == INVALID_SOCKET) {
dwError = WSAGetLastError();
goto exit;
}
ZeroMemory(&sockAddr, sizeof(sockAddr));
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
sockAddr.sin_port = htons(wPort);
err = bind(s, (PSOCKADDR)&sockAddr, sizeof(sockAddr));
while ((err == SOCKET_ERROR)
&& (WSAGetLastError() == WSAEADDRINUSE)
&& (wPort < ATQ_BMON_WAKEUP_PORT_MAX)) {
//
// try another port
//
wPort++;
sockAddr.sin_port = htons(wPort);
err = bind(s, (PSOCKADDR)&sockAddr, sizeof(sockAddr));
}
if ( err == SOCKET_ERROR ) {
DBGERROR((DBG_CONTEXT,"Error %d in bind\n", WSAGetLastError()));
dwError = WSAGetLastError();
closesocket(s);
goto exit;
}
pWakeup = new BMON_WAKEUP_ENTRY(s);
if ( pWakeup == NULL )
{
dwError = ERROR_NOT_ENOUGH_MEMORY;
goto exit;
}
if (!pWakeup->InitEvent()) {
delete pWakeup;
pWakeup = NULL;
goto exit;
}
pWakeup->SetContainingBmonSet(this);
pWakeup->SetOpcode(BMON_WAIT);
m_pWakeupEntry = pWakeup;
m_Port = wPort;
//
// just jam this entry into the lists
//
DBG_ASSERT(m_SetSize == 0);
m_SetSize = 1;
m_apEntrySet[0] = pWakeup;
FD_SET(pWakeup->GetSocket(), &m_ListenSet);
//
// now set up our thread. from now on you
// have to Lock before manipulating the
// lists
//
m_hThread = CreateThread(
NULL, // defualt security
0, // default stack
::BmonThreadFunc, // thread func
this, // func parameter
0, // flags
NULL // discard tid
);
if (m_hThread) {
//
// it worked!
//
bRetval = TRUE;
} else {
//
// doh! clean up the wakeup entry
//
dwError = GetLastError();
closesocket( pWakeup->GetSocket() );
delete pWakeup;
m_SetSize = 0;
}
exit:
if (dwError != NO_ERROR) {
SetLastError(dwError);
}
return bRetval;
}
BOOL
ATQ_BMON_SET::Cleanup(
VOID
)
/*++
Routine Description:
Tells the select thread to clean up by
removing the wakeup entry.
Arguments:
None
Return Values:
TRUE on success
--*/
{
//
// Cleanup is done when wakup entry
// is removed.
//
// Don't use RemoveEntry, because the
// other thread will delete the wakeup
// entry.
//
DBG_ASSERT( m_pWakeupEntry && m_pWakeupEntry->CheckSignature() );
m_pWakeupEntry->SetOpcode(BMON_REMOVE);
Wakeup();
//
// Wait a reasonable amount of time for the thread to go away.
//
WaitForSingleObject( m_hThread, 10000 );
CloseHandle( m_hThread );
m_hThread = NULL;
delete this;
return TRUE;
}
BOOL
ATQ_BMON_SET::IsEmpty(
VOID
)
/*++
Routine Description:
This tells you if there are sockets in the
set. Note that one socket is the wakeup
socket, so a count of one means we're empty.
Arguments:
None
Return Values:
TRUE on empty
--*/
{
BOOL bRetval;
Lock();
bRetval = (m_SetSize <= 1);
DBG_ASSERT( m_SetSize <= FD_SETSIZE );
Unlock();
return bRetval;
}
BOOL
ATQ_BMON_SET::IsNotFull(
VOID
)
/*++
Routine Description:
This tells you if there is room for more sockets
in the set. Note that one socket is the wakeup
socket.
Arguments:
None
Return Values:
TRUE when space is available
--*/
{
BOOL bRetval;
Lock();
bRetval = (m_SetSize < FD_SETSIZE);
DBG_ASSERT( m_SetSize <= FD_SETSIZE );
Unlock();
return bRetval;
}
BOOL
ATQ_BMON_SET::AddEntry(
ATQ_BMON_ENTRY * pBmonEntry
)
/*++
Routine Description:
Add an entry to the set
Arguments:
pBmonEntry - the entry to be added
Return Values:
TRUE on success
--*/
{
DWORD i;
BOOL bAdded = FALSE;
DWORD dwError;
pBmonEntry->SetOpcode(BMON_ADD);
Lock();
for (i = 0; i < FD_SETSIZE; i++) {
if (!m_apEntrySet[i]) {
m_apEntrySet[i] = pBmonEntry;
pBmonEntry->SetContainingBmonSet(this);
bAdded = TRUE;
m_SetSize++;
DBG_ASSERT( m_SetSize <= FD_SETSIZE );
break;
}
}
Unlock();
if (bAdded) {
Wakeup();
pBmonEntry->WaitForAddRemove();
dwError = pBmonEntry->GetError();
if (dwError) {
//
// other thread will remove from list
//
SetLastError(dwError);
bAdded = FALSE;
}
}
return bAdded;
}
BOOL
ATQ_BMON_SET::RemoveEntry(
ATQ_BMON_ENTRY * pBmonEntry
)
/*++
Routine Description:
Remove an entry from the set
Arguments:
pBmonEntry - the entry to be removed
Return Values:
TRUE on success
--*/
{
DWORD i;
BOOL bRemoved = FALSE;
pBmonEntry->SetOpcode(BMON_REMOVE);
Wakeup();
pBmonEntry->WaitForAddRemove();
return (pBmonEntry->GetError() == NO_ERROR);
}
BOOL
ATQ_BMON_SET::PauseEntry(
ATQ_BMON_ENTRY * pBmonEntry
)
/*++
Routine Description:
Pause an entry in the set. The entry's
socket will be removed from the FD_SET,
but the entry will stay.
Arguments:
pBmonEntry - the entry to be paused
Return Values:
TRUE on success
--*/
{
DWORD i;
BOOL bRemoved = FALSE;
DBGPRINTF((DBG_CONTEXT,
"Pausing backlog monitor entry %p\n",
pBmonEntry));
pBmonEntry->SetOpcode(BMON_PAUSE);
Wakeup();
//
// We don't do the event stuff for pause and
// resume. It's a pain because the client will
// want to pause from within the callback
// function.
//
return (TRUE);
}
BOOL
ATQ_BMON_SET::ResumeEntry(
ATQ_BMON_ENTRY * pBmonEntry
)
/*++
Routine Description:
Resume an entry in the set. The entry's
socket will be added back to the FD_SET.
Arguments:
pBmonEntry - the entry to be resumed
Return Values:
TRUE on success
--*/
{
DWORD i;
BOOL bRemoved = FALSE;
if (pBmonEntry->GetOpcode() == BMON_NOWAIT) {
DBGPRINTF((DBG_CONTEXT,
"Resuming backlog monitor entry %p\n",
pBmonEntry));
pBmonEntry->SetOpcode(BMON_RESUME);
Wakeup();
}
//
// We don't do the event stuff for pause and
// resume. It's a pain because the client will
// want to pause from within the callback
// function.
//
return (TRUE);
}
VOID
ATQ_BMON_SET::BmonThreadFunc(
VOID
)
/*++
Routine Description:
This function is for the set's select thread.
It calls accept with the listen set, and calls
notification functions for all sockets that
are ready.
SynchronizeSets returns false when it's time
to shut down.
Arguments:
None
Return Values:
None
--*/
{
INT err;
while (SynchronizeSets()) {
err = select(
0, // junk
&m_ListenSet, // readfds
NULL, // writefds
NULL, // exceptfds
NULL // want to block
);
if (err != SOCKET_ERROR) {
DBG_ASSERT(err > 0);
if ( !NotifyEntries() )
{
//
// If we couldn't notify the entries, stop doing our thing
//
m_dwError = GetLastError();
m_dwError |= BMON_NOTIFY_ERROR;
break;
}
} else {
//
// Actually let's take the general approach that the moment the
// Backlog monitor sees trouble, it should stop. This avoids
// low memory situations where the backlog monitor just spins,
// thus becoming it's own denial of service attack.
//
m_dwError = WSAGetLastError();
m_dwError |= BMON_SELECT_ERROR;
break;
DBGPRINTF(( DBG_CONTEXT,
"Select failed with error %d\n",
WSAGetLastError()
));
}
if ( m_fDoSleep )
{
//
// Now sleep for a while. It will take time for unconnected to go away.
// We don't want to spin
//
// Of course, this means we don't do useful work on any other endpoints
// which may also be in trouble. Oh well.
//
Sleep( 5000 );
m_fDoSleep = FALSE;
}
}
m_fThreadFinished = TRUE;
}
VOID
ATQ_BMON_SET::Wakeup(
VOID
)
/*++
Routine Description:
We call this function when adding or removing
an entry. Writing to the wakeup socket wakes
up the select thread.
Arguments:
None
Return Values:
None
--*/
{
SOCKADDR_IN sockAddr;
INT err;
DWORD dwBuf;
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
sockAddr.sin_port = htons(m_Port);
dwBuf = ATQ_BMON_WAKEUP_MESSAGE;
err = sendto(
m_pWakeupEntry->GetSocket(),
(PCHAR)&dwBuf,
sizeof(dwBuf),
0,
(PSOCKADDR)&sockAddr,
sizeof(sockAddr)
);
if ( err == SOCKET_ERROR ) {
m_dwError = WSAGetLastError();
m_dwError |= BMON_SENDTO_ERROR;
DBGPRINTF((DBG_CONTEXT,
"Error %d in sendto\n",WSAGetLastError()));
}
}
BOOL
ATQ_BMON_SET::SynchronizeSets(
VOID
)
/*++
Routine Description:
This function updates our FD_SET to reflect
what's in the entry set, and also notifies
entries which have been added or removed.
We also check to see if it's time to shut
down (as indicated by the removal of the
wakeup entry.
Arguments:
None
Return Values:
TRUE to continue operating
FALSE on shutdown
--*/
{
DWORD i;
ATQ_BMON_ENTRY * pBmonEntry;
BOOL bRetval;
bRetval = TRUE;
Lock();
//
// clear out whatever is there now
//
FD_ZERO(&m_ListenSet);
//
// put in everything we want
//
for (i = 0; i < FD_SETSIZE; i++) {
pBmonEntry = m_apEntrySet[i];
if (pBmonEntry) {
DBG_ASSERT( pBmonEntry->CheckSignature() );
switch(pBmonEntry->GetOpcode()) {
case BMON_ADD:
FD_SET(pBmonEntry->GetSocket(), &m_ListenSet);
pBmonEntry->SetOpcode(BMON_WAIT);
pBmonEntry->SignalAddRemove(NO_ERROR);
break;
case BMON_RESUME:
FD_SET(pBmonEntry->GetSocket(), &m_ListenSet);
pBmonEntry->SetOpcode(BMON_WAIT);
break;
case BMON_PAUSE:
pBmonEntry->SetOpcode(BMON_NOWAIT);
pBmonEntry->SignalAddRemove(NO_ERROR);
break;
case BMON_REMOVE:
if (pBmonEntry == m_pWakeupEntry) {
//
// this means it's time to shut down
//
bRetval = FALSE;
}
m_apEntrySet[i] = NULL;
m_SetSize--;
pBmonEntry->SetContainingBmonSet(NULL);
pBmonEntry->SignalAddRemove(NO_ERROR);
break;
case BMON_WAIT:
FD_SET(pBmonEntry->GetSocket(), &m_ListenSet);
break;
case BMON_NOWAIT:
//
// this entry is paused, so don't do
// anything
//
break;
default:
//
// should never get here
// remove the bad entry
//
DBGPRINTF((DBG_CONTEXT,
"Invalid opcode in ATQ_BMON_ENTRY %p, %d\n",
pBmonEntry, pBmonEntry->GetOpcode()));
DBG_ASSERT(FALSE);
m_apEntrySet[i] = NULL;
break;
}
}
}
Unlock();
return bRetval;
}
BOOL
ATQ_BMON_SET::NotifyEntries(
VOID
)
/*++
Routine Description:
This function looks through the entries
to see who needs to be notified and calls
their callback function.
Arguments:
None
Return Values:
TRUE if successful, else FALSE
--*/
{
DWORD i;
ATQ_BMON_ENTRY * pBmonEntry;
BOOL fRet = TRUE;
Lock();
for (i = 0; i < FD_SETSIZE; i++) {
pBmonEntry = m_apEntrySet[i];
if (pBmonEntry) {
if (!pBmonEntry->CheckSignature()) {
DBGPRINTF(( DBG_CONTEXT,
"ATQ_BMON_ENTRY(%p)::CheckSignature() failed. index = %d\n",
pBmonEntry, i));
DBG_ASSERT( pBmonEntry->CheckSignature() );
}
if ((pBmonEntry->GetOpcode() == BMON_WAIT)
&& (FD_ISSET(pBmonEntry->GetSocket(), &m_ListenSet))) {
if ( !pBmonEntry->Callback() )
{
fRet = FALSE;
break;
}
}
}
}
Unlock();
return fRet;
}
DWORD WINAPI
BmonThreadFunc(
LPVOID lpBmonSet
)
/*++
Routine Description:
This function starts the select thread
of an ATQ_BMON_SET.
Arguments:
pBmonSet - the set to call
Return Values:
0
--*/
{
ATQ_BMON_SET * pBmonSet = (ATQ_BMON_SET *) lpBmonSet;
pBmonSet->BmonThreadFunc();
return 0;
}
//
// atqbmon.cxx
//