From: Dmitry Timoshkov Subject: user32/tests: Add a test for sending inter-thread messages. Message-Id: <20150420185406.43705fe3.dmitry@baikal.ru> Date: Mon, 20 Apr 2015 18:54:06 +0800 This test shows that handling of internal Wine messages (that are sent across the threads) breaks normal message flow order, which leads to races and unexpected behaviour. For instance an application that I have here does at the end of the dialog processing code: thread_proc() { [some work here] PostMessageA(hdlg, WM_NULL, 0, 0); EndDialog(hdlg, 1); /* in reality this not an assert but some code that never executes * under Windows and leads to an exception under Wine */ assert(0); } main() { hdlg = CreateDialog(); CreateThread(thread_proc); while (GetMessageA(&msg, 0, 0, 0)) { TranslateMessage(&msg); DispatchMessageA(&msg); if (msg.message == WM_NULL) { [some finishing work here] DestroyWindow(hdlg); break; } } ExitProcess(0); } EndDialog() from another thread indirectly leads to sending messages to main thread (SetWindowPos(SWP_HIDEWINDOW) + focus/activation messages), but since main thread doesn't process messages at that point EndDialog() waits forever and never returns under Windows, but under Wine SetWindowPos() and focus/activation sends internal inter-thread messages which actually get executed and that sometimes makes EndDialog() return (under Wine) and causes crashes by running into the code that never executes under Windows. This test reproduces described above scenario with a simplified approach. Testbot failures are not caused by new tests. --- dlls/user32/tests/msg.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/dlls/user32/tests/msg.c b/dlls/user32/tests/msg.c index 5f10a89..b014dd9 100644 --- a/dlls/user32/tests/msg.c +++ b/dlls/user32/tests/msg.c @@ -14598,6 +14598,153 @@ static void test_TrackPopupMenuEmpty(void) DestroyWindow(hwnd); } +static const struct message send_message_1[] = { + { WM_USER+2, sent|wparam|lparam, 0, 0 }, + { WM_USER, sent|wparam|lparam, 0, 0 }, + { 0 } +}; +static const struct message send_message_2[] = { + { WM_USER+4, sent|wparam|lparam, 0, 0 }, + { 0 } +}; +static const struct message send_message_3[] = { + { WM_USER+3, sent|wparam|lparam, 0, 0 }, + { WM_USER+1, sent|wparam|lparam, 0, 0 }, + { 0 } +}; + +static DWORD WINAPI SendMessage_thread_1(void *param) +{ + struct wnd_event *wnd_event = param; + + trace("thread: starting\n"); + WaitForSingleObject(wnd_event->start_event, INFINITE); + + trace("thread: call PostMessage\n"); + PostMessageA(wnd_event->hwnd, WM_USER, 0, 0); + + trace("thread: call PostMessage\n"); + PostMessageA(wnd_event->hwnd, WM_USER+1, 0, 0); + + trace("thread: call SendMessage\n"); + SendMessageA(wnd_event->hwnd, WM_USER+2, 0, 0); + + trace("thread: call SendMessage\n"); + SendMessageA(wnd_event->hwnd, WM_USER+3, 0, 0); + + return 0; +} + +static DWORD WINAPI SendMessage_thread_2(void *param) +{ + struct wnd_event *wnd_event = param; + + trace("thread: starting\n"); + WaitForSingleObject(wnd_event->start_event, INFINITE); + + trace("thread: call PostMessage\n"); + PostMessageA(wnd_event->hwnd, WM_USER, 0, 0); + + trace("thread: call PostMessage\n"); + PostMessageA(wnd_event->hwnd, WM_USER+1, 0, 0); + + /* this leads to sending an internal message under Wine */ + trace("thread: call EnableWindow\n"); + EnableWindow(wnd_event->hwnd, TRUE); + + trace("thread: call SendMessage\n"); + SendMessageA(wnd_event->hwnd, WM_USER+2, 0, 0); + + trace("thread: call SendMessage\n"); + SendMessageA(wnd_event->hwnd, WM_USER+3, 0, 0); + + return 0; +} + +static void test_SendMessage_other_thread(int thread_n) +{ + DWORD qs_all_input = QS_ALLINPUT & ~QS_RAWINPUT; + HANDLE hthread; + struct wnd_event wnd_event; + DWORD tid, ret; + MSG msg; + + wnd_event.start_event = CreateEventA(NULL, 0, 0, NULL); + + wnd_event.hwnd = CreateWindowExA(0, "TestWindowClass", NULL, WS_OVERLAPPEDWINDOW, + 100, 100, 200, 200, 0, 0, 0, NULL); + ok(wnd_event.hwnd != 0, "CreateWindowEx failed\n"); + + hthread = CreateThread(NULL, 0, thread_n == 1 ? SendMessage_thread_1 : SendMessage_thread_2, &wnd_event, 0, &tid); + ok(hthread != NULL, "CreateThread failed, error %d\n", GetLastError()); + CloseHandle(hthread); + + flush_events(); + flush_sequence(); + + ret = GetQueueStatus(QS_SENDMESSAGE); + ok(ret == 0, "wrong status %08x\n", ret); + + SetEvent(wnd_event.start_event); + + /* wait for other thread's SendMessage */ + for (;;) + { + ret = GetQueueStatus(QS_SENDMESSAGE); + if (ret == MAKELONG(QS_SENDMESSAGE, QS_SENDMESSAGE)) break; + Sleep(50); + } + + ret = GetQueueStatus(QS_SENDMESSAGE|QS_POSTMESSAGE); +todo_wine + ok(ret == MAKELONG(QS_POSTMESSAGE, QS_SENDMESSAGE|QS_POSTMESSAGE), "wrong status %08x\n", ret); + + trace("main: call GetMessage\n"); + GetMessageA(&msg, 0, 0, 0); + ok(msg.message == WM_USER, "expected WM_USER, got %04x\n", msg.message); + DispatchMessageA(&msg); + ok_sequence(send_message_1, "SendMessage from other thread 1", thread_n == 2); + + /* intentionally yield */ + MsgWaitForMultipleObjects(0, NULL, FALSE, 100, qs_all_input); + + trace("main: call SendMessage\n"); + SendMessageA(wnd_event.hwnd, WM_USER+4, 0, 0); + ok_sequence(send_message_2, "SendMessage from other thread 2", FALSE); + + ret = GetQueueStatus(QS_SENDMESSAGE|QS_POSTMESSAGE); + ok(ret == MAKELONG(QS_SENDMESSAGE, QS_SENDMESSAGE|QS_POSTMESSAGE), "wrong status %08x\n", ret); + + trace("main: call PeekMessage\n"); + ok(PeekMessageA(&msg, 0, 0, 0, PM_REMOVE), "PeekMessage should not fail\n"); + ok(msg.message == WM_USER+1, "expected WM_USER+1, got %04x\n", msg.message); + DispatchMessageA(&msg); + ok_sequence(send_message_3, "SendMessage from other thread 3", thread_n == 2); + + /* intentionally yield */ + MsgWaitForMultipleObjects(0, NULL, FALSE, 100, qs_all_input); + + ret = GetQueueStatus(QS_SENDMESSAGE|QS_POSTMESSAGE); + /* FIXME: remove once Wine is fixed */ +if (thread_n == 2) todo_wine + ok(ret == 0, "wrong status %08x\n", ret); +else + ok(ret == 0, "wrong status %08x\n", ret); + + trace("main: call PeekMessage\n"); + ok(!PeekMessageA(&msg, 0, 0, 0, PM_REMOVE), "PeekMessage should fail\n"); + ok_sequence(WmEmptySeq, "SendMessage from other thread 4", thread_n == 2); + + ret = GetQueueStatus(QS_SENDMESSAGE|QS_POSTMESSAGE); + ok(ret == 0, "wrong status %08x\n", ret); + + trace("main: call DestroyWindow\n"); + DestroyWindow(msg.hwnd); + + flush_events(); + flush_sequence(); +} + static void init_funcs(void) { HMODULE hKernel32 = GetModuleHandleA("kernel32.dll"); @@ -14675,6 +14822,9 @@ START_TEST(msg) pUnhookWinEvent = 0; } hEvent_hook = 0; + + test_SendMessage_other_thread(1); + test_SendMessage_other_thread(2); test_SetFocus(); test_SetParent(); test_PostMessage(); -- 2.3.5