Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1218 lines
29 KiB

//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1997.
//
// File: thdpool.c
//
// Contents: Home of the SPM thread pool
//
// Classes:
//
// Functions:
//
// History: 6-08-93 RichardW Created
//
//----------------------------------------------------------------------------
#include <lsapch.hxx>
#define POOL_SEM_LIMIT 0x7FFFFFFF
#define MAX_POOL_THREADS_HARD 256
#define MAX_SUBQUEUE_THREADS (4 * MAX_POOL_THREADS_HARD)
LSAP_TASK_QUEUE GlobalQueue;
//
// Local Prototypes:
//
typedef enum _LSAP_TASK_STATUS {
TaskNotQueued,
TaskQueued,
TaskUnknown
} LSAP_TASK_STATUS ;
LSAP_TASK_STATUS
EnqueueThreadTask(
PLSAP_TASK_QUEUE pQueue,
PLSAP_THREAD_TASK pTask,
BOOLEAN fUrgent);
#define LockQueue(q) EnterCriticalSection(&((q)->Lock))
#define UnlockQueue(q) LeaveCriticalSection(&((q)->Lock))
//+---------------------------------------------------------------------------
//
// Function: InitializeTaskQueue
//
// Synopsis: Initialize a Queue Structure
//
// Arguments: [pQueue] --
// [Type] --
//
// History: 11-10-95 RichardW Created
//
// Notes:
//
//----------------------------------------------------------------------------
BOOL
InitializeTaskQueue(
PLSAP_TASK_QUEUE pQueue,
LSAP_TASK_QUEUE_TYPE Type
)
{
OBJECT_HANDLE_FLAG_INFORMATION FlagInfo ;
RtlZeroMemory( pQueue, sizeof(LSAP_TASK_QUEUE) );
InitializeListHead(
&pQueue->pTasks
);
pQueue->Type = Type;
pQueue->hSemaphore = CreateSemaphore(NULL, 0, POOL_SEM_LIMIT, NULL);
if (!pQueue->hSemaphore)
{
return FALSE;
}
if ( FALSE == InitializeCriticalSectionAndSpinCount(
&pQueue->Lock,
LsaTuningParameters.CritSecSpinCount
))
{
NtClose( pQueue->hSemaphore );
pQueue->hSemaphore = NULL ;
return FALSE;
}
FlagInfo.Inherit = FALSE ;
FlagInfo.ProtectFromClose = TRUE ;
NtSetInformationObject(
pQueue->hSemaphore,
ObjectHandleFlagInformation,
&FlagInfo,
sizeof( FlagInfo ) );
pQueue->StartSync = CreateEvent( NULL, TRUE, FALSE, NULL );
if ( pQueue->StartSync == NULL )
{
DebugLog(( DEB_ERROR, "Could not create start sync event\n" ));
NtClose( pQueue->hSemaphore );
pQueue->hSemaphore = NULL;
DeleteCriticalSection( &pQueue->Lock );
return FALSE ;
}
return( TRUE );
}
//+---------------------------------------------------------------------------
//
// Function: InitializeThreadPool
//
// Synopsis: Initializes necessary data for the thread pool
//
// Arguments: (none)
//
// History: 7-13-93 RichardW Created
//
// Notes:
//
//----------------------------------------------------------------------------
BOOL
InitializeThreadPool(void)
{
if (!InitializeTaskQueue(&GlobalQueue, QueueShared))
{
return( FALSE );
}
return(TRUE);
}
//+---------------------------------------------------------------------------
//
// Function: QueueAssociateThread
//
// Synopsis: Associates the thread with the queue
//
// Arguments: [pQueue] --
//
// History: 11-09-95 RichardW Created
//
// Notes:
//
//----------------------------------------------------------------------------
VOID
QueueAssociateThread(
PLSAP_TASK_QUEUE pQueue)
{
PSession pSession;
LockQueue( pQueue );
pQueue->TotalThreads++ ;
//
// Update the statistics:
//
if ( pQueue->MaxThreads < pQueue->TotalThreads )
{
pQueue->MaxThreads = pQueue->TotalThreads ;
}
UnlockQueue( pQueue );
}
//+---------------------------------------------------------------------------
//
// Function: QueueDisassociateThread
//
// Synopsis: Disconnects a thread and a queue
//
// Arguments: [pQueue] --
// [pLastThread] -- OPTIONAL flag indicating last thread of queue
//
// History: 11-09-95 RichardW Created
//
// Notes:
//
//----------------------------------------------------------------------------
BOOL
QueueDisassociateThread(
PLSAP_TASK_QUEUE pQueue,
BOOLEAN * pLastThread)
{
PSession pSession;
LockQueue( pQueue );
DsysAssert(pQueue->TotalThreads > 0);
if ( pQueue->TotalThreads == 1 )
{
if ( pLastThread )
{
*pLastThread = TRUE ;
//
// If the queue is being run down, set the
// event since we are the last thread
//
if ( pQueue->Type == QueueZombie )
{
SetEvent( pQueue->StartSync );
}
}
if ( pQueue->Tasks )
{
//
// Make sure that we never have more tasks queued
// to a zombie
//
DsysAssert( pQueue->Type != QueueZombie );
UnlockQueue( pQueue );
return FALSE ;
}
}
pQueue->TotalThreads--;
UnlockQueue( pQueue );
return TRUE ;
}
//+---------------------------------------------------------------------------
//
// Function: DequeueAnyTask
//
// Synopsis: Returns a task from this queue or any shared, if available
//
// Arguments: [pQueue] --
//
// Requires: pQueue must be locked!
//
// History: 11-09-95 RichardW Created
//
// Notes:
//
//----------------------------------------------------------------------------
PLSAP_THREAD_TASK
DequeueAnyTask(
PLSAP_TASK_QUEUE pQueue)
{
PLSAP_THREAD_TASK pTask;
PLSAP_TASK_QUEUE pShared;
if ( !IsListEmpty(&pQueue->pTasks) )
{
pTask = (PLSAP_THREAD_TASK) RemoveHeadList(&pQueue->pTasks);
pQueue->Tasks --;
pQueue->TaskCounter++;
//
// Reset the pointers. This is required by the recovery logic
// in the Enqueue function below.
//
pTask->Next.Flink = NULL ;
pTask->Next.Blink = NULL ;
return( pTask );
}
//
// No pending on primary queue. Check secondaries:
//
if (pQueue->Type == QueueShared)
{
pShared = pQueue->pShared;
while ( pShared )
{
PLSAP_TASK_QUEUE pPrev;
DWORD WaitStatus;
//
// We need to wait now to change the semaphore count
//
WaitStatus = WaitForSingleObject(pShared->hSemaphore, 0);
LockQueue( pShared );
if ((WaitStatus == WAIT_OBJECT_0) && !IsListEmpty(&pShared->pTasks) )
{
pTask = (PLSAP_THREAD_TASK) RemoveHeadList(&pShared->pTasks);
pShared->Tasks--;
pShared->TaskCounter++;
UnlockQueue( pShared ); // Unlock shared queue
return( pTask );
}
pPrev = pShared;
pShared = pShared->pNext;
UnlockQueue( pPrev );
}
}
return( NULL );
}
//+---------------------------------------------------------------------------
//
// Function: WaitForThreadTask
//
// Synopsis: Function called by queue waiters
//
// Arguments: [pQueue] -- Queue to wait on
// [TimeOut] -- timeout in seconds
//
// History: 11-10-95 RichardW Created
//
// Notes:
//
//----------------------------------------------------------------------------
PLSAP_THREAD_TASK
WaitForThreadTask(
PLSAP_TASK_QUEUE pQueue,
DWORD TimeOut)
{
PLSAP_THREAD_TASK pTask;
DWORD WaitResult;
LockQueue( pQueue );
pTask = DequeueAnyTask( pQueue );
if (pTask)
{
UnlockQueue( pQueue );
return( pTask );
}
//
// No pending anywhere.
//
if (TimeOut == 0)
{
UnlockQueue( pQueue );
return( NULL );
}
//
// Principal of the loop: We do this loop so long as we were awakened
// by the semaphore being released. If there was no task to pick up, then
// we go back and wait again. We return NULL only after a timeout, or an
// error.
//
do
{
pQueue->IdleThreads++;
UnlockQueue( pQueue );
WaitResult = WaitForSingleObject( pQueue->hSemaphore, TimeOut );
LockQueue( pQueue );
//
// In between the wait returning and the lock succeeding, another
// thread might have queued up a request.
DsysAssert(pQueue->IdleThreads > 0);
pQueue->IdleThreads--;
//
// In between the wait returning and the lock succeeding, another
// thread might have queued up a request, so don't blindly
// bail out. Check the pending count, and skip over this
// exit path if something is there
//
if ( pQueue->Tasks == 0 )
{
if (WaitResult != WAIT_OBJECT_0)
{
UnlockQueue( pQueue );
if ( WaitResult == WAIT_FAILED )
{
DebugLog((DEB_ERROR, "Error on waiting for semaphore, %d\n", GetLastError()));
}
return( NULL );
}
}
//
// If the queue type is reset to Zombie, then this queue is
// being killed. Return NULL immediately. If we're the last
// thread, set the event so the thread deleting the queue will
// wake up in a timely fashion.
//
if ( pQueue->Type == QueueZombie )
{
UnlockQueue( pQueue );
return NULL ;
}
pTask = DequeueAnyTask( pQueue );
if (pTask)
{
UnlockQueue( pQueue );
return( pTask );
}
//
// Track number of times we woke up but didn't have anything to do
//
pQueue->MissedTasks ++ ;
} while ( WaitResult != WAIT_TIMEOUT );
UnlockQueue( pQueue );
return( NULL );
}
//+---------------------------------------------------------------------------
//
// Function: SpmPoolThreadBase
//
// Synopsis: New Pool Thread Base
//
// Arguments: [pvQueue] -- OPTIONAL queue to use
//
// History: 11-09-95 RichardW Created
//
// Notes:
//
//----------------------------------------------------------------------------
DWORD
SpmPoolThreadBase(
PVOID pvSession)
{
PLSAP_THREAD_TASK pTask;
PLSAP_TASK_QUEUE pQueue;
PSession pSession;
BOOLEAN ShrinkWS = FALSE;
DWORD dwResult;
DWORD Timeout;
PSession ThreadSession ;
PSession OriginalSession ;
OriginalSession = GetCurrentSession();
if ( pvSession )
{
ThreadSession = (PSession) pvSession ;
SpmpReferenceSession( ThreadSession );
SetCurrentSession( ThreadSession );
}
else
{
ThreadSession = OriginalSession ;
SpmpReferenceSession( ThreadSession );
}
pQueue = ThreadSession->SharedData->pQueue ;
if (!pQueue)
{
pQueue = &GlobalQueue;
}
QueueAssociateThread( pQueue );
//
// Share pool threads have short lifespans. Dedicated single, or
// single read threads have infinite life span.
//
if (pQueue->Type == QueueShared)
{
Timeout = LsaTuningParameters.ThreadLifespan * 1000;
}
else
{
//
// If we are the dedicated thread for this queue, the timeout
// is infinite. If we are a temporary thread, the timeout is
// the subqueue
//
if ( ThreadSession->ThreadId != GetCurrentThreadId() )
{
Timeout = LsaTuningParameters.SubQueueLifespan ;
}
else
{
Timeout = INFINITE ;
}
}
if ( pQueue->StartSync )
{
DebugLog(( DEB_TRACE, "ThreadPool: Signaling start event\n" ));
//
// If a queue was passed in, the caller of CreateXxxQueue is blocked
// waiting for us to strobe the start sync event.
//
SetEvent( pQueue->StartSync );
}
while ( TRUE )
{
pTask = WaitForThreadTask( pQueue, Timeout );
if ( pTask )
{
SetCurrentSession( pTask->pSession );
dwResult = pTask->pFunction(pTask->pvParameter);
#if DBG
RtlCheckForOrphanedCriticalSections( NtCurrentThread() );
#endif
SetCurrentSession( ThreadSession );
//
// The session in this task was referenced during the AssignThread call.
// This dereference cleans that up.
//
SpmpDereferenceSession(pTask->pSession);
LsapFreePrivateHeap( pTask );
}
else
{
//
// We can never leave the queue empty of threads if
// there are still tasks pending. QueueDisassociateThread
// will fail if there are tasks pending. In that case,
// skip up to the top of the loop again.
//
if ( !QueueDisassociateThread( pQueue, &ShrinkWS ) )
{
continue;
}
//
// Now that we are not part of that queue, reset our thread session
//
SpmpDereferenceSession( ThreadSession );
SetCurrentSession( OriginalSession );
if ( LsaTuningParameters.Options & TUNE_TRIM_WORKING_SET )
{
if (ShrinkWS)
{
SetProcessWorkingSetSize( GetCurrentProcess(),
(SIZE_T)(-1),
(SIZE_T)(-1) );
}
}
DebugLog(( DEB_TRACE,
"No tasks pending on queue %x, thread exiting\n",
pQueue ));
return( 0 );
}
}
return( 0 );
}
//+---------------------------------------------------------------------------
//
// Function: EnqueueThreadTask
//
// Synopsis: Enqueue a task, update counts, etc.
//
// Arguments: [pTask] -- Task to add
//
// History: 7-13-93 RichardW Created
//
// Notes:
//
//----------------------------------------------------------------------------
LSAP_TASK_STATUS
EnqueueThreadTask(
PLSAP_TASK_QUEUE pQueue,
PLSAP_THREAD_TASK pTask,
BOOLEAN fUrgent)
{
BOOLEAN NeedMoreThreads = FALSE;
HANDLE hQueueSem ;
HANDLE hParentSem = NULL ;
PLSAP_TASK_QUEUE pThreadQueue = NULL;
PSession pSession = NULL;
LockQueue(pQueue);
if ( pQueue->Type == QueueZombie )
{
UnlockQueue( pQueue );
return TaskNotQueued ;
}
if (fUrgent)
{
InsertHeadList(
&pQueue->pTasks,
&pTask->Next
);
}
else
{
InsertTailList(
&pQueue->pTasks,
&pTask->Next
);
}
pQueue->Tasks++;
pQueue->QueuedCounter++;
if ( pQueue->Tasks > pQueue->TaskHighWater )
{
pQueue->TaskHighWater = pQueue->Tasks ;
}
if (pQueue->Type == QueueShared)
{
if ((pQueue->Tasks > pQueue->IdleThreads) &&
(pQueue->TotalThreads < MAX_POOL_THREADS_HARD))
{
NeedMoreThreads = TRUE;
pThreadQueue = pQueue;
}
hParentSem = NULL ;
}
else if (pQueue->Type == QueueShareRead )
{
DsysAssert( pQueue->pOriginal );
//
// Here's the race potential. If the queue we have has no idle thread,
// then make sure there is an idle thread at the parent queue, otherwise
// we can deadlock (e.g. this call is in response to the job being executed
// by the dedicated thread. Of course, this can also be a problem correctly
// determining the parent queue's status, since locks should always flow down,
// not up. So, if the number of jobs that we have pending exceeds the number
// of idle threads, *always*, regardless of the other queue's real state or
// total threads, queue up another thread.
//
if ( pQueue->Tasks > pQueue->IdleThreads )
{
NeedMoreThreads = TRUE ;
pSession = pTask->pSession ;
if ( pQueue->TotalThreads < MAX_SUBQUEUE_THREADS )
{
pThreadQueue = pQueue ;
}
else
{
pThreadQueue = pQueue->pOriginal;
}
}
//
// This is a safe read. The semaphore is not subject to change after creation,
// and the worst that can happen is a bad handle.
//
hParentSem = pQueue->pOriginal->hSemaphore ;
}
hQueueSem = pQueue->hSemaphore ;
UnlockQueue( pQueue );
//
// Kick our semaphore.
//
ReleaseSemaphore( hQueueSem, 1, NULL );
//
// Kick the parent semaphore
//
if ( hParentSem )
{
ReleaseSemaphore( hParentSem, 1, NULL );
}
if (NeedMoreThreads)
{
HANDLE hThread;
DWORD tid;
DebugLog((DEB_TRACE_QUEUE, "Queue %x needs more threads\n", pQueue));
//
// Increment the number of threads now so we don't create more threads
// while we wait for the first one to be created.
//
InterlockedIncrement( &pThreadQueue->ReqThread );
//
// If the queue is a dedicated queue, supply the session from the task.
// if the queue is a shared (global) queue, pass in NULL:
//
hThread = LsapCreateThread( NULL, 0,
SpmPoolThreadBase,
(pThreadQueue->Type == QueueShareRead ?
pThreadQueue->OwnerSession : NULL ),
0, &tid);
//
// Check for failure
//
if (hThread == NULL)
{
//
// This is extremely painful. The thread creation attempt
// failed, but because of the nature of the queue, we don't
// know if it was picked up and executed, or it was dropped,
// or anything about it.
//
return TaskUnknown ;
}
else
{
NtClose(hThread);
}
}
return TaskQueued ;
}
//+---------------------------------------------------------------------------
//
// Function: SpmAssignThread
//
// Synopsis: Assigns a task to a thread pool thread.
//
// Arguments: [pFunction] -- Function to execute
// [pvParameter] -- Parameter to function
// [pSession] -- Session to execute as
//
// History: 11-24-93 RichardW Created
//
// Notes:
//
//----------------------------------------------------------------------------
PVOID
LsapAssignThread(
LPTHREAD_START_ROUTINE pFunction,
PVOID pvParameter,
PSession pSession,
BOOLEAN fUrgent)
{
PLSAP_THREAD_TASK pTask;
PLSAP_TASK_QUEUE pQueue;
LSAP_TASK_STATUS TaskStatus ;
pTask = (PLSAP_THREAD_TASK) LsapAllocatePrivateHeap( sizeof( LSAP_THREAD_TASK ) );
if (!pTask)
{
return( NULL );
}
pTask->pFunction = pFunction;
pTask->pvParameter = pvParameter;
pTask->pSession = pSession;
if ( pSession->SharedData->pQueue )
{
LockSession(pSession);
if( pSession->SharedData->pQueue )
{
pQueue = pSession->SharedData->pQueue;
} else
{
pQueue = &GlobalQueue;
}
UnlockSession(pSession);
}
else
{
pQueue = &GlobalQueue;
}
//
// Reference the session so that it will never go away while a thread
// is working on this task. The worker function will deref the session.
//
SpmpReferenceSession( pSession );
TaskStatus = EnqueueThreadTask( pQueue,
pTask,
fUrgent );
if ( ( TaskStatus == TaskQueued ) ||
( TaskStatus == TaskUnknown ) )
{
return pTask ;
}
if ( TaskStatus == TaskNotQueued )
{
//
// Failed, therefore deref this session.
//
SpmpDereferenceSession( pSession );
LsapFreePrivateHeap( pTask );
}
return NULL ;
}
//+---------------------------------------------------------------------------
//
// Function: CreateSubordinateQueue
//
// Synopsis: Create a Queue hanging off an original queue.
//
// Arguments: [pQueue] --
// [pOriginalQueue] --
//
// History: 11-17-95 RichardW Created
//
// Notes:
//
//----------------------------------------------------------------------------
BOOL
CreateSubordinateQueue(
PSession pSession,
PLSAP_TASK_QUEUE pOriginalQueue
)
{
HANDLE hThread;
DWORD tid;
PLSAP_TASK_QUEUE pQueue ;
pQueue = (LSAP_TASK_QUEUE *) LsapAllocatePrivateHeap( sizeof( LSAP_TASK_QUEUE ) );
if ( !pQueue )
{
return FALSE ;
}
DebugLog(( DEB_TRACE_QUEUE, "Creating sub queue %x\n", pQueue ));
if (InitializeTaskQueue( pQueue, QueueShareRead ))
{
hThread = LsapCreateThread(NULL,
0,
SpmPoolThreadBase,
pSession,
CREATE_SUSPENDED,
&pSession->ThreadId);
if (hThread == NULL)
{
NtClose( pQueue->StartSync );
NtClose( pQueue->hSemaphore );
RtlDeleteCriticalSection( &pQueue->Lock );
LsapFreePrivateHeap( pQueue );
pQueue = NULL;
return FALSE;
}
else
{
LockQueue( pQueue );
LockQueue( pOriginalQueue );
pQueue->pNext = pOriginalQueue->pShared;
pOriginalQueue->pShared = pQueue;
pQueue->pOriginal = pOriginalQueue;
pQueue->OwnerSession = pSession ;
UnlockQueue( pOriginalQueue );
UnlockQueue( pQueue );
pSession->SharedData->pQueue = pQueue ;
ResumeThread( hThread );
NtClose( hThread );
//
// Wait for the thread to signal the event, so that
// we know it's ready
//
WaitForSingleObject( pQueue->StartSync, INFINITE );
NtClose( pQueue->StartSync );
pQueue->StartSync = NULL ;
return TRUE;
}
}
LsapFreePrivateHeap( pQueue );
return FALSE;
}
//+---------------------------------------------------------------------------
//
// Function: DeleteSubordinateQueue
//
// Synopsis:
//
// Effects:
//
// Arguments: [pQueue] --
//
// Requires:
//
// Returns:
//
// Signals:
//
// Modifies:
//
// Algorithm:
//
// History: 8-05-96 RichardW Created
//
// Notes:
//
//----------------------------------------------------------------------------
BOOL
DeleteSubordinateQueue(
PLSAP_TASK_QUEUE pQueue,
ULONG Flags)
{
PLSAP_TASK_QUEUE pOriginal;
PLSAP_THREAD_TASK pTask ;
DWORD dwResult ;
PLIST_ENTRY List ;
PSession ThreadSession = GetCurrentSession();
OBJECT_HANDLE_FLAG_INFORMATION FlagInfo ;
//
// Lock it
//
DebugLog(( DEB_TRACE, "Deleting queue %x\n", pQueue ));
LockQueue( pQueue );
if ( pQueue->pShared )
{
pOriginal = pQueue->pOriginal ;
LockQueue( pOriginal );
//
// Unlink Queue from parent:
//
if ( pOriginal->pShared != pQueue )
{
PLSAP_TASK_QUEUE pScan = pOriginal->pShared;
LockQueue( pScan );
while ( pScan->pNext && (pScan->pNext != pQueue) )
{
pScan = pScan->pNext;
}
if ( pScan->pNext )
{
pScan->pNext = pQueue->pNext ;
}
UnlockQueue( pScan );
}
else
{
pOriginal->pShared = pQueue->pNext;
}
pQueue->pNext = NULL ;
//
// Done with parent
//
UnlockQueue( pOriginal );
}
//
// Drain queue by removing all the tasks.
//
while ( !IsListEmpty( &pQueue->pTasks ) )
{
List = RemoveHeadList( &pQueue->pTasks );
pQueue->Tasks-- ;
pTask = CONTAINING_RECORD( List, LSAP_THREAD_TASK, Next );
//
// A synchronous drain will have this thread execute
// all remaining tasks.
//
if ( Flags & DELETEQ_SYNC_DRAIN )
{
SpmpReferenceSession(pTask->pSession);
SetCurrentSession( pTask->pSession );
dwResult = pTask->pFunction(pTask->pvParameter);
SetCurrentSession( ThreadSession );
SpmpDereferenceSession(pTask->pSession);
LsapFreePrivateHeap( pTask );
}
else
{
//
// Otherwise, send them to the global queue to be
// executed by other threads
//
EnqueueThreadTask(
&GlobalQueue,
pTask,
FALSE );
}
}
//
// Now, kill off all the threads
//
pQueue->Type = QueueZombie ;
//
// We might be executing on our own worker thread. If there are
// more than one thread associated with this queue, we also
// need to do the sync.
//
if ( ( pQueue->OwnerSession != ThreadSession ) ||
( pQueue->TotalThreads > 1 ) )
{
//
// We are not a worker thread. Sync with the other
// threads to clean up:
//
pQueue->StartSync = CreateEvent( NULL, FALSE, FALSE, NULL );
//
// Kick the semaphore for all the threads that need it
// (all of them if we aren't a worker thread, or n-1 if
// we are:
//
ReleaseSemaphore(
pQueue->hSemaphore,
(pQueue->OwnerSession == ThreadSession ?
pQueue->TotalThreads - 1 :
pQueue->TotalThreads),
NULL );
UnlockQueue( pQueue );
//
// if we failed to create an event, then we may cause an invalid handle
// problem in the client threads. Sleep a little to let the other threads
// go (hopefully), then close the semaphore and let the error handling
// in the threads deal with it.
//
if ( pQueue->StartSync )
{
WaitForSingleObjectEx( pQueue->StartSync, INFINITE, FALSE );
//
// Synchronize with the last thread to own the queue:
//
LockQueue( pQueue );
NtClose( pQueue->StartSync );
pQueue->StartSync = NULL ;
}
else
{
//
// kludge up a retry loop:
//
int i = 50 ;
while ( i && pQueue->TotalThreads )
{
Sleep( 100 );
i-- ;
}
//
// If they're still there, forget it. Return FALSE. Leak.
//
if ( pQueue->TotalThreads )
{
return FALSE ;
}
}
}
//
// At this point, we close the queue down:
//
FlagInfo.Inherit = FALSE ;
FlagInfo.ProtectFromClose = FALSE ;
NtSetInformationObject(
pQueue->hSemaphore,
ObjectHandleFlagInformation,
&FlagInfo,
sizeof( FlagInfo ) );
NtClose( pQueue->hSemaphore );
UnlockQueue( pQueue );
RtlDeleteCriticalSection( &pQueue->Lock );
LsapFreePrivateHeap( pQueue );
return( TRUE );
}