mirror of
https://github.com/reactos/reactos.git
synced 2025-05-06 18:31:26 +00:00
1217 lines
32 KiB
C++
1217 lines
32 KiB
C++
/*++
|
|
|
|
Copyright (c) Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
FxObjectStateMachine.cpp
|
|
|
|
Abstract:
|
|
|
|
This module contains the implementation of the base object's state machine.
|
|
|
|
Author:
|
|
|
|
|
|
|
|
|
|
|
|
Environment:
|
|
|
|
Both kernel and user mode
|
|
|
|
Revision History:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--*/
|
|
|
|
#include "fxobjectpch.hpp"
|
|
|
|
extern "C" {
|
|
|
|
#if defined(EVENT_TRACING)
|
|
#include "FxObjectStateMachine.tmh"
|
|
#endif
|
|
|
|
}
|
|
|
|
VOID
|
|
FxObject::DeleteObject(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This is a public method that is called on an object to request that it Delete.
|
|
|
|
Arguments:
|
|
None
|
|
|
|
Returns:
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
KIRQL oldIrql;
|
|
BOOLEAN result;
|
|
|
|
m_SpinLock.Acquire(&oldIrql);
|
|
|
|
result = MarkDeleteCalledLocked();
|
|
|
|
// This method should only be called once per object
|
|
ASSERT(result);
|
|
UNREFERENCED_PARAMETER(result); //for fre build
|
|
|
|
//
|
|
// Perform the right action based on the objects current state
|
|
//
|
|
switch(m_ObjectState) {
|
|
case FxObjectStateCreated:
|
|
//
|
|
// If we have a parent object, notify it of our deletion
|
|
//
|
|
if (m_ParentObject != NULL) {
|
|
//
|
|
// We call this holding our spinlock, the hierachy is child->parent
|
|
// when the lock is held across calls
|
|
//
|
|
status = m_ParentObject->RemoveChildObjectInternal(this);
|
|
|
|
if (status == STATUS_DELETE_PENDING) {
|
|
|
|
//
|
|
// We won the race to ourselves (still FxObjectStateCreated),
|
|
// but lost the race on the parent who is going to try and
|
|
// dispose us through the ParentDeleteEvent().
|
|
//
|
|
// This is OK since the state machine protects us from
|
|
// doing improper actions, but we must not rundown and
|
|
// release our reference count till the parent object
|
|
// eventually calls our ParentDeleteEvent().
|
|
//
|
|
// So we note the state, and return waiting for the
|
|
// parent to dispose us.
|
|
//
|
|
|
|
//
|
|
// Wait for our parent to come in and dispose us through
|
|
// the ParentDeleteEvent().
|
|
//
|
|
SetObjectStateLocked(FxObjectStateWaitingForEarlyDispose);
|
|
m_SpinLock.Release(oldIrql);
|
|
break;
|
|
}
|
|
else {
|
|
//
|
|
// We no longer have a parent object
|
|
//
|
|
m_ParentObject = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Start Dispose, do delete state machine
|
|
// returns with m_SpinLock released
|
|
//
|
|
DeleteWorkerAndUnlock(oldIrql, TRUE);
|
|
break;
|
|
|
|
case FxObjectStateDisposed:
|
|
|
|
if (m_ParentObject != NULL) {
|
|
status = m_ParentObject->RemoveChildObjectInternal(this);
|
|
|
|
if (status == STATUS_DELETE_PENDING) {
|
|
SetObjectStateLocked(FxObjectStateWaitingForParentDeleteAndDisposed);
|
|
m_SpinLock.Release(oldIrql);
|
|
break;
|
|
}
|
|
else {
|
|
//
|
|
// We no longer have a parent object
|
|
//
|
|
m_ParentObject = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// This will release the spinlock
|
|
//
|
|
DeletedAndDisposedWorkerLocked(oldIrql);
|
|
break;
|
|
|
|
case FxObjectStateDisposingDisposeChildren:
|
|
case FxObjectStateWaitingForEarlyDispose:
|
|
case FxObjectStateDeletedDisposing: // Do nothing, workitem will move into disposed and deleted
|
|
case FxObjectStateDeletedAndDisposed: // Do nothing, already deleted
|
|
TraceDroppedEvent(FxObjectDroppedEventDeleteObject);
|
|
m_SpinLock.Release(oldIrql);
|
|
break;
|
|
|
|
// These are bad states for this event
|
|
case FxObjectStateInvalid:
|
|
case FxObjectStateDestroyed:
|
|
case FxObjectStateWaitingForParentDeleteAndDisposed:
|
|
default:
|
|
TraceDroppedEvent(FxObjectDroppedEventDeleteObject);
|
|
// Bad state
|
|
ASSERT(FALSE);
|
|
m_SpinLock.Release(oldIrql);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
FxObject::DeleteEarlyDisposedObject(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Deletes an object which has already been explicitly early disposed.
|
|
|
|
Arguments:
|
|
None
|
|
|
|
Return Value:
|
|
None
|
|
|
|
--*/
|
|
{
|
|
BOOLEAN result;
|
|
|
|
ASSERT(m_ObjectFlags & FXOBJECT_FLAGS_EARLY_DISPOSED_EXT);
|
|
ASSERT(m_ObjectState == FxObjectStateDisposed);
|
|
|
|
result = MarkDeleteCalledLocked();
|
|
ASSERT(result);
|
|
UNREFERENCED_PARAMETER(result); //for fre build
|
|
|
|
if (m_ParentObject != NULL) {
|
|
NTSTATUS status;
|
|
KIRQL irql;
|
|
|
|
m_SpinLock.Acquire(&irql);
|
|
|
|
if (m_ParentObject != NULL) {
|
|
status = m_ParentObject->RemoveChildObjectInternal(this);
|
|
|
|
if (status == STATUS_DELETE_PENDING) {
|
|
SetObjectStateLocked(FxObjectStateWaitingForParentDeleteAndDisposed);
|
|
m_SpinLock.Release(irql);
|
|
return;
|
|
}
|
|
else {
|
|
//
|
|
// We no longer have a parent object
|
|
//
|
|
m_ParentObject = NULL;
|
|
}
|
|
}
|
|
|
|
m_SpinLock.Release(irql);
|
|
}
|
|
|
|
//
|
|
// This will release the spinlock
|
|
//
|
|
DeletedAndDisposedWorkerLocked(PASSIVE_LEVEL, FALSE);
|
|
}
|
|
|
|
BOOLEAN
|
|
FxObject::Dispose(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This is a virtual function overriden by sub-classes if they want
|
|
Dispose notifications.
|
|
|
|
Arguments:
|
|
None
|
|
|
|
Returns:
|
|
TRUE if the registered cleanup routines on this object should be called
|
|
when this funciton returns
|
|
|
|
--*/
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
VOID
|
|
FxObject::ProcessDestroy(
|
|
VOID
|
|
)
|
|
{
|
|
FxTagTracker* pTagTracker;
|
|
|
|
//
|
|
// Set the debug info to NULL so that we don't use it after the
|
|
// SelfDestruct call. Setting the DestroyFunction to NULL
|
|
// will also prevent reuse of the REF_OBJ after it has been destroyed.
|
|
//
|
|
pTagTracker = GetTagTracker();
|
|
|
|
//
|
|
// We will free debug info later. It useful to hang on to the debug
|
|
// info after the destructor has been called for debugging purposes.
|
|
//
|
|
if (pTagTracker != NULL) {
|
|
pTagTracker->CheckForAbandondedTags();
|
|
}
|
|
|
|
//
|
|
// Call the destroy callback *before* any desctructor is called. This
|
|
// way the callback has access to a full fledged object that hasn't been
|
|
// cleaned up yet.
|
|
//
|
|
// We only do this for committed objects. A non committed object will
|
|
// *NOT* have additional contexts to free.
|
|
//
|
|
if (m_ObjectSize > 0 && IsCommitted()) {
|
|
FxContextHeader* pHeader, *pNext;
|
|
WDFOBJECT h;
|
|
BOOLEAN first;
|
|
|
|
//
|
|
// We are getting the object handle when the ref count is zero. We
|
|
// don't want to ASSERT in this case.
|
|
//
|
|
h = GetObjectHandleUnchecked();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (pHeader = GetContextHeader();
|
|
pHeader != NULL;
|
|
pHeader = pHeader->NextHeader) {
|
|
|
|
//
|
|
// Cleanup *may* have been called earlier in the objects
|
|
// destruction, and in this case the EvtCleanupCallback will
|
|
// be set to NULL. Ensuring its always called provides
|
|
// symmetry to the framework.
|
|
//
|
|
|
|
//
|
|
// No need to interlockexchange out the value of
|
|
// EvtCleanupCallback because any codepath that might be calling
|
|
// CallCleanup must have a valid reference and no longer have
|
|
// any outstanding references
|
|
//
|
|
if (pHeader->EvtCleanupCallback != NULL) {
|
|
pHeader->EvtCleanupCallback(h);
|
|
pHeader->EvtCleanupCallback = NULL;
|
|
}
|
|
|
|
if (pHeader->EvtDestroyCallback != NULL) {
|
|
pHeader->EvtDestroyCallback(h);
|
|
pHeader->EvtDestroyCallback = NULL;
|
|
}
|
|
}
|
|
|
|
first = TRUE;
|
|
for (pHeader = GetContextHeader(); pHeader != NULL; pHeader = pNext) {
|
|
|
|
pNext = pHeader->NextHeader;
|
|
|
|
//
|
|
// The first header is embedded, so it will be freed with the
|
|
// object
|
|
//
|
|
if (first == FALSE) {
|
|
FxPoolFree(pHeader);
|
|
}
|
|
|
|
first = FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// NOTE: The delete of the tag tracker *MUST* occur before the SelfDestruct()
|
|
// of this object. The tag tracker has a back pointer to this object which
|
|
// it dereferences in its own destructor. If SelfDestruct() is called
|
|
// first, then ~FxTagTracker will touch freed pool and bugcheck.
|
|
//
|
|
if (pTagTracker != NULL) {
|
|
GetDebugExtension()->TagTracker = NULL;
|
|
delete pTagTracker;
|
|
}
|
|
|
|
//
|
|
// See NOTE above.
|
|
//
|
|
SelfDestruct();
|
|
}
|
|
|
|
BOOLEAN
|
|
FxObject::EarlyDispose(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Public early dipose functionality. Removes the object from the parent's
|
|
list of children. This assumes the caller or someone else will eventually
|
|
invoke DeleteObject() on this object.
|
|
|
|
Arguments:
|
|
None
|
|
|
|
Return Value:
|
|
BOOLEAN - same semantic as DisposeChildrenWorker.
|
|
TRUE - dispose of this object and its children occurred synchronously in
|
|
this call
|
|
FALSE - the dispose was pended to a work item
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
KIRQL oldIrql;
|
|
BOOLEAN result;
|
|
|
|
//
|
|
// By default, we assume a synchronous diposal
|
|
//
|
|
result = TRUE;
|
|
|
|
m_SpinLock.Acquire(&oldIrql);
|
|
|
|
switch(m_ObjectState) {
|
|
case FxObjectStateCreated:
|
|
//
|
|
// If we have a parent object, notify it of our deletion
|
|
//
|
|
if (m_ParentObject != NULL) {
|
|
//
|
|
// We call this holding our spinlock, the hierachy is child->parent
|
|
// when the lock is held across calls
|
|
//
|
|
status = m_ParentObject->RemoveChildObjectInternal(this);
|
|
|
|
if (status == STATUS_DELETE_PENDING) {
|
|
|
|
//
|
|
// We won the race to ourselves (still FxObjectStateCreated),
|
|
// but lost the race on the parent who is going to try and
|
|
// dispose us through the PerformEarlyDipose().
|
|
//
|
|
// This is OK since the state machine protects us from
|
|
// doing improper actions, but we must not rundown and
|
|
// release our reference count till the parent object
|
|
// eventually calls our ParentDeleteEvent().
|
|
//
|
|
// So we note the state, and return waiting for the
|
|
// parent to dispose us.
|
|
//
|
|
|
|
//
|
|
// Wait for our parent to come in and dispose us through
|
|
// the PerformEarlyDipose().
|
|
//
|
|
SetObjectStateLocked(FxObjectStateWaitingForEarlyDispose);
|
|
m_SpinLock.Release(oldIrql);
|
|
|
|
return FALSE;
|
|
}
|
|
else {
|
|
//
|
|
// We no longer have a parent object
|
|
//
|
|
m_ParentObject = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Mark that this object was early disposed externally wrt the
|
|
// state machine.
|
|
//
|
|
m_ObjectFlags |= FXOBJECT_FLAGS_EARLY_DISPOSED_EXT;
|
|
|
|
//
|
|
// Start the dispose path. This call will release the spinlock.
|
|
//
|
|
result = PerformEarlyDisposeWorkerAndUnlock(oldIrql, TRUE);
|
|
break;
|
|
|
|
default:
|
|
//
|
|
// Not in the right state for an early dispose
|
|
//
|
|
result = FALSE;
|
|
m_SpinLock.Release(oldIrql);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
BOOLEAN
|
|
FxObject::PerformEarlyDispose(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Allows Dispose() processing on an object to occur before calling DeleteObject().
|
|
|
|
Arguments:
|
|
CanDefer - if TRUE, can defer to a dispose list if IRQL requirements are
|
|
incorrect. If FALSE, the caller has guaranteed that we are at
|
|
the correct IRQL
|
|
|
|
Returns:
|
|
None
|
|
|
|
--*/
|
|
{
|
|
KIRQL oldIrql;
|
|
BOOLEAN result;
|
|
|
|
//
|
|
// By default we assume that the dispose was synchronous
|
|
//
|
|
result = TRUE;
|
|
|
|
|
|
//
|
|
// It's OK for an object to already be disposing due to
|
|
// a parent delete call.
|
|
//
|
|
// To check for verifier errors in which two calls to
|
|
// PerformEarlyDispose() occur, a separate flag is used
|
|
// rather than complicating the state machine.
|
|
//
|
|
m_SpinLock.Acquire(&oldIrql);
|
|
|
|
//
|
|
// Perform the right action based on the objects current state
|
|
//
|
|
switch(m_ObjectState) {
|
|
case FxObjectStateCreated:
|
|
//
|
|
// Start dispose, move into Disposing state
|
|
// returns with m_SpinLock released
|
|
//
|
|
result = PerformEarlyDisposeWorkerAndUnlock(oldIrql, FALSE);
|
|
break;
|
|
|
|
case FxObjectStateWaitingForEarlyDispose:
|
|
//
|
|
// Start the dispose path.
|
|
//
|
|
result = PerformEarlyDisposeWorkerAndUnlock(oldIrql, FALSE);
|
|
break;
|
|
|
|
case FxObjectStateDeferedDisposing:
|
|
//
|
|
// We should only get an early dispose in this state once we have thunked
|
|
// to passive level via the dispose list.
|
|
//
|
|
result = PerformDisposingDisposeChildrenLocked(oldIrql, FALSE);
|
|
break;
|
|
|
|
case FxObjectStateWaitingForParentDeleteAndDisposed: // Do nothing, parent object will delete and dispose
|
|
case FxObjectStateDisposed: // Do nothing
|
|
case FxObjectStateDisposingEarly: // Do nothing
|
|
case FxObjectStateDeletedDisposing: // Do nothing, workitem will moved into disposed
|
|
case FxObjectStateDeletedAndDisposed:
|
|
TraceDroppedEvent(FxObjectDroppedEventPerformEarlyDispose);
|
|
m_SpinLock.Release(oldIrql);
|
|
break;
|
|
|
|
// These are bad states for this event
|
|
case FxObjectStateInvalid:
|
|
case FxObjectStateDestroyed:
|
|
|
|
default:
|
|
TraceDroppedEvent(FxObjectDroppedEventPerformEarlyDispose);
|
|
// Bad state
|
|
ASSERT(FALSE);
|
|
m_SpinLock.Release(oldIrql);
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
_Must_inspect_result_
|
|
NTSTATUS
|
|
FxObject::RemoveParentAssignment(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Remove the current objects ParentObject.
|
|
|
|
Arguments:
|
|
None
|
|
|
|
Returns:
|
|
NTSTATUS of action
|
|
|
|
--*/
|
|
{
|
|
KIRQL oldIrql;
|
|
NTSTATUS status;
|
|
|
|
m_SpinLock.Acquire(&oldIrql);
|
|
|
|
//
|
|
// Object is already being deleted, this object will be removed as a child
|
|
// by the parents Dispose()
|
|
//
|
|
if (m_ObjectState != FxObjectStateCreated) {
|
|
TraceDroppedEvent(FxObjectDroppedEventRemoveParentAssignment);
|
|
m_SpinLock.Release(oldIrql);
|
|
return STATUS_DELETE_PENDING;
|
|
}
|
|
|
|
// We should have a parent
|
|
ASSERT(m_ParentObject != NULL);
|
|
|
|
status = m_ParentObject->RemoveChildObjectInternal(this);
|
|
if (NT_SUCCESS(status)) {
|
|
m_ParentObject = NULL;
|
|
}
|
|
|
|
m_SpinLock.Release(oldIrql);
|
|
|
|
return status;
|
|
}
|
|
|
|
VOID
|
|
FxObject::ParentDeleteEvent(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is invoked by the parent object when it is Dispose()'ing us.
|
|
|
|
Arguments:
|
|
None
|
|
|
|
Returns:
|
|
None
|
|
|
|
--*/
|
|
{
|
|
KIRQL oldIrql;
|
|
|
|
//
|
|
// Note: It's ok for an object to already be in the delete
|
|
// state since there can be an allowed race between
|
|
// parent disposing an object, and the DeleteObject()
|
|
// call on the object itself.
|
|
//
|
|
m_SpinLock.Acquire(&oldIrql);
|
|
|
|
//
|
|
// We no longer have a parent object
|
|
//
|
|
m_ParentObject = NULL;
|
|
|
|
//
|
|
// Perform the right action based on the objects current state
|
|
//
|
|
switch(m_ObjectState) {
|
|
case FxObjectStateWaitingForParentDeleteAndDisposed:
|
|
case FxObjectStateDisposed:
|
|
//
|
|
// This will release the spinlock
|
|
//
|
|
DeletedAndDisposedWorkerLocked(oldIrql, TRUE);
|
|
break;
|
|
|
|
case FxObjectStateDeletedDisposing:
|
|
//
|
|
// Do nothing, workitem will move into disposed
|
|
//
|
|
TraceDroppedEvent(FxObjectDroppedEventParentDeleteEvent);
|
|
m_SpinLock.Release(oldIrql);
|
|
break;
|
|
|
|
case FxObjectStateDeletedAndDisposed: // Do nothing, already deleted
|
|
m_SpinLock.Release(oldIrql);
|
|
break;
|
|
|
|
case FxObjectStateDisposingDisposeChildren:
|
|
//
|
|
// In the process of deleting, ignore it
|
|
//
|
|
m_SpinLock.Release(oldIrql);
|
|
break;
|
|
|
|
// These are bad states for this event
|
|
|
|
|
|
|
|
|
|
|
|
case FxObjectStateCreated:
|
|
case FxObjectStateWaitingForEarlyDispose:
|
|
|
|
case FxObjectStateInvalid:
|
|
case FxObjectStateDestroyed:
|
|
default:
|
|
//
|
|
// Bad state
|
|
//
|
|
ASSERT(FALSE);
|
|
m_SpinLock.Release(oldIrql);
|
|
break;
|
|
}
|
|
}
|
|
|
|
VOID
|
|
FxObject::DeferredDisposeWorkItem(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Invoked by deferred dispose workitem. This is invoked at PASSIVE_LEVEL,
|
|
and returns at PASSIVE_LEVEL
|
|
|
|
Arguments:
|
|
None
|
|
|
|
Return Value:
|
|
None
|
|
|
|
--*/
|
|
{
|
|
KIRQL oldIrql;
|
|
BOOLEAN result, destroy;
|
|
|
|
destroy = FALSE;
|
|
|
|
m_SpinLock.Acquire(&oldIrql);
|
|
|
|
ASSERT(oldIrql == PASSIVE_LEVEL);
|
|
|
|
//
|
|
// Perform the right action based on the objects current state
|
|
//
|
|
// DisposeChildrenWorker return result can be ignored because we are
|
|
// guaranteed to be calling it at PASSIVE.
|
|
//
|
|
switch (m_ObjectState) {
|
|
case FxObjectStateDeferedDisposing:
|
|
//
|
|
// This will drop the spinlock and move the object to the right state
|
|
// before returning.
|
|
//
|
|
result = PerformDisposingDisposeChildrenLocked(oldIrql, FALSE);
|
|
|
|
//
|
|
// The substree should never defer to the dispose list if we pass FALSE.
|
|
//
|
|
ASSERT(result);
|
|
UNREFERENCED_PARAMETER(result); //for fre build
|
|
|
|
return;
|
|
|
|
case FxObjectStateDeferedDeleting:
|
|
SetObjectStateLocked(FxObjectStateDeletedDisposing);
|
|
result = DisposeChildrenWorker(FxObjectStateDeferedDeleting, oldIrql, FALSE);
|
|
ASSERT(result);
|
|
UNREFERENCED_PARAMETER(result); //for fre build
|
|
|
|
//
|
|
// This will release the spinlock
|
|
//
|
|
DeletedAndDisposedWorkerLocked(oldIrql, FALSE);
|
|
return;
|
|
|
|
case FxObjectStateDeferedDestroy:
|
|
// Perform final destroy actions now that we are at passive level
|
|
destroy = TRUE;
|
|
break;
|
|
|
|
// These are bad states for this event
|
|
case FxObjectStateDeletedAndDisposed: // Do nothing
|
|
case FxObjectStateDisposed:
|
|
case FxObjectStateWaitingForParentDeleteAndDisposed: // Do nothing
|
|
case FxObjectStateCreated:
|
|
case FxObjectStateWaitingForEarlyDispose:
|
|
case FxObjectStateInvalid:
|
|
case FxObjectStateDestroyed:
|
|
|
|
default:
|
|
// Bad state
|
|
ASSERT(FALSE);
|
|
break;
|
|
}
|
|
|
|
m_SpinLock.Release(oldIrql);
|
|
|
|
if (destroy) {
|
|
ProcessDestroy();
|
|
}
|
|
}
|
|
|
|
_Releases_lock_(this->m_SpinLock.m_Lock)
|
|
__drv_requiresIRQL(DISPATCH_LEVEL)
|
|
BOOLEAN
|
|
FxObject::PerformDisposingDisposeChildrenLocked(
|
|
__in __drv_restoresIRQL KIRQL OldIrql,
|
|
__in BOOLEAN CanDefer
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This is entered with m_SpinLock held, and returns with it released.
|
|
|
|
Arguments:
|
|
OldIrql - the IRQL before m_SpinLock was acquired
|
|
|
|
CanDefer - if TRUE, can defer to a dispose list if IRQL requirements are
|
|
incorrect. If FALSE, the caller has guaranteed that we are at
|
|
the correct IRQL
|
|
|
|
Return Value:
|
|
BOOLEAN - same semantic as DisposeChildrenWorker.
|
|
TRUE - delete of this object and its children occurred synchronously in
|
|
this call
|
|
FALSE - the delete was pended to a work item
|
|
|
|
--*/
|
|
{
|
|
static const USHORT edFlags = (FXOBJECT_FLAGS_DELETECALLED |
|
|
FXOBJECT_FLAGS_EARLY_DISPOSED_EXT);
|
|
|
|
SetObjectStateLocked(FxObjectStateDisposingDisposeChildren);
|
|
|
|
if (DisposeChildrenWorker(FxObjectStateDeferedDisposing, OldIrql, CanDefer)) {
|
|
//
|
|
// Upon returning TRUE, the lock is still held
|
|
//
|
|
|
|
//
|
|
// If this object was early disposed externally, destroy the children
|
|
// immediately (FxRequest relies on this so that the WDFMEMORYs created
|
|
// for probed and locked buffers are freed before the request is
|
|
// completed.)
|
|
//
|
|
// Otherwise, wait for DeleteObject or ParentDeleteEvent() to occur.
|
|
//
|
|
if ((m_ObjectFlags & edFlags) == edFlags) {
|
|
//
|
|
// This will drop the lock
|
|
//
|
|
DeletedAndDisposedWorkerLocked(OldIrql, FALSE);
|
|
}
|
|
else {
|
|
//
|
|
// Will wait for the parent deleted event
|
|
//
|
|
SetObjectStateLocked(FxObjectStateDisposed);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
else {
|
|
//
|
|
// Upon return FALSE, the lock was released and a work item was
|
|
// queued to dispose of children at passive level
|
|
//
|
|
DO_NOTHING();
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
_Releases_lock_(this->m_SpinLock.m_Lock)
|
|
__drv_requiresIRQL(DISPATCH_LEVEL)
|
|
BOOLEAN
|
|
FxObject::PerformEarlyDisposeWorkerAndUnlock(
|
|
__in __drv_restoresIRQL KIRQL OldIrql,
|
|
__in BOOLEAN CanDefer
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This is entered with m_SpinLock held, and returns with it released.
|
|
|
|
Arguments:
|
|
OldIrql - the previous IRQL before the object lock was acquired
|
|
|
|
CanDefer - if TRUE, can defer to a dispose list if IRQL requirements are
|
|
incorrect. If FALSE, the caller has guaranteed that we are at
|
|
the correct IRQL
|
|
|
|
Return Value:
|
|
BOOLEAN - same semantic as DisposeChildrenWorker.
|
|
TRUE - delete of this object and its children occurred synchronously in
|
|
this call
|
|
FALSE - the delete was pended to a work item
|
|
|
|
--*/
|
|
{
|
|
ASSERT(m_ObjectState == FxObjectStateCreated ||
|
|
m_ObjectState == FxObjectStateWaitingForEarlyDispose);
|
|
|
|
SetObjectStateLocked(FxObjectStateDisposingEarly);
|
|
|
|
if (CanDefer && ShouldDeferDisposeLocked(&OldIrql)) {
|
|
QueueDeferredDisposeLocked(FxObjectStateDeferedDisposing);
|
|
m_SpinLock.Release(OldIrql);
|
|
|
|
return FALSE;
|
|
}
|
|
else {
|
|
return PerformDisposingDisposeChildrenLocked(OldIrql, CanDefer);
|
|
}
|
|
}
|
|
|
|
_Releases_lock_(this->m_SpinLock.m_Lock)
|
|
__drv_requiresIRQL(DISPATCH_LEVEL)
|
|
BOOLEAN
|
|
FxObject::DeleteWorkerAndUnlock(
|
|
__in __drv_restoresIRQL KIRQL OldIrql,
|
|
__in BOOLEAN CanDefer
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This is entered with m_SpinLock held, and returns with it released.
|
|
|
|
|
|
Arguments:
|
|
OldIrql - the IRQL before m_SpinLock was acquired
|
|
|
|
CanDefer - if TRUE, can defer to a dispose list if IRQL requirements are
|
|
incorrect. If FALSE, the caller has guaranteed that we are at
|
|
the correct IRQL
|
|
|
|
Return Value:
|
|
BOOLEAN - same semantic as DisposeChildrenWorker.
|
|
TRUE - delete of this object and its children occurred synchronously in
|
|
this call
|
|
FALSE - the delete was pended to a work item
|
|
|
|
--*/
|
|
{
|
|
ASSERT(m_ObjectState == FxObjectStateCreated);
|
|
// m_ObjectState == FxObjectStateWaitingForParentDelete);
|
|
|
|
if (CanDefer && ShouldDeferDisposeLocked(&OldIrql)) {
|
|
QueueDeferredDisposeLocked(FxObjectStateDeferedDeleting);
|
|
m_SpinLock.Release(OldIrql);
|
|
|
|
return FALSE;
|
|
}
|
|
else {
|
|
SetObjectStateLocked(FxObjectStateDeletedDisposing);
|
|
|
|
if (DisposeChildrenWorker(FxObjectStateDeferedDeleting, OldIrql, CanDefer)) {
|
|
//
|
|
// This will release the spinlock
|
|
//
|
|
DeletedAndDisposedWorkerLocked(OldIrql, FALSE);
|
|
|
|
return TRUE;
|
|
}
|
|
else {
|
|
//
|
|
// Upon return FALSE, the lock was released and a work item was
|
|
// queued to dispose of children at passive level
|
|
//
|
|
DO_NOTHING();
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID
|
|
FxObject::QueueDeferredDisposeLocked(
|
|
__in FxObjectState NewDeferedState
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Queues this object onto a work item list which will dispose it at passive
|
|
level. The work item will be owned by the parent device or driver.
|
|
|
|
This is called with the object's m_SpinLock held.
|
|
|
|
NOTE: This function only looks at this object and the parent to attempt to
|
|
find the owning FxDeviceBase*. If this is a deeper hierarchy, the deeply
|
|
rooted FxDeviceBase will not be used.
|
|
|
|
Arguments:
|
|
Parent - the parent of this objec (it may have already been removed from
|
|
m_ParentObject, so we can't use that field
|
|
|
|
Return Value:
|
|
None
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// Queue workitem, which will run DisposeChildrenWorker()
|
|
//
|
|
ASSERT(m_Globals != NULL);
|
|
ASSERT(m_Globals->Driver != NULL);
|
|
|
|
SetObjectStateLocked(NewDeferedState);
|
|
|
|
//FxToObjectItf::FxAddToDisposeList(m_DeviceBase, m_Globals, this);
|
|
if (m_DeviceBase != NULL) {
|
|
m_DeviceBase->AddToDisposeList(this);
|
|
}
|
|
else {
|
|
m_Globals->Driver->GetDisposeList()->Add(this);
|
|
}
|
|
}
|
|
|
|
_Releases_lock_(this->m_SpinLock.m_Lock)
|
|
__drv_requiresIRQL(DISPATCH_LEVEL)
|
|
BOOLEAN
|
|
FxObject::DisposeChildrenWorker(
|
|
__in FxObjectState NewDeferedState,
|
|
__in __drv_restoresIRQL KIRQL OldIrql,
|
|
__in BOOLEAN CanDefer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Rundown list of child objects removing their entries and invoking
|
|
their ParentDeleteEvent() on them.
|
|
|
|
This is called with the m_SpinLock held and upon returning the lock is
|
|
released.
|
|
|
|
Arguments:
|
|
NewDeferedState - If the state transition requires defering to a dispose
|
|
list, this is the new state to move to
|
|
|
|
OldIrql - the previous IRQL when the caller acquired the object's lock
|
|
|
|
CanDefer - if TRUE, can defer to a dispose list if IRQL requirements are
|
|
incorrect. If FALSE, the caller has guaranteed that we are at
|
|
the correct IRQL
|
|
|
|
Returns:
|
|
TRUE: Dispose is completed in this function.
|
|
FALSE: Dispose is deferred either to a workitem (if CanDefer is TRUE) or will
|
|
be done later in the current thread (if CanDefer is FALSE).
|
|
|
|
In either case lock is released.
|
|
|
|
If the OldIrql == PASSIVE_LEVEL, TRUE is guaranteed to be returned
|
|
|
|
Comments:
|
|
|
|
This routine is entered with the spinlock held, and may return with it released.
|
|
|
|
The state machine ensures that this is only invoked once in
|
|
an objects lifetime, regardless of races between PerformEarlyDispose,
|
|
DeleteObject, or a ParentDeleteEvent. If there are requirements for passive
|
|
level dispose and the previous IRQL is != PASSIVE, this function will be
|
|
called twice, the first at IRQL > PASSIVE, the second at PASSIVE.
|
|
|
|
Top level code has ensured this is invoked under the right IRQL level
|
|
for the object to perform the Dispose() callbacks.
|
|
|
|
--*/
|
|
{
|
|
LIST_ENTRY *ple;
|
|
FxObject* childObject;
|
|
|
|
//
|
|
// Called from:
|
|
//
|
|
// DeferredDisposeWorkItem (will complete, and release in current thread)
|
|
// PerformDisposeWorkerAndUnlock(PerformEarlyDispose), no release, but possible thread deferral
|
|
// DeleteWorkerAndUnlock (will release, but may defer, its logic must change!)
|
|
//
|
|
|
|
ASSERT((m_ObjectState == FxObjectStateDisposingDisposeChildren) ||
|
|
(m_ObjectState == FxObjectStateDeletedDisposing));
|
|
|
|
//
|
|
// This routine will attempt to dispose as many children as possible
|
|
// in the current thread. It may have to stop if the thread is
|
|
// not at PASSIVE_LEVEL when the object spinlock was acquired, and
|
|
// a child object is marked as a passive level object.
|
|
//
|
|
// If this occurs, dispose processing is suspended and resumed in
|
|
// a passive level workitem, which calls this routine back to
|
|
// complete the processing.
|
|
//
|
|
// Once all child object's Dispose() callback has returned, we then
|
|
// can call Dispose() on the parent object.
|
|
//
|
|
// This must be done in this order to guarantee the contract with the
|
|
// device driver (and internal object system) that all child
|
|
// EvtObjectCleanup callbacks have returned before their parents
|
|
// EvtObjectCleanup event.
|
|
//
|
|
// This is important to avoid extra references
|
|
// when child objects expect their parent object to be valid until
|
|
// EvtObjectCleanup is called.
|
|
//
|
|
|
|
// Rundown list removing entries and calling Dispose on child objects
|
|
|
|
//
|
|
// If this object requires being forced onto the dispose thread, do it now
|
|
//
|
|
if (IsForceDisposeThreadLocked() && OldIrql != PASSIVE_LEVEL) {
|
|
//
|
|
// Workitem will re-run this function at PASSIVE_LEVEL
|
|
//
|
|
if (CanDefer) {
|
|
QueueDeferredDisposeLocked(NewDeferedState);
|
|
}
|
|
else {
|
|
SetObjectStateLocked(NewDeferedState);
|
|
}
|
|
|
|
m_SpinLock.Release(OldIrql);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
for (ple = m_ChildListHead.Flink;
|
|
ple != &m_ChildListHead;
|
|
ple = ple->Flink) {
|
|
//
|
|
// Before removing the child object, we need to see if we need to defer
|
|
// to a work item to dispose the child.
|
|
//
|
|
childObject = CONTAINING_RECORD(ple, FxObject, m_ChildEntry);
|
|
|
|
//
|
|
// Should not associate with self
|
|
//
|
|
ASSERT(childObject != this);
|
|
|
|
//
|
|
// If current threads IRQL before acquiring the spinlock is not
|
|
// passive, and the child object is passive constrained, we must
|
|
// defer the current disposal processing to a workitem.
|
|
//
|
|
// We stay in the Disposing state, which this routine will continue
|
|
// processing when called back from the workitem.
|
|
//
|
|
// This code is re-entered at the proper passive_level to complete
|
|
// processing where it left off (at the head of the m_ChildListHead).
|
|
//
|
|
if (OldIrql != PASSIVE_LEVEL && childObject->IsPassiveDisposeLocked()) {
|
|
//
|
|
// Workitem will re-run this function at PASSIVE_LEVEL
|
|
//
|
|
if (CanDefer) {
|
|
QueueDeferredDisposeLocked(NewDeferedState);
|
|
}
|
|
else {
|
|
SetObjectStateLocked(NewDeferedState);
|
|
}
|
|
m_SpinLock.Release(OldIrql);
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
m_SpinLock.Release(OldIrql);
|
|
|
|
for (ple = m_ChildListHead.Flink; ple != &m_ChildListHead; ple = ple->Flink) {
|
|
childObject = CONTAINING_RECORD(ple, FxObject, m_ChildEntry);
|
|
|
|
//
|
|
// Inform child object of disposal. We will release the reference on
|
|
// the child only after we have disposed ourself.
|
|
//
|
|
if (childObject->PerformEarlyDispose() == FALSE) {
|
|
|
|
m_SpinLock.Acquire(&OldIrql);
|
|
if (CanDefer) {
|
|
QueueDeferredDisposeLocked(NewDeferedState);
|
|
}
|
|
else {
|
|
SetObjectStateLocked(NewDeferedState);
|
|
}
|
|
m_SpinLock.Release(OldIrql);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
ASSERT(childObject->GetRefCnt() > 0);
|
|
}
|
|
|
|
//
|
|
// Call Dispose virtual callback on ourselves for benefit
|
|
// of sub-classes if it is overridden.
|
|
//
|
|
if ((m_ObjectFlags & FXOBJECT_FLAGS_DISPOSE_OVERRIDE) == 0x00 || Dispose()) {
|
|
//
|
|
// Now call Cleanup on any handle context's exposed
|
|
// to the device driver.
|
|
//
|
|
CallCleanup();
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Despite the name this function may not always be called with lock held
|
|
// but if Unlock is TRUE, lock must be held.
|
|
//
|
|
_When_(Unlock, _Releases_lock_(this->m_SpinLock.m_Lock))
|
|
__drv_when(Unlock, __drv_requiresIRQL(DISPATCH_LEVEL))
|
|
VOID
|
|
FxObject::DeletedAndDisposedWorkerLocked(
|
|
__in __drv_when(Unlock, __drv_restoresIRQL) KIRQL OldIrql,
|
|
__in BOOLEAN Unlock
|
|
)
|
|
{
|
|
SetObjectStateLocked(FxObjectStateDeletedAndDisposed);
|
|
|
|
if (Unlock) {
|
|
m_SpinLock.Release(OldIrql);
|
|
}
|
|
|
|
DestroyChildren();
|
|
|
|
//
|
|
// Release the final reference on the object
|
|
//
|
|
RELEASE(NULL);
|
|
}
|
|
|