0

I have an unmanaged class that is running a message loop for a child Win32 window. When the program goes to close, it starts the finalizer for the managed class that holds the unmanaged reference to this class. Because another thread is dependent on this class, I need the finalizer to wait until the message loop thread has completed a loop and exits and terminates. However, the timeout loop I have apparently takes too long for the GC finalizer thread or the main thread terminates destroying the entire process.

Is there a way to tell the GC to not timeout a thread for finalizers? I.E. - I need the finalizer thread to block for a little while in the finalizer so it can complete terminating the message loop thread and then release the unmanaged resource.

Here is my finalizer so you get an idea of what's going on:

PONms::NestedWin32::
!NestedWin32()
{

    if (msgLoop->IsAlive)
    {
        winProcess->EndThread(); // blocks and waits for message loop thread to terminate
                                // and GC apparently doesn't like this causeing the
                                // entire process to terminate here.
    }
    if (childHandle != nullptr)
    {
        DestroyWindowCore(childHandle);
    }
    if (winProcess != nullptr)
    {
        delete winProcess; // memory leak due to resource not being released
    }
}

I'm thinking I went about this the wrong way, just expecting the code to behave properly and the finalizer to complete.

Here is the simple method I use to poll the other thread to see if it has terminated:

void PONms::NestedWin32UM::
EndThread()
{
    int timeOut = 5000;
    threadContinue = false;
    SendNotifyMessage(childWin, WM_CLOSE, 0, 0);
    while (threadActive && timeOut > 0)
    {
        POCPP::Threading::SleepThreadOne();
        timeOut--;
    }
}
Alan Stokes
  • 18,815
  • 3
  • 45
  • 64
ThisHandleNotInUse
  • 1,135
  • 1
  • 10
  • 23

1 Answers1

2
int timeOut = 5000;

That is a pretty drastic mismatch with the default CLR policy for the finalizer thread timeout. You've got 2 seconds to get the job done. Roughly 10 billion instructions on a modern processor. We can't see what SleepThreadOne() does, but Sleep(1) doesn't sleep for 1 millisecond. Default sleep granularity is 15.625 msec so you'll end up waiting for as long as 78 seconds.

Technically you can extend the timeout by custom-hosting the CLR, ICLRPolicyManager::SetTimeout() method, OPR_FinalizerRun setting. But, realistically, if you can't hack it with 10 billion instructions then extending it isn't very likely to bring relief.

Debugging this isn't that simple, those 2 seconds are over in a hurry. Look at structural fixes. Don't use a bool to synchronize code, use an event (CreateEvent winapi function). And WaitForSingleObject() with a timeout to wait for it to be set. Use 1000 msec max so you give the finalizer thread enough breathing room. And don't be too nice asking the message loop to quit, WM_CLOSE is far too friendly. Code is apt to respond to it with a "Save changes?" message box, that's a guaranteed fail. Use PostQuitMessage(). Or don't bother at all, programs should terminate through the UI and you seem to need to pull the rug another way.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • " you'll end up waiting for as long as 78 seconds." - Agh, yeah, I forgot about that. There's no circumstances where it's going to take that long to release under normal conditions - but I will shorten the interval now that I've remembered the sleep resolution. "PostQuitMessage()" PQM doesn't take a HWND handle that I can tell and the thread calling it is not the owning thread. This window is just a DX host in WPF without using HwndHost, it's not going to have any "save changes" dialogs or anything. It's a window that pretends to be a WPF control. – ThisHandleNotInUse Sep 16 '15 at 09:58
  • I seem to have pretty much solved my problem by subscribing to the "Application.Exit" event and handling destruction there. I guess I will just implement a manual destruction for other cases. – ThisHandleNotInUse Sep 16 '15 at 10:04