In part 5 of this unnecessarily long series on agile delegates, commenter LB asked, “Is the ContextÂCallback in the deleter guaranteed to always succeed? According to the docs it can fail. I wonder if there’s a way to move the fallible part to an earlier point so the deleter can be infallible.”
Let’s look at the first part: What if IContextÂCallback:: fails?
If it fails, it means that COM couldn’t switch to the destination context.
If you can’t switch to the destination context, then you can’t release the pointer. It’s not clear what recovery is possible anyway. Do you just keep retrying until it finally works?
If the destination context is an ASTA, then it’s possible that the reason is that the context is already busy, and ASTA doesn’t allow re-entrancy. We’d have to wait a little bit and try again later, when the destination context might be ready. We can’t just block on the retry because the destination context might be calling into the thread we are on right now, so just spinning in a retry loop won’t help because it’s waiting for us! We’re have to return, allow whatever we’re doing to finish, which in turn allows the ASTA to resume, and then it becomes worthwhile to try to call into the ASTA again.
This would be the issue for conventional COM calls into the ASTA, but we are using IContextCallback, and that lets us control whether or not to honor ASTA reentrancy roadblocks.
If riid is set to IID_ICallbackWithNoReentrancyToApplicationSTA, the function does not reenter an ASTA arbitrarily.
We are not passing that special value, so our call to ContextÂCallback is allowed to reenter an ASTA. That removes one possible source of failure.
What other reasons could there be for not being able to switch to the destination apartment?
The most likely reason is that the destination apartment no longer exists, in which case there is no recovery. Depending on how the object was managed by its creating thread, it might have been forcibly destroyed at thread termination¹, or it may simply have been leaked. We don’t know. At any rate, there’s no way to release it now.
The other case is that the destination apartment is not reachable due to a low-memory condition. We discussed earlier how the most common reason is a destination thread that has stopped responding to messages. I guess you could wait and try again later, but in practice if a thread has stopped responding for so long that its inbound message queue is full, the odds that it will magically start responding soon are pretty low.
All of the failures are effectively unrecoverable. But some of them are non-fatal, such as the CoÂDisconnectÂObject discussed in the footnote. Unfortunately, we can’t tell what case we are in. The ContextÂCallback returns RPC_E_DISCONNECTED to say that the destination apartment no longer exists, but we don’t know how that apartment cleaned up its orphaned objects.
The C++/CX implementation of lazy-created agile delegates ignores errors that occur trying to release the original pointer. So we’ll do the same.
But wait, we can do better. Next time.
¹ This is often combined with a CoÂDisconnectÂObject to tell proxies to fail all calls with RPC_E_DISCONNECTED, so that there are no external references to destroyed objects.
0 comments
Be the first to start the discussion.