On thread destroy, a WM_WINE_DESTROYWINDOW is sent to the child windows living in other threads.
There's then a race condition between these threads peeking for messages and the current thread detaching its child windows from their threads and clearing their message queues, and the message may never be received from these threads and the windows kept alive.
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/user32/tests/msg.c | 4 ++-- server/window.c | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/dlls/user32/tests/msg.c b/dlls/user32/tests/msg.c index fab8f291a52..23ba4f1d3d5 100644 --- a/dlls/user32/tests/msg.c +++ b/dlls/user32/tests/msg.c @@ -8843,7 +8843,7 @@ static DWORD CALLBACK create_grand_child_thread( void *param ) ok( !ret, "WaitForSingleObject returned %x, error: %u\n", ret, GetLastError() ); ok( IsWindow( hchild ), "Child window already destroyed\n" ); flush_events(); - todo_wine ok( !IsWindow( hchild ), "Child window not destroyed\n" ); + ok( !IsWindow( hchild ), "Child window not destroyed\n" );
return 0; } @@ -9032,7 +9032,7 @@ static void test_interthread_messages(void) CloseHandle( wnd_event.stop_event ); CloseHandle( wnd_event.ready_event ); flush_events(); - ok_sequence( WmExitThreadSeq, "destroy child on thread exit", TRUE ); + ok_sequence( WmExitThreadSeq, "destroy child on thread exit", FALSE ); log_all_parent_messages--; DestroyWindow( wnd_event.hwnd );
diff --git a/server/window.c b/server/window.c index ac12912db3a..026d54e6d32 100644 --- a/server/window.c +++ b/server/window.c @@ -1890,9 +1890,17 @@ void destroy_window( struct window *win )
/* destroy all children */ while (!list_empty(&win->children)) - destroy_window( LIST_ENTRY( list_head(&win->children), struct window, entry )); + { + struct window *child = LIST_ENTRY( list_head( &win->children ), struct window, entry ); + if (!child->thread || child->thread == win->thread) destroy_window( child ); + else list_remove( &child->entry ); + } while (!list_empty(&win->unlinked)) - destroy_window( LIST_ENTRY( list_head(&win->unlinked), struct window, entry )); + { + struct window *child = LIST_ENTRY( list_head( &win->unlinked ), struct window, entry ); + if (!child->thread || child->thread == win->thread) destroy_window( child ); + else list_remove( &child->entry ); + }
/* reset global window pointers, if the corresponding window is destroyed */ if (win == shell_window) shell_window = NULL;