2020-09-30 17:12:29 +02:00

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 );
}