WDF object lifetime: WDFLOOKASIDE child vs WDFMEMORY (siblings under same parent)

I’m trying to clarify WDF object-lifetime ordering when WDFLOOKASIDE, its child object, and WDFMEMORY allocations are involved.

In my driver, I create a WDFLOOKASIDE where both LookasideAttributes.ParentObject and MemoryAttributes.ParentObject point to the same parent WDFOBJECT. This means:

  • The WDFLOOKASIDE is a child of the parent object.

  • Any WDFMEMORY allocated via WdfMemoryCreateFromLookaside is also a child of that same parent (i.e., sibling to the lookaside).

  • Separately, I create another child WDFOBJECT whose parent is the lookaside itself (so the lookaside is its parent).

So the hierarchy is roughly:

ParentObject
 ├── WDFLOOKASIDE
 │    └── ChildWdfObject
 └── WDFMEMORY (multiple instances)

I’m trying to understand what happens when the parent object is deleted (either explicitly with WdfObjectDelete, or implicitly at driver unload).


:one: Ordering question #1

Is there a framework guarantee that the ChildWdfObject’s EvtDestroyCallback (the one parented to the lookaside) will be invoked before the destruction of the sibling WDFMEMORY objects that were allocated from the lookaside?


:two: Ordering question #2

Does WDF perform deletion in distinct phases over the object tree:

  • Phase 1: invoke EvtCleanupCallback bottom-up across the tree

  • Phase 2: after the root cleanup completes, invoke EvtDestroyCallback bottom-up

In other words, is it guaranteed that the ChildWdfObject’s EvtCleanupCallback will run before any EvtDestroyCallback for the sibling WDFMEMORY objects?

The documentation here is a bit ambiguous:

“…the framework calls the child object's EvtCleanupCallback callback functions before calling the parent object's EvtCleanupCallback callback function. Next, if the child's reference count is zero, the framework calls the child object's EvtDestroyCallback callback function…”

What’s unclear is whether “next” means:

  • the child’s EvtDestroyCallback runs immediately after the child’s EvtCleanupCallback,
    or

  • the child’s EvtDestroyCallback runs only after all of its parent objects’ EvtCleanupCallback up to the root, have completed.

I’m trying to determine whether relying on this ordering is documented and safe, or whether the behavior should be considered undefined.

Thanks for any insight.

According to Developing Drivers with WDF - Reference Book, the framework does call phase 1, then calls EvtDestroyCallback in any order for each node.

If I may step back for a moment, any design that RELIES on that ordering is badly flawed. When individual items are deleted, you have to handle EvtCleanupCallback and EvtDestroyCallback while the rest of the list survives, right?

1 Like

It’s confusing that the documentation says “Object deletion starts from the object farthest from the parent and works up the object hierarchy toward the root.” (from://learn.microsoft.com/en-us/windows-hardware/drivers/wdf/framework-object-life-cycle#deleting-a-framework-object).
Maybe the destruction phase also proceeds from the object farthest from the parent up toward the root when all object reference counts are zero.

If you’re referring to ordering question #2: assuming a design where a request object is not set as the parent of any object that requires cleanup at PASSIVE_LEVEL, then what is the purpose of WDF_OBJECT_ATTRIBUTES.ParentObject if not to provide ordering, at least during the cleanup phase?

The WDK reference book states:
The framework calls the destroy function after the object's reference count reaches zero and after the cleanup callbacks of the object, its parent, and any further ancestors-grandparents and so forth-have returned.”

I think the language there tells you that the ordering is guaranteed. What I was trying poorly to say is that it is extremely difficult for me to imagine a kernel situation where an object requires BOTH a cleanup callback and a destroy callback. Circumstances almost always dictate which one you need, but not both.

That’s all I was trying to say. If you have a design that needs both, and also depends on the interplay of those two callbacks among a group of objects, then your design is too complicated for a kernel driver.

1 Like

relative order of destroy() wrt object hierarchy is not guaranteed. destroy() is called when the ref count goes to zero, so dangling references can easily come into play and change the lifetime of one object relative to the others.

2 Likes

As far as I understand, destroy() order isn’t guaranteed, but cleanup() order is guaranteed according to both the WDF reference book and Microsoft’s documentation.

So let me restate my scenario, with a small clarification.

Hierarchy:

ParentWdfObject
 ├── WDFLOOKASIDE
 │    └── ChildWdfObject
 └── WDFMEMORY (multiple instances)

ChildWdfObject has WDFLOOKASIDE as its parent because it allocates memory from that lookaside. The intended design is that when ChildWdfObject cleanup runs, that’s the last opportunity to use both the WDFLOOKASIDE and the WDFMEMORY objects it created.

The problem is that the WDFMEMORY objects are siblings of the WDFLOOKASIDE. That means their cleanup can occur before cleanup runs on the WDFLOOKASIDE subtree.

From the EvtCleanupCallback documentation:

“After the framework calls an object's EvtCleanupCallback, the driver can access the object only from its EvtDestroyCallback.”

This implies the driver might still access those WDFMEMORY objects in other contexts, so ChildWdfObject cleanup is not a reliable signal that it is safe to stop using them.

My questions:

  1. Is there any guarantee, specifically for WDFLOOKASIDE, that the framework will delay cleanup of the sibling WDFMEMORY objects until cleanup completes for the whole lookaside subtree?

  2. Alternatively, is there any guarantee that cleanup traversal among siblings follows creation order?
    If such an order exists, then since the WDFLOOKASIDE is created first, its subtree cleanup would complete before cleanup of the WDFMEMORY objects which were created later.

  3. If neither guarantee exists, is my only option to register an EvtCleanupCallback on every WDFMEMORY allocated from the lookaside and treat that as the signal for the whole lookaside subtree to stop using it?
    That approach might work, but it noticeably complicates the driver.

So, the ChildWdfObject is not PART of the lookaside, but has the lookaside as its parent, and the WDFMEMORY objects are allocated BY the ChildWdfObject FROM the lookaside? That does seem like a bit of a tangled web.

Even so, this should be under your control. Things don’t get deleted out of the blue. As long as YOU delete the ChildWdfObject before the lookaside list gets deleted, and the child object deletes the WDFMEMORY objects, there’s no problem. Right? Presumably, your code that deletes the ChildWdfObject won’t run until you know no one else is using the WDFMEMORY.

1 Like

That does seem like a bit of a tangled web

I actually think this is the right design for WDF drivers that rely on WDFLOOKASIDE. Structuring it this way means anything using the lookaside list is notified in its cleanup callback when the list is about to be torn down, giving it a clear point to stop referencing it - a nice bit of encapsulation. If you remove that dependency and parent the ChildWdfObject higher up (to the WDFDEVICE or even the WDFDRIVER), the cleanup ordering becomes even harder to reason about, and you can end up with objects outliving the resources they depend on.

Even so, this should be under your control.

I’m not sure the cleanup order of the WDFMEMORY objects actually is under my control, and that’s really at the heart of my earlier questions.

Consider this scenario: ChildWdfObject owns a worker thread. That thread allocates from the lookaside list and holds references to the resulting WDFMEMORY objects. When cleanup starts, ChildWdfObject’s cleanup routine is responsible for stopping that thread. However, because the WDFMEMORY objects are siblings of WDFLOOKASIDE, they may get cleaned up first. That means the thread can still be running and trying to touch WDFMEMORY that has already been cleaned up.

The documentation for EvtCleanupCallback specifically says that after cleanup, only EvtDestroyCallback is allowed to access the object , yet in this scenario, the thread is still accessing the WDFMEMORY after cleanup has occurred. That’s exactly the ordering problem I’m trying to avoid.

How about you take an explicit wdf object reference on each wdfmemory used in the worker thread and release it on cleanup or when you are done using it? That extends the object lifetime. That doesn’t prevent cleanup() from being called on the referenced memory but I have to wonder how stateful a wdfmemory object is.

As you mentioned, taking a reference delays destruction but does not prevent cleanup, and I also wonder whether WDFMEMORY actually does anything meaningful during its cleanup.

I presented a simplified scenario, but it could involve two threads: one thread can add the WDFMEMORY objects to a collection for another thread to process. One of the advantages of the WDF object hierarchy is that any leftover WDFMEMORY in such a collection is automatically freed by the framework. If I increase the reference count myself, I would then need to implement manual cleanup for that collection.