#2614 "ButtonProc called on an invalid HWND "

obsolete: 8.4.19

I think it is just the same as "W XP: ButtonProc called on invalid HWND - ID: 644641". I was able to reproduce it (thought not on tclsh/wish but on Tcl/Tk interpreter embedded into large C application). I believe that I have also debugged the problem and have a simple fix. Please read on:

The problem is as follows: suppose you're creating a big number of buttons, and that under heavy load. Just immediately after issuing request to create them, you decide (for some reason) to delete them. It can happen (in fact, this is required for the bug to occur) that their (buttons) respective windows are not created yet when command for them to be destroyed is issued. It can also happen that their respective parent windows are not created, too. I believe that their creation requests (X events emulated on top of Win32 messages) are still in message queues at the time.

Now, for some obscure reasons, Tk_DestroyWindow procedure calls Tk_MakeWindowExists if the former is called on not-yet-created window. Just like that:
if (winPtr->window == None) {
Tk_MakeWindowExist for a given window can, in turn, cause Tk_MakeWindowExist call on parent window, if said parent window is not existent at the moment. Example code:
if (winPtr->parentPtr->window == None) {
Tk_MakeWindowExist((Tk_Window) winPtr->parentPtr);
(tkwindow.c:1716 in tk version 8.4.16)
Now the problem is, that somewhere along stack filled with recursive Tk_MakeWindowExist calls, event loop is being processed. That event loop can cause original creation requests for our buttons to be executed, and windows actually created, along with attaching (via Tk_AttachHWND) window handles to them. But, what follows in Tk_MakeWindowExist is _nonconditional_ call to CreateProc for current window, which causes this very same window to be re-created and re-attached to different handle. Handle window was originally attached to gets "orphaned" because of this (adding new mapping to Tk_AttachHWND causes old mapping to be removed). Thereby, when some event (Win32 message, actually) makes it's way to this HWND's ButtonProc, lookup for apropriate Tk_Window causes NULL to be returned and panic called in ButtonProc.

So, to sum things up:
1. Tcl script causes hierarchy of windows containing few buttons to be created. Creation requests for these are issued to queue but not executed yet (they have no physical windows), when...
2. Request comes to delete all these buttons. Doing so requires their actual _creation_, so Tk_DestroyWindow calls Tk_MakeWindowExist (which can call itself recursively). Recursive execution of Tk_MakeWindowExist may cause event loop execution and creation of windows for buttons and controls.
3. But on returing from recursive calls to Tk_MakeWindowExist, this very procedure makes no effort so as to check if window was created meantime, it just assumes that it has to create it. This can cause re-creation of already created window, and by this removing of HWND to TkWindow map entry, resulting in "orphan" HWND.
4. Any message arriving to "orphaned" HWND (from some later-executed event loop) causes it to get NULL from HWND to TkWIndow map and, in consequence, panicking.

Fortunatelly, resolution for this effect is very simple. It is enough to add simple check in Tk_MakeWindowExist, _just_ after Tk_MakeWindowExist was called recursively for parent window, like that:

// original code from Tk_MakeWindowExist, calling recursively itself for parent
if (winPtr->parentPtr->window == None) {
Tk_MakeWindowExist((Tk_Window) winPtr->parentPtr);
parent = winPtr->parentPtr->window;
} // from earlier if

// this is added protection code
if( winPtr->window != None ) {

// this is original, unconditional creation of window
createProc = Tk_GetClassProc(winPtr->classProcsPtr, createProc);
// and so on

I have tested this and it works. I would prefer not to provide test environment, because that is really big and proprietary application, and setting for up it's compile environment alone takes days. Anyway, I had 100% reproductible test case, and after adding simple test (just like described above) problem is gone. In case of questions, doubts, etc, please contact m<dot>adamczak<at> google mail.



  • Don Porter

    Don Porter - 2009-04-01

    How does this relate to Bug 2645457 ?

  • Don Porter

    Don Porter - 2009-04-01
    • priority: 5 --> 8
    • assigned_to: hobbs --> patthoyts
  • Pat Thoyts

    Pat Thoyts - 2009-04-03

    (mailed to original poster)

    This seems quite similar to bug #2645457 which I recently fixed for the gitk application. In this case the crash was in Tk_MapWindow but Tk_MakeWindowExist was the trigger for this.
    It would be useful if you could test your application with the current code from CVS using either the 8.4 or 8.5 branch tips or the 8.6 HEAD. The fix for this bug has been applied to all three active branches but is not yet present in any released version.

    If the problem still occurs then your suggested fix looks correct to me but obviously it will be hard to incorporate into the test suite unless we can find a way to generate the fault without requiring your application.

  • Pat Thoyts

    Pat Thoyts - 2009-04-29
    • status: open --> closed-out-of-date
  • Pat Thoyts

    Pat Thoyts - 2009-04-29

    No response in a month. Closing.