864 lines
18 KiB
C++
864 lines
18 KiB
C++
/*++
|
|
|
|
Copyright (c) 1995 Microsoft Corporation
|
|
All rights reserved.
|
|
|
|
Module Name:
|
|
|
|
notify.cxx
|
|
|
|
Abstract:
|
|
|
|
Handles object updates and notifications from the printing system.
|
|
|
|
Author:
|
|
|
|
Albert Ting (AlbertT) 15-Sept-1994
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "precomp.hxx"
|
|
#pragma hdrstop
|
|
|
|
#include "notify.hxx"
|
|
|
|
#if DBG
|
|
TBackTraceMem* gpbtNotify;
|
|
#endif
|
|
|
|
/********************************************************************
|
|
|
|
Public functions.
|
|
|
|
********************************************************************/
|
|
|
|
TNotify::
|
|
TNotify(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Constructs a Notify object that can be used to watch several
|
|
event handles.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
|
|
{
|
|
DBGMSG( DBG_NOTIFY, ( "Notify.Notify: ctr %x\n", this ));
|
|
|
|
#if DBG
|
|
if( !gpbtNotify ){
|
|
gpbtNotify = new TBackTraceMem;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Initialize member fields.
|
|
//
|
|
CSGuard._bDelete = FALSE;
|
|
_dwSleepTime = kSleepTime;
|
|
|
|
_hEventProcessed = CreateEvent( NULL,
|
|
FALSE,
|
|
FALSE,
|
|
NULL );
|
|
|
|
//
|
|
// _hEventProcessed is our valid check.
|
|
//
|
|
|
|
#if DBG
|
|
if( gpbtNotify ){
|
|
gpbtNotify->pvCapture( 0x10, (DWORD)this );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
VOID
|
|
TNotify::
|
|
vDelete(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Mark the object as pending deletion.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Notify all objects on linked list.
|
|
//
|
|
TIter Iter;
|
|
TWait* pWait;
|
|
|
|
#if DBG
|
|
if( gpbtNotify ){
|
|
gpbtNotify->pvCapture( 0x1d, (DWORD)this );
|
|
}
|
|
#endif
|
|
|
|
{
|
|
TCritSecLock CSL( _CritSec );
|
|
|
|
CSGuard._bDelete = TRUE;
|
|
|
|
for( CSGuard.Wait_vIterInit( Iter ), Iter.vNext();
|
|
Iter.bValid();
|
|
Iter.vNext( )){
|
|
|
|
pWait = CSGuard.Wait_pConvert( Iter );
|
|
vSendRequest( pWait );
|
|
}
|
|
}
|
|
#if DBG
|
|
if( gpbtNotify ){
|
|
gpbtNotify->pvCapture( 0x1e, (DWORD)this );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
VOID
|
|
TNotify::
|
|
vSendRequest(
|
|
TWait* pWait
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send a request to the pWait thread. The pWait thread will pickup
|
|
our notification, process it, then shend a handshake.
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
SetEvent( pWait->_ahNotifyArea[0] );
|
|
#if DBG
|
|
if( gpbtNotify ){
|
|
gpbtNotify->pvCapture( 0x12, (DWORD)this, (DWORD)pWait );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
STATUS
|
|
TNotify::
|
|
sRegister(
|
|
MNotifyWork* pNotifyWork
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Registers a notification work item with the system. It may
|
|
already be watched; in this case, the notification is modified
|
|
(this may occur if the event handle needs to change).
|
|
|
|
Arguments:
|
|
|
|
pNotifyWork - Item that should be modified.
|
|
|
|
Return Value:
|
|
|
|
STATUS - ERROR_SUCCESS = success, else error.
|
|
|
|
--*/
|
|
|
|
{
|
|
TStatus Status( DBG_WARN );
|
|
Status DBGCHK = ERROR_SUCCESS;
|
|
|
|
TWait* pWait = NULL;
|
|
|
|
{
|
|
|
|
TCritSecLock CSL( _CritSec );
|
|
|
|
SPLASSERT( !CSGuard._bDelete );
|
|
|
|
//
|
|
// The TWait will look in these globals to determine which
|
|
// pNotifyWork it should register.
|
|
//
|
|
CSGuard._pNotifyWork = pNotifyWork;
|
|
|
|
//
|
|
// If registering, make sure the event is OK.
|
|
//
|
|
if( pNotifyWork->hEvent() == INVALID_HANDLE_VALUE ||
|
|
!pNotifyWork->hEvent( )){
|
|
|
|
SPLASSERT( FALSE );
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Check and see if we are already registered.
|
|
//
|
|
if( pNotifyWork->_pWait ){
|
|
|
|
//
|
|
// We are already on a TWait, so notify that particular thread.
|
|
//
|
|
pWait = pNotifyWork->_pWait;
|
|
CSGuard._eOperation = kEopModify;
|
|
|
|
} else {
|
|
|
|
//
|
|
// New item to watch. Traverse through all the TWaits
|
|
// and add to the first one that has space.
|
|
//
|
|
CSGuard._eOperation = kEopRegister;
|
|
TIter Iter;
|
|
|
|
for( CSGuard.Wait_vIterInit( Iter ), Iter.vNext();
|
|
Iter.bValid();
|
|
Iter.vNext( )){
|
|
|
|
pWait = CSGuard.Wait_pConvert( Iter );
|
|
if( !pWait->bFull( )){
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the iter is valid, we broke out of the loop because
|
|
// we found one. If it's not valid, we need to create
|
|
// a new TWork.
|
|
//
|
|
if( !Iter.bValid( )){
|
|
|
|
//
|
|
// We need to create a new TWait.
|
|
//
|
|
pWait = new TWait( this );
|
|
|
|
if( !VALID_PTR( pWait )){
|
|
|
|
Status DBGCHK = GetLastError();
|
|
SPLASSERT( (STATUS)Status );
|
|
|
|
delete pWait;
|
|
pWait = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Inform worker thread that it must wake up
|
|
// and register this new event.
|
|
//
|
|
if( pWait ){
|
|
|
|
vSendRequest( pWait );
|
|
|
|
//
|
|
// We must perform a handshake to ensure that only one register
|
|
// occurs at a time. We will sit in the critical section until
|
|
// the worker signals us that they are done.
|
|
//
|
|
WaitForSingleObject( _hEventProcessed, INFINITE );
|
|
|
|
} else {
|
|
|
|
//
|
|
// We _must_ have status set to indicate an error
|
|
// occurred.
|
|
//
|
|
SPLASSERT( (STATUS)Status );
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
STATUS
|
|
TNotify::
|
|
sUnregister(
|
|
MNotifyWork* pNotifyWork
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Unregister a work item to be watched by the system.
|
|
|
|
Arguments:
|
|
|
|
pNotifyWork - Item that should be modified.
|
|
|
|
Return Value:
|
|
|
|
STATUS - ERROR_SUCCESS = success, else error.
|
|
|
|
--*/
|
|
|
|
{
|
|
{
|
|
TCritSecLock CSL( _CritSec );
|
|
|
|
SPLASSERT( !CSGuard._bDelete );
|
|
|
|
//
|
|
// Only do this if the TNotifyWork is registered.
|
|
//
|
|
if( pNotifyWork->_pWait ){
|
|
|
|
CSGuard._eOperation = kEopUnregister;
|
|
CSGuard._pNotifyWork = pNotifyWork;
|
|
|
|
vSendRequest( pNotifyWork->_pWait );
|
|
WaitForSingleObject( _hEventProcessed, INFINITE );
|
|
|
|
//
|
|
// We are now unregistered, remove _pWait.
|
|
//
|
|
pNotifyWork->_pWait = NULL;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Deleting handle, but it doesn't exist.
|
|
//
|
|
DBGMSG( DBG_NOTIFY,
|
|
( "Notify.sUnregister: %x Del NotifyWork %x not on list\n",
|
|
this, pNotifyWork ));
|
|
}
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
/********************************************************************
|
|
|
|
Private functions
|
|
|
|
********************************************************************/
|
|
|
|
VOID
|
|
TNotify::
|
|
vRefZeroed(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Virtual definition for MRefCom. When the refcount has reached
|
|
zero, we want to delete ourselves.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
|
|
{
|
|
if( bValid( )){
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
|
|
TNotify::
|
|
~TNotify(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Delete TNotify.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
DBGMSG( DBG_NOTIFY, ( "Notify.Notify: dtr %x\n", this ));
|
|
|
|
#if DBG
|
|
if( gpbtNotify ){
|
|
gpbtNotify->pvCapture( 0x1f, (DWORD)this );
|
|
}
|
|
#endif
|
|
|
|
SPLASSERT( CSGuard._bDelete );
|
|
SPLASSERT( CSGuard.Wait_bEmpty( ));
|
|
|
|
if( _hEventProcessed ){
|
|
CloseHandle( _hEventProcessed );
|
|
}
|
|
}
|
|
|
|
/********************************************************************
|
|
|
|
TWait
|
|
|
|
********************************************************************/
|
|
|
|
TNotify::
|
|
TWait::
|
|
TWait(
|
|
TNotify* pNotify
|
|
) : RL_Notify( pNotify ), _cNotifyWork( 0 )
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Construct TWait.
|
|
|
|
TWaits are built when new object need to be watched. We
|
|
need several TWaits since only 31 handles can be watched
|
|
per TWait.
|
|
|
|
Arguments:
|
|
|
|
pNotify - Owning TNotify.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
|
|
{
|
|
SPLASSERT( pNotify->_CritSec.bInside( ));
|
|
SPLASSERT( pNotify );
|
|
|
|
#if DBG
|
|
if( gpbtNotify ){
|
|
gpbtNotify->pvCapture( 0x20, (DWORD)this );
|
|
}
|
|
#endif
|
|
|
|
HANDLE hThread;
|
|
DWORD dwIgnore;
|
|
|
|
//
|
|
// Create our trigger event to add and remove events.
|
|
//
|
|
_ahNotifyArea[0] = CreateEvent( NULL,
|
|
FALSE,
|
|
FALSE,
|
|
NULL );
|
|
//
|
|
// _ahNotifyArea[0] is our valid check.
|
|
//
|
|
if( !_ahNotifyArea[0] ){
|
|
return;
|
|
}
|
|
|
|
hThread = CreateThread( NULL,
|
|
0,
|
|
(LPTHREAD_START_ROUTINE)vRun,
|
|
this,
|
|
0,
|
|
&dwIgnore );
|
|
|
|
if( !hThread ){
|
|
goto Fail;
|
|
}
|
|
|
|
CloseHandle( hThread );
|
|
|
|
//
|
|
// Add ourselves to the linked list.
|
|
//
|
|
pNotify->CSGuard.Wait_vAdd( this );
|
|
return;
|
|
|
|
Fail:
|
|
//
|
|
// _ahNotifyArea[0] is our valid check, so clear it here.
|
|
//
|
|
CloseHandle( _ahNotifyArea[0] );
|
|
_ahNotifyArea[0] = NULL;
|
|
}
|
|
|
|
|
|
TNotify::
|
|
TWait::
|
|
~TWait(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Destory TWait.
|
|
Should be called once everything has been unregistered.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
|
|
{
|
|
#if DBG
|
|
if( gpbtNotify ){
|
|
gpbtNotify->pvCapture( 0x2f, (DWORD)this, (DWORD)this->pNotify( ));
|
|
}
|
|
#endif
|
|
{
|
|
TCritSecLock CSL( pNotify()->_CritSec );
|
|
|
|
SPLASSERT( !_cNotifyWork );
|
|
|
|
if ( _ahNotifyArea[0] ) {
|
|
|
|
//
|
|
// If we were valid, delink ourselves.
|
|
//
|
|
SPLASSERT( Wait_bLinked( ));
|
|
Wait_vDelinkSelf();
|
|
|
|
CloseHandle( _ahNotifyArea[0] );
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID
|
|
TNotify::
|
|
TWait::
|
|
vProcessOperation(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Process registration/unregistration of handles.
|
|
Note: doesn't reset cAwake, so callee must reset.
|
|
|
|
This routine MUST be called when someone else is holding the
|
|
critical section, since we access data here. This routine will
|
|
be called when another thread needs to register/unregister, so
|
|
it grabs the critical section, sets the correct state, then sets
|
|
the event which calls us. When we are done, we set another event
|
|
and the calling thread releases the critical section.
|
|
|
|
This routine doesn't not move items across the cAwake boundary, so
|
|
the callee must reset and watch all events (e.g., we may unregister
|
|
an event that was awake, and we replace it with one that wasn't).
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
|
|
{
|
|
MNotifyWork* pNotifyWork = pNotify()->CSGuard._pNotifyWork;
|
|
|
|
//
|
|
// Switch based on operation.
|
|
//
|
|
switch( pNotify()->CSGuard._eOperation ){
|
|
case kEopRegister:
|
|
|
|
//
|
|
// Add it to our list.
|
|
//
|
|
SPLASSERT( _cNotifyWork < kNotifyWorkMax );
|
|
|
|
DBGMSG( DBG_NOTIFY,
|
|
( "Wait.vProcessOperation: %x Register %x (%d)\n",
|
|
pNotify(), this, _cNotifyWork ));
|
|
|
|
//
|
|
// Add to the list by putting it at the end.
|
|
//
|
|
phNotifys()[_cNotifyWork] = pNotifyWork->hEvent();
|
|
_apNotifyWork[_cNotifyWork] = pNotifyWork;
|
|
++_cNotifyWork;
|
|
|
|
//
|
|
// Update pNotifyWork.
|
|
//
|
|
pNotifyWork->_pWait = this;
|
|
|
|
break;
|
|
|
|
case kEopModify:
|
|
case kEopUnregister:
|
|
|
|
UINT i = 0;
|
|
|
|
//
|
|
// Find the index of the MNotifyWork in TWait.
|
|
//
|
|
for( i = 0; i< _cNotifyWork; ++i ){
|
|
if( _apNotifyWork[i] == pNotifyWork ){
|
|
break;
|
|
}
|
|
}
|
|
|
|
DBGMSG( DBG_NOTIFY,
|
|
( "Wait.bNotify: %x update hEvent on index %d %x (%d)\n",
|
|
this, i, phNotifys()[i], _cNotifyWork ));
|
|
|
|
SPLASSERT( i != _cNotifyWork );
|
|
|
|
if( pNotify()->CSGuard._eOperation == kEopModify ){
|
|
|
|
//
|
|
// Update hEvent only. This is necessary because the client
|
|
// may have very quickly deleted and re-added with a new
|
|
// event. In this case, we must do the update.
|
|
//
|
|
phNotifys()[i] = pNotifyWork->hEvent();
|
|
|
|
} else {
|
|
|
|
//
|
|
// We are going to reset after this anyway, so don't
|
|
// worry about moving items across the cAwake
|
|
// boundary.
|
|
//
|
|
|
|
//
|
|
// Fill in the last element into the hole.
|
|
//
|
|
DBGMSG( DBG_NOTIFY,
|
|
( "Wait.vProcessOperation: %x removing index %d %x (%d)\n",
|
|
this, i, phNotifys()[i], _cNotifyWork ));
|
|
|
|
//
|
|
// Predecrement since array is zero-based.
|
|
//
|
|
--_cNotifyWork;
|
|
|
|
phNotifys()[i] = phNotifys()[_cNotifyWork];
|
|
_apNotifyWork[i] = _apNotifyWork[_cNotifyWork];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
TNotify::
|
|
TWait::
|
|
vRun(
|
|
TWait* pWait
|
|
)
|
|
{
|
|
DWORD dwWait;
|
|
DWORD cObjects;
|
|
DWORD cAwake;
|
|
DWORD dwTimeout = INFINITE;
|
|
MNotifyWork* pNotifyWork;
|
|
TNotify* pNotify = pWait->pNotify();
|
|
|
|
#if DBG
|
|
if( gpbtNotify ){
|
|
gpbtNotify->pvCapture( 0x21, (DWORD)pWait );
|
|
}
|
|
#endif
|
|
|
|
DBGMSG( DBG_NOTIFY, ( "Wait.vRun start %x\n", pWait ));
|
|
|
|
Reset:
|
|
|
|
cAwake =
|
|
cObjects = pWait->_cNotifyWork;
|
|
|
|
dwTimeout = INFINITE;
|
|
|
|
for( ; ; ){
|
|
|
|
DBGMSG( DBG_NOTIFY,
|
|
( "WaitForMultpleObjects: 1+%d %x timeout %d cObjects %d\n"
|
|
"%x %x %x %x %x %x %x %x %x\n"
|
|
"%x %x %x %x %x %x %x %x %x\n",
|
|
cAwake,
|
|
pNotify,
|
|
dwTimeout,
|
|
cObjects,
|
|
pWait->_apNotifyWork[0],
|
|
pWait->_apNotifyWork[1],
|
|
pWait->_apNotifyWork[2],
|
|
pWait->_apNotifyWork[3],
|
|
pWait->_apNotifyWork[4],
|
|
pWait->_apNotifyWork[5],
|
|
pWait->_apNotifyWork[6],
|
|
pWait->_apNotifyWork[7],
|
|
pWait->_apNotifyWork[8],
|
|
pWait->phNotifys()[0],
|
|
pWait->phNotifys()[1],
|
|
pWait->phNotifys()[2],
|
|
pWait->phNotifys()[3],
|
|
pWait->phNotifys()[4],
|
|
pWait->phNotifys()[5],
|
|
pWait->phNotifys()[6],
|
|
pWait->phNotifys()[7],
|
|
pWait->phNotifys()[8] ));
|
|
|
|
//
|
|
// Wait on multiple NotifyWork notification handles.
|
|
// (Including our trigger event.)
|
|
//
|
|
dwWait = WaitForMultipleObjects(
|
|
cAwake + 1,
|
|
pWait->_ahNotifyArea,
|
|
FALSE,
|
|
dwTimeout );
|
|
|
|
if( dwWait == WAIT_TIMEOUT ){
|
|
|
|
DBGMSG( DBG_NOTIFY,
|
|
( "Notify.vRun: %x TIMEOUT, cAwake old %d cObjects %d (%d)\n",
|
|
pNotify, cAwake, cObjects, pWait->_cNotifyWork ));
|
|
|
|
//
|
|
// We had a notification recently, but we didn't want to spin,
|
|
// so we took it out of the list. Add everyone back in.
|
|
//
|
|
goto Reset;
|
|
}
|
|
|
|
if( dwWait == WAIT_OBJECT_0 ){
|
|
|
|
//
|
|
// If we are marked as pending deletion, delete everything now.
|
|
//
|
|
if( pNotify->CSGuard._bDelete ){
|
|
|
|
SPLASSERT( !pWait->_cNotifyWork );
|
|
delete pWait;
|
|
|
|
DBGMSG( DBG_NOTIFY, ( "Wait.vRun delete %x\n", pWait ));
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// We have a genuine register event. The thread that triggered
|
|
// this event holds the critical section, so we don't need to
|
|
// grab it. When we set _hEventProcessed, the requester will
|
|
// release it.
|
|
//
|
|
pWait->vProcessOperation();
|
|
|
|
SetEvent( pNotify->_hEventProcessed );
|
|
|
|
DBGMSG( DBG_NOTIFY,
|
|
( "Notify.vRun: %x ProcessOperation, cAwake old %d cObjects %d (%d)\n",
|
|
pNotify, cAwake, cObjects, pWait->_cNotifyWork ));
|
|
|
|
goto Reset;
|
|
}
|
|
|
|
if( dwWait >= WAIT_OBJECT_0 &&
|
|
dwWait < WAIT_OBJECT_0 + cObjects + 1 ){
|
|
|
|
dwWait -= WAIT_OBJECT_0;
|
|
|
|
SPLASSERT( dwWait != 0 );
|
|
|
|
//
|
|
// dwWait is one over the number of the item that triggered
|
|
// since we put our own handle in slot 0.
|
|
//
|
|
--dwWait;
|
|
|
|
//
|
|
// NotifyWork has notification, process it.
|
|
//
|
|
pWait->_apNotifyWork[dwWait]->vProcessNotifyWork( pNotify );
|
|
|
|
//
|
|
// Once we handle a notification on a NotifyWork, we
|
|
// don't want to watch the handle immediately again,
|
|
// since we may spin in a tight loop getting notifications.
|
|
// Instead, ignore the handle and sleep for a bit.
|
|
//
|
|
if( dwTimeout == INFINITE ){
|
|
dwTimeout = pNotify->_dwSleepTime;
|
|
}
|
|
|
|
DBGMSG( DBG_NOTIFY,
|
|
( "Wait.vRun: %x index going to sleep %d work %x handle %x (%d)\n",
|
|
pWait, dwWait,
|
|
pWait->_apNotifyWork[dwWait],
|
|
pWait->phNotifys()[dwWait],
|
|
pWait->_cNotifyWork ));
|
|
|
|
//
|
|
// Swap it to the end so that we can decrement
|
|
// cObjects and not watch it for a while.
|
|
//
|
|
HANDLE hNotify = pWait->phNotifys()[dwWait];
|
|
pNotifyWork = pWait->_apNotifyWork[dwWait];
|
|
|
|
//
|
|
// Another one has gone to sleep. Decrement it now.
|
|
//
|
|
--cAwake;
|
|
|
|
//
|
|
// Now swap the element that needs to sleep with the
|
|
// element that's at the end of the list.
|
|
//
|
|
pWait->phNotifys()[dwWait] = pWait->phNotifys()[cAwake];
|
|
pWait->_apNotifyWork[dwWait] = pWait->_apNotifyWork[cAwake];
|
|
|
|
pWait->phNotifys()[cAwake] = hNotify;
|
|
pWait->_apNotifyWork[cAwake] = pNotifyWork;
|
|
|
|
} else {
|
|
|
|
DBGMSG( DBG_ERROR,
|
|
( "Notify.Run: WaitForMultipleObjects failed %x %d\n",
|
|
pNotify, GetLastError( )));
|
|
|
|
Sleep( pNotify->_dwSleepTime );
|
|
|
|
goto Reset;
|
|
}
|
|
}
|
|
|
|
SPLASSERT( FALSE );
|
|
}
|
|
|