2025-04-27 07:49:33 -04:00

1013 lines
23 KiB
C++

/***************************************************************************
*
* Copyright (C) 2001 Microsoft Corporation. All Rights Reserved.
*
* File: dp8simworkerthread.cpp
*
* Content: DP8SIM worker thread functions.
*
* History:
* Date By Reason
* ======== ======== =========
* 04/23/01 VanceO Created.
*
***************************************************************************/
#include "dp8simi.h"
//=============================================================================
// Globals
//=============================================================================
LONG g_lWorkerThreadRefCount = 0; // number of times worker thread has been started
DNCRITICAL_SECTION g_csJobQueueLock; // lock protecting the job queue
CBilink g_blJobQueue; // list of jobs to be performed
HANDLE g_hWorkerThreadJobEvent = NULL; // event to signal when worker thread has a new job
HANDLE g_hWorkerThread = NULL; // handle to worker thread
//=============================================================================
// Prototypes
//=============================================================================
DWORD DP8SimWorkerThreadProc(PVOID pvParameter);
#undef DPF_MODNAME
#define DPF_MODNAME "StartGlobalWorkerThread"
//=============================================================================
// StartGlobalWorkerThread
//-----------------------------------------------------------------------------
//
// Description: Starts the global worker thread if it hasn't already been
// started. Each successful call to this function must be
// balanced by a call to StopGlobalWorkerThread.
//
// Arguments: None.
//
// Returns: HRESULT
//=============================================================================
HRESULT StartGlobalWorkerThread(void)
{
HRESULT hr = DPN_OK;
DWORD dwThreadID;
BOOL fInittedCriticalSection = FALSE;
DPFX(DPFPREP, 5, "Enter");
DNEnterCriticalSection(&g_csGlobalsLock);
DNASSERT(g_lWorkerThreadRefCount >= 0);
if (g_lWorkerThreadRefCount == 0)
{
//
// This is the first worker thread user.
//
if (! DNInitializeCriticalSection(&g_csJobQueueLock))
{
DPFX(DPFPREP, 0, "Failed initializing job queue critical section!");
hr = DPNERR_GENERIC;
goto Failure;
}
//
// Don't allow critical section re-entry.
//
DebugSetCriticalSectionRecursionCount(&g_csJobQueueLock, 0);
fInittedCriticalSection = TRUE;
g_blJobQueue.Initialize();
//
// Create the new job notification event.
//
g_hWorkerThreadJobEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (g_hWorkerThreadJobEvent == NULL)
{
hr = GetLastError();
DPFX(DPFPREP, 0, "Failed creating worker thread job event!");
goto Failure;
}
//
// Create the thread.
//
g_hWorkerThread = CreateThread(NULL,
0,
DP8SimWorkerThreadProc,
NULL,
0,
&dwThreadID);
if (g_hWorkerThread == NULL)
{
hr = GetLastError();
DPFX(DPFPREP, 0, "Failed creating worker thread!");
goto Failure;
}
}
//
// Bump the refcount for this successful call.
//
g_lWorkerThreadRefCount++;
Exit:
DNLeaveCriticalSection(&g_csGlobalsLock);
DPFX(DPFPREP, 5, "Returning: [0x%lx]", hr);
return hr;
Failure:
if (g_hWorkerThreadJobEvent != NULL)
{
CloseHandle(g_hWorkerThreadJobEvent);
g_hWorkerThreadJobEvent = NULL;
}
if (fInittedCriticalSection)
{
DNDeleteCriticalSection(&g_csJobQueueLock);
fInittedCriticalSection = FALSE;
}
goto Exit;
} // StartGlobalWorkerThread
#undef DPF_MODNAME
#define DPF_MODNAME "StopGlobalWorkerThread"
//=============================================================================
// StopGlobalWorkerThread
//-----------------------------------------------------------------------------
//
// Description: Stop the global worker thread. This must balance a successful
// call to StartGlobalWorkerThread.
//
// Arguments: None.
//
// Returns: None.
//=============================================================================
void StopGlobalWorkerThread(void)
{
DPFX(DPFPREP, 5, "Enter");
DNEnterCriticalSection(&g_csGlobalsLock);
DNASSERT(g_lWorkerThreadRefCount > 0);
g_lWorkerThreadRefCount--;
if (g_lWorkerThreadRefCount == 0)
{
//
// Time to shut down the worker thread.
//
//
// The job queue had better be empty.
//
DNASSERT(g_blJobQueue.IsEmpty());
//
// Submit a quit job. Ignore error.
//
AddWorkerJob(0, DP8SIMJOBTYPE_QUIT, NULL, NULL, FALSE);
//
// Wait for the worker thread to close.
//
WaitForSingleObject(g_hWorkerThread, INFINITE);
//
// The job queue needs to be empty again.
//
DNASSERT(g_blJobQueue.IsEmpty());
//
// Close the thread handle.
//
CloseHandle(g_hWorkerThread);
g_hWorkerThread = NULL;
//
// Close the event handle.
//
CloseHandle(g_hWorkerThreadJobEvent);
g_hWorkerThreadJobEvent = NULL;
//
// Delete the critical section.
//
DNDeleteCriticalSection(&g_csJobQueueLock);
}
DNLeaveCriticalSection(&g_csGlobalsLock);
DPFX(DPFPREP, 5, "Leave");
} // StopGlobalWorkerThread
#undef DPF_MODNAME
#define DPF_MODNAME "AddWorkerJob"
//=============================================================================
// AddWorkerJob
//-----------------------------------------------------------------------------
//
// Description: Submits a new job of the given type to performed dwDelay
// milliseconds from now.
//
// If fDelayFromPreviousJob is TRUE, then if there is a job of
// the same type already queued, the new job is delayed further so
// that it occurs dwDelay milliseconds after the last similar job.
//
// Arguments:
// DWORD dwDelay - How long to wait before performing job, in
// milliseconds.
// DWORD dwJobType - ID indicating the type of type.
// PVOID pvContext - Context for the job.
// CDP8SimSP * pDP8SimSP - Pointer to interface submitting job, or NULL
// for none.
// BOOL fDelayFromPreviousJob - Whether to add the delay beyond the last job
// of the same type or not.
//
// Returns: HRESULT
//=============================================================================
HRESULT AddWorkerJob(const DWORD dwDelay,
const DWORD dwJobType,
PVOID const pvContext,
CDP8SimSP * const pDP8SimSP,
const BOOL fDelayFromPreviousJob)
{
HRESULT hr = DPN_OK;
DP8SIMJOB_FPMCONTEXT JobFPMContext;
CDP8SimJob * pDP8SimJob;
CBilink * pBilinkOriginalFirstItem;
CBilink * pBilink;
CDP8SimJob * pDP8SimTempJob;
DPFX(DPFPREP, 5, "Parameters: (%u, %u, 0x%p, 0x%p, %i)",
dwDelay, dwJobType, pvContext, pDP8SimSP, fDelayFromPreviousJob);
DNASSERT(g_hWorkerThreadJobEvent != NULL);
DNASSERT(g_hWorkerThread != NULL);
//
// Get a job object from the pool.
//
// Assume the job should execute after the appropriate delay. If
// fDelayFromPreviousJob is TRUE, we may need to adjust this.
//
ZeroMemory(&JobFPMContext, sizeof(JobFPMContext));
JobFPMContext.dwTime = timeGetTime() + dwDelay;
JobFPMContext.dwJobType = dwJobType;
JobFPMContext.pvContext = pvContext;
JobFPMContext.pDP8SimSP = pDP8SimSP;
pDP8SimJob = g_pFPOOLJob->Get(&JobFPMContext);
if (pDP8SimJob == NULL)
{
hr = E_OUTOFMEMORY;
goto Failure;
}
//
// Lock the job queue.
//
DNEnterCriticalSection(&g_csJobQueueLock);
//
// If fDelayFromPreviousJob is TRUE, look for a previous job of the same
// type. If found, we need to delay this new job from the end of the last
// one.
//
if (fDelayFromPreviousJob)
{
//
// Work backward through the list.
//
pBilink = g_blJobQueue.GetPrev();
while (pBilink != &g_blJobQueue)
{
pDP8SimTempJob = DP8SIMJOB_FROM_BILINK(pBilink);
DNASSERT(pDP8SimTempJob->IsValidObject());
if (dwJobType == pDP8SimTempJob->GetJobType())
{
//
// We found a similar job. Update the new job's time so that
// the delay is added on top of the previous job.
//
DPFX(DPFPREP, 9, "Found similar job 0x%p (job time = %u, type = %u, context 0x%p, interface = 0x%p).",
pDP8SimTempJob, pDP8SimTempJob->GetTime(), dwJobType, pDP8SimTempJob->GetContext(), pDP8SimSP);
pDP8SimJob->SetNewTime(pDP8SimTempJob->GetTime() + dwDelay);
break;
}
pBilink = pBilink->GetPrev();
}
}
DPFX(DPFPREP, 8, "Adding new job 0x%p (job time = %u, type = %u, context 0x%p, interface = 0x%p).",
pDP8SimJob, pDP8SimJob->GetTime(), dwJobType, pDP8SimJob->GetContext(), pDP8SimSP);
//
// Remember the current first item.
//
pBilinkOriginalFirstItem = g_blJobQueue.GetNext();
//
// Find the first job that needs to be fired after this one and insert the
// new job before it.
//
pBilink = pBilinkOriginalFirstItem;
while (pBilink != &g_blJobQueue)
{
pDP8SimTempJob = DP8SIMJOB_FROM_BILINK(pBilink);
DNASSERT(pDP8SimTempJob->IsValidObject());
if ((int) (pDP8SimJob->GetTime() - pDP8SimTempJob->GetTime()) < 0)
{
//
// Stop looping.
//
break;
}
pBilink = pBilink->GetNext();
}
//
// If we didn't find a place to insert the job, pBilink will point to the
// end/beginning of the list.
//
pDP8SimJob->m_blList.InsertBefore(pBilink);
//
// If the front of the queue changed, alert the worker thread.
//
if (g_blJobQueue.GetNext() != pBilinkOriginalFirstItem)
{
DPFX(DPFPREP, 9, "Front of job queue changed, alerting worker thread.");
//
// Ignore error, there's nothing we can do about it.
//
SetEvent(g_hWorkerThreadJobEvent);
}
else
{
DPFX(DPFPREP, 9, "Front of job queue did not change.");
}
DNLeaveCriticalSection(&g_csJobQueueLock);
Exit:
DPFX(DPFPREP, 5, "Returning: [0x%lx]", hr);
return hr;
Failure:
goto Exit;
} // AddWorkerJob
#undef DPF_MODNAME
#define DPF_MODNAME "FlushAllDelayedSendsToEndpoint"
//=============================================================================
// FlushAllDelayedSendsToEndpoint
//-----------------------------------------------------------------------------
//
// Description: Removes all delayed sends intended for the given endpoint that
// are still queued. If fDrop is TRUE, the messages are dropped.
// If fDrop is FALSE, they are all submitted to the real SP.
//
// Arguments:
// CDP8SimEndpoint * pDP8SimEndpoint - Endpoint whose sends are to be
// removed.
// BOOL fDrop - Whether to drop the sends or not.
//
// Returns: None.
//=============================================================================
void FlushAllDelayedSendsToEndpoint(CDP8SimEndpoint * const pDP8SimEndpoint,
BOOL fDrop)
{
CBilink blDelayedSendJobs;
CBilink * pBilinkOriginalFirstItem;
CBilink * pBilink;
CDP8SimJob * pDP8SimJob;
CDP8SimSend * pDP8SimSend;
CDP8SimSP * pDP8SimSP;
DPFX(DPFPREP, 5, "Parameters: (0x%p, %i)", pDP8SimEndpoint, fDrop);
DNASSERT(pDP8SimEndpoint->IsValidObject());
blDelayedSendJobs.Initialize();
DNEnterCriticalSection(&g_csJobQueueLock);
pBilinkOriginalFirstItem = g_blJobQueue.GetNext();
pBilink = pBilinkOriginalFirstItem;
while (pBilink != &g_blJobQueue)
{
pDP8SimJob = DP8SIMJOB_FROM_BILINK(pBilink);
DNASSERT(pDP8SimJob->IsValidObject());
pBilink = pBilink->GetNext();
//
// See if the job is a delayed send.
//
if (pDP8SimJob->GetJobType() == DP8SIMJOBTYPE_DELAYEDSEND)
{
pDP8SimSend = (CDP8SimSend*) pDP8SimJob->GetContext();
DNASSERT(pDP8SimSend->IsValidObject());
//
// See if the delayed send is for the right endpoint.
//
if (pDP8SimSend->GetEndpoint() == pDP8SimEndpoint)
{
//
// Pull the job out of the queue.
//
pDP8SimJob->m_blList.RemoveFromList();
//
// Place it on the temporary list.
//
pDP8SimJob->m_blList.InsertBefore(&blDelayedSendJobs);
}
else
{
//
// Not intended for the given endpoint.
//
}
}
else
{
//
// Not a delayed send.
//
}
}
//
// If the front of the queue changed, alert the worker thread.
//
if (g_blJobQueue.GetNext() != pBilinkOriginalFirstItem)
{
DPFX(DPFPREP, 9, "Front of job queue changed, alerting worker thread.");
SetEvent(g_hWorkerThreadJobEvent);
}
else
{
DPFX(DPFPREP, 9, "Front of job queue did not change.");
}
DNLeaveCriticalSection(&g_csJobQueueLock);
//
// Now actually drop or transmit those messages.
//
pBilink = blDelayedSendJobs.GetNext();
while (pBilink != &blDelayedSendJobs)
{
pDP8SimJob = DP8SIMJOB_FROM_BILINK(pBilink);
DNASSERT(pDP8SimJob->IsValidObject());
pBilink = pBilink->GetNext();
//
// Pull the job out of the temporary list.
//
pDP8SimJob->m_blList.RemoveFromList();
pDP8SimSend = (CDP8SimSend*) pDP8SimJob->GetContext();
pDP8SimSP = pDP8SimJob->GetDP8SimSP();
DNASSERT(pDP8SimSP != NULL);
//
// Either drop the data on the floor or submit it.
//
if (fDrop)
{
//
// Remove the send counter.
//
pDP8SimSP->DecSendsPending();
DPFX(DPFPREP, 7, "Releasing cancelled send 0x%p.", pDP8SimSend);
pDP8SimSend->Release();
}
else
{
//
// Transmit the message.
//
pDP8SimSP->PerformDelayedSend(pDP8SimSend);
}
//
// Release the job object.
//
DPFX(DPFPREP, 7, "Returning job object 0x%p to pool.", pDP8SimJob);
g_pFPOOLJob->Release(pDP8SimJob);
}
DNASSERT(blDelayedSendJobs.IsEmpty());
DPFX(DPFPREP, 5, "Leave");
} // FlushAllDelayedSendsToEndpoint
#undef DPF_MODNAME
#define DPF_MODNAME "FlushAllDelayedReceivesFromEndpoint"
//=============================================================================
// FlushAllDelayedReceivesFromEndpoint
//-----------------------------------------------------------------------------
//
// Description: Removes all data received from the given endpoint that has not
// been indicated yet. If fDrop is TRUE, the messages are
// dropped. If fDrop is FALSE, they are all indicated to the
// upper layer.
//
// Arguments:
// CDP8SimEndpoint * pDP8SimEndpoint - Endpoint whose receives are to be
// removed.
// BOOL fDrop - Whether to drop the receives or not.
//
// Returns: None.
//=============================================================================
void FlushAllDelayedReceivesFromEndpoint(CDP8SimEndpoint * const pDP8SimEndpoint,
BOOL fDrop)
{
HRESULT hr;
CBilink blDelayedReceiveJobs;
CBilink * pBilinkOriginalFirstItem;
CBilink * pBilink;
CDP8SimJob * pDP8SimJob;
CDP8SimReceive * pDP8SimReceive;
CDP8SimSP * pDP8SimSP;
SPIE_DATA * pData;
DPFX(DPFPREP, 5, "Parameters: (0x%p, %i)", pDP8SimEndpoint, fDrop);
DNASSERT(pDP8SimEndpoint->IsValidObject());
blDelayedReceiveJobs.Initialize();
DNEnterCriticalSection(&g_csJobQueueLock);
pBilinkOriginalFirstItem = g_blJobQueue.GetNext();
pBilink = pBilinkOriginalFirstItem;
while (pBilink != &g_blJobQueue)
{
pDP8SimJob = DP8SIMJOB_FROM_BILINK(pBilink);
DNASSERT(pDP8SimJob->IsValidObject());
pBilink = pBilink->GetNext();
//
// See if the job is a delayed receive.
//
if (pDP8SimJob->GetJobType() == DP8SIMJOBTYPE_DELAYEDRECEIVE)
{
pDP8SimReceive = (CDP8SimReceive*) pDP8SimJob->GetContext();
DNASSERT(pDP8SimReceive->IsValidObject());
//
// See if the delayed receive is for the right endpoint.
//
if (pDP8SimReceive->GetEndpoint() == pDP8SimEndpoint)
{
//
// Pull the job out of the queue.
//
pDP8SimJob->m_blList.RemoveFromList();
//
// Place it on the temporary list.
//
pDP8SimJob->m_blList.InsertBefore(&blDelayedReceiveJobs);
}
else
{
//
// Not intended for the given endpoint.
//
}
}
else
{
//
// Not a delayed receive.
//
}
}
//
// If the front of the queue changed, alert the worker thread.
//
if (g_blJobQueue.GetNext() != pBilinkOriginalFirstItem)
{
DPFX(DPFPREP, 9, "Front of job queue changed, alerting worker thread.");
SetEvent(g_hWorkerThreadJobEvent);
}
else
{
DPFX(DPFPREP, 9, "Front of job queue did not change.");
}
DNLeaveCriticalSection(&g_csJobQueueLock);
//
// Now actually drop or transmit those messages.
//
pBilink = blDelayedReceiveJobs.GetNext();
while (pBilink != &blDelayedReceiveJobs)
{
pDP8SimJob = DP8SIMJOB_FROM_BILINK(pBilink);
DNASSERT(pDP8SimJob->IsValidObject());
pBilink = pBilink->GetNext();
//
// Pull the job out of the temporary list.
//
pDP8SimJob->m_blList.RemoveFromList();
pDP8SimReceive = (CDP8SimReceive*) pDP8SimJob->GetContext();
pDP8SimSP = pDP8SimJob->GetDP8SimSP();
DNASSERT(pDP8SimSP != NULL);
//
// Either drop the data on the floor or submit it.
//
if (fDrop)
{
pData = pDP8SimReceive->GetReceiveDataBlockPtr();
DPFX(DPFPREP, 8, "Returning receive data 0x%p (wrapper object 0x%p).",
pData->pReceivedData, pDP8SimSP);
hr = pDP8SimSP->ReturnReceiveBuffers(pData->pReceivedData);
if (hr != DPN_OK)
{
DPFX(DPFPREP, 0, "Failed returning receive buffers 0x%p (err = 0x%lx)! Ignoring.",
pData->pReceivedData, hr);
//
// Ignore failure.
//
}
//
// Remove the receive counter.
//
pDP8SimSP->DecReceivesPending();
DPFX(DPFPREP, 7, "Releasing cancelled receive 0x%p.", pDP8SimReceive);
pDP8SimReceive->Release();
}
else
{
//
// Indicate the message.
//
pDP8SimSP->PerformDelayedReceive(pDP8SimReceive);
}
//
// Release the job object.
//
DPFX(DPFPREP, 7, "Returning job object 0x%p to pool.", pDP8SimJob);
g_pFPOOLJob->Release(pDP8SimJob);
}
DNASSERT(blDelayedReceiveJobs.IsEmpty());
DPFX(DPFPREP, 5, "Leave");
} // FlushAllDelayedReceivesFromEndpoint
#undef DPF_MODNAME
#define DPF_MODNAME "DP8SimWorkerThreadProc"
//=============================================================================
// DP8SimWorkerThreadProc
//-----------------------------------------------------------------------------
//
// Description: The global worker thread function.
//
// Arguments:
// PVOID pvParameter - Thread parameter. Ignored.
//
// Returns: 0 if all goes well.
//=============================================================================
DWORD DP8SimWorkerThreadProc(PVOID pvParameter)
{
DWORD dwReturn;
DWORD dwWaitTimeout = INFINITE;
BOOL fRunning = TRUE;
BOOL fFoundJob;
DWORD dwCurrentTime;
CBilink * pBilink;
CDP8SimJob * pDP8SimJob;
CDP8SimSP * pDP8SimSP;
DPFX(DPFPREP, 5, "Parameters: (0x%p)", pvParameter);
//
// Keep looping until we're told to quit.
//
do
{
//
// Wait for the next job.
//
dwReturn = WaitForSingleObject(g_hWorkerThreadJobEvent, dwWaitTimeout);
switch (dwReturn)
{
case WAIT_OBJECT_0:
case WAIT_TIMEOUT:
{
//
// There's a change in the job queue or a timer expired. See
// if it's time to execute something.
//
//
// Keep looping while we have jobs to perform now.
//
do
{
fFoundJob = FALSE;
//
// Take the lock while we look at the list.
//
DNEnterCriticalSection(&g_csJobQueueLock);
pBilink = g_blJobQueue.GetNext();
if (pBilink != &g_blJobQueue)
{
pDP8SimJob = DP8SIMJOB_FROM_BILINK(pBilink);
DNASSERT(pDP8SimJob->IsValidObject());
dwCurrentTime = timeGetTime();
//
// If the timer has expired, pull the job from the list
// and execute it.
//
if ((int) (pDP8SimJob->GetTime() - dwCurrentTime) <= 0)
{
pDP8SimJob->m_blList.RemoveFromList();
//
// Drop the list lock.
//
DNLeaveCriticalSection(&g_csJobQueueLock);
pDP8SimSP = pDP8SimJob->GetDP8SimSP();
DPFX(DPFPREP, 8, "Job 0x%p has expired (job time = %u, current time = %u, type = %u, context 0x%p, interface = 0x%p).",
pDP8SimJob, pDP8SimJob->GetTime(), dwCurrentTime,
pDP8SimJob->GetJobType(), pDP8SimJob->GetContext(),
pDP8SimSP);
//
// Figure out what to do with the job.
//
switch (pDP8SimJob->GetJobType())
{
case DP8SIMJOBTYPE_DELAYEDSEND:
{
//
// Finally submit the send.
//
DNASSERT(pDP8SimSP != NULL);
pDP8SimSP->PerformDelayedSend(pDP8SimJob->GetContext());
break;
}
case DP8SIMJOBTYPE_DELAYEDRECEIVE:
{
//
// Finally indicate the receive.
//
DNASSERT(pDP8SimSP != NULL);
pDP8SimSP->PerformDelayedReceive(pDP8SimJob->GetContext());
break;
}
case DP8SIMJOBTYPE_QUIT:
{
//
// Stop looping.
//
DNASSERT(pDP8SimSP == NULL);
DPFX(DPFPREP, 2, "Quit job received.");
fRunning = FALSE;
break;
}
default:
{
DPFX(DPFPREP, 0, "Unexpected job type %u!",
pDP8SimJob->GetJobType());
DNASSERT(FALSE);
fRunning = FALSE;
break;
}
}
//
// Release the job object.
//
DPFX(DPFPREP, 7, "Returning job object 0x%p to pool.", pDP8SimJob);
g_pFPOOLJob->Release(pDP8SimJob);
//
// Check out the next job (unless we're bailing).
//
fFoundJob = fRunning;
}
else
{
//
// Not time for job yet. Figure out when it will
// be.
//
dwWaitTimeout = pDP8SimJob->GetTime() - dwCurrentTime;
//
// Drop the list lock.
//
DNLeaveCriticalSection(&g_csJobQueueLock);
DPFX(DPFPREP, 8, "Next job in %u ms.", dwWaitTimeout);
}
}
else
{
//
// Nothing in the job queue. Drop the list lock.
//
DNLeaveCriticalSection(&g_csJobQueueLock);
//
// Wait until something gets put into the queue.
//
dwWaitTimeout = INFINITE;
DPFX(DPFPREP, 8, "No more jobs.");
}
}
while (fFoundJob);
//
// Go back to waiting.
//
break;
}
default:
{
//
// Something unusual happened.
//
DPFX(DPFPREP, 0, "Got unexpected return value from WaitForSingleObject (%u)!");
DNASSERT(FALSE);
fRunning = FALSE;
break;
}
}
}
while (fRunning);
DPFX(DPFPREP, 5, "Returning: [%u]", dwReturn);
return dwReturn;
} // DP8SimWorkerThreadProc