https://bugs.winehq.org/show_bug.cgi?id=54005
--- Comment #4 from Kevin Puetz PuetzKevinA@JohnDeere.com --- Created attachment 73628 --> https://bugs.winehq.org/attachment.cgi?id=73628 Text code exploring the behavior of reentrant cross-thread SendMessage
#include <windows.h> #include <stdio.h> #include <assert.h>
LRESULT magic_result = 0;
//#define SUSPEND_THREAD #ifdef SUSPEND_THREAD HANDLE suspended_thread = 0; #endif
#if 0 #define USE_HWND_MESSAGE HWND_MESSAGE #else #define USE_HWND_MESSAGE NULL #endif
static LRESULT WINAPI MsgCheckProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { //fprintf(stderr, "MsgCheckProc %d in thread %d\n",message,GetCurrentThreadId()); switch(message) { case WM_USER: HANDLE thread = (HANDLE)wParam; HWND thread_hwnd = (HWND)lParam;
// nudge our own thread's message pump to quit (and resume the other thread) after this wndproc returns PostQuitMessage(0); #ifdef SUSPEND_THREAD suspended_thread = thread; DWORD suspend_count = SuspendThread(thread); //fprintf(stderr, "SuspendThread(%p) from %d\n",suspended_thread,GetCurrentThreadId()); assert(suspend_count == 0); // nobody else should have been suspending it before us, so previous count ought to be 0...
// FIXME: SuspendThread is itself asynchronous: https://devblogs.microsoft.com/oldnewthing/20150205-00/?p=44743 // But calling GetThreadContext requires that the the scheduler save and read back the (user) context, forcing at least that much synchronization #ifndef __WINE__ CONTEXT thread_context; if(!GetThreadContext(thread,&thread_context)) { fprintf(stderr, "***GetThreadContext(%p) failed\n",thread); } #endif
// but even this isn't *quite* there, becuse https://stackoverflow.com/questions/3444190/windows-suspendthread-doesnt-get... // > SuspendThread is asynchronous with respect to kernel-mode execution, but user-mode execution does not occur until the thread is resumed // So if we happen to catch it when it has already begun some kernel wait primitive (as seems likely), // the we can get the context (that it will be returning to), and yet the wait has begun and might observe the QS_SENDMESSAGE wakeup // alone (once SendNotifyMessage queues it) even though it will be unable to act until after wndproc has returned. // // But the race window is now very small, so it fails **most** of the time #endif
LRESULT magic_orig = magic_result; SendNotifyMessage(thread_hwnd,WM_USER+1,0,magic_result+1); return magic_orig; case WM_USER+1: magic_result = lParam; return magic_result; }
return DefWindowProc(hwnd, message, wParam, lParam); }
static BOOL RegisterWindowClasses(void) { WNDCLASS cls; memset(&cls,0, sizeof(cls));
cls.lpfnWndProc = MsgCheckProc; cls.hInstance = GetModuleHandle(0); cls.lpszClassName = "TestWindowClass"; if(!RegisterClass(&cls)) return FALSE;
return TRUE; }
struct pump_thread_param { HANDLE init_event; HWND hwnd; };
static DWORD WINAPI pump_thread_proc(LPVOID lpParameter) { BOOL bRet = 0; MSG msg; struct pump_thread_param *thread_param = (struct pump_thread_param *)lpParameter; HWND thread_hwnd = thread_param->hwnd = CreateWindow("TestWindowClass", 0, 0, 0, 0, 0, 0, USE_HWND_MESSAGE, NULL, GetModuleHandleA(0), 0); //fprintf(stderr, "thread_hwnd = %p\n",thread_hwnd); SetEvent(thread_param->init_event);
while( (bRet = GetMessage( &msg, 0, 0, 0 )) != 0) { if (bRet == -1) { break; } else { TranslateMessage(&msg); DispatchMessage(&msg); } } #ifdef SUSPEND_THREAD if(suspended_thread) { //fprintf(stderr, "ResumeThread(%p) from %d\n",suspended_thread,GetCurrentThreadId()); ResumeThread(suspended_thread); } #endif DestroyWindow(thread_hwnd); return bRet; }
BOOL test_SendMessage_incoming() { HANDLE main_thread = 0; BOOL ret = DuplicateHandle(GetCurrentProcess(),GetCurrentThread(),GetCurrentProcess(),&main_thread,THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION,FALSE,0); assert(ret); //fprintf(stderr, "main_thread id = %d\n",GetCurrentThreadId()); HWND main_hwnd = CreateWindow("TestWindowClass", NULL, 0, 0, 0, 0, 0, USE_HWND_MESSAGE, NULL, GetModuleHandleA(0), 0);
//fprintf(stderr, "main_hwnd = %p\n",main_hwnd);
struct pump_thread_param pump_thread_param; pump_thread_param.init_event = CreateEvent(NULL,FALSE,FALSE,NULL); DWORD pump_thread_id = 0; HANDLE pump_thread = CreateThread(NULL,0,pump_thread_proc,&pump_thread_param,0,&pump_thread_id); //fprintf(stderr, "pump_thread_id id = %d\n",GetCurrentThreadId());
WaitForSingleObject(pump_thread_param.init_event,INFINITE); CloseHandle(pump_thread_param.init_event);
magic_result = 1;
LRESULT wndproc_magic = SendMessage(pump_thread_param.hwnd,WM_USER,(WPARAM)main_thread,(LPARAM)main_hwnd); LRESULT after_magic = magic_result;
assert(wndproc_magic == 1); // thread_hwnd/WM_USER should see the value we set before doing anything
//fprintf(stderr, "waiting for pump_thread to exit\n",main_hwnd); WaitForSingleObject(pump_thread,INFINITE);
//fprintf(stderr,"magic before PeekMessage: WndProc returned %d, global shows %d\n",wndproc_magic,magic); MSG msg;
// any PeekMessage at all will dispatch all incoming nonqueued message (i.e. QS_SENDMESSAGE) // the filtering by hwnd, msg, etc is only applied posted messages // so this will actually also deliver the WM_USER+1 message to main_hwnd (if it hasnt't already been) PeekMessage(&msg,(HWND)-1,WM_PAINT,WM_PAINT,PM_NOREMOVE); assert(!GetQueueStatus(QS_SENDMESSAGE)); assert(magic_result == 2); // main_hwnd/WM_USER+1 should have stored the new message
if(after_magic == magic_result) { ret = TRUE; /* the WM_USER+1 which changes this was pumped during SendMessage */ } else { /* if magic changed between SendMessage and here (i.e. during PeekMessage), * the WM_USER+1 queued by SendNotifyMessage was not pumped by SendMessage */ //fprintf(stderr,"*** message not pumped by SendMessage changed `magic` from %ld != %ld\n",after_magic, magic_result); ret = FALSE; }
CloseHandle(main_thread); DestroyWindow(main_hwnd); return ret; }
int main() { //SetProcessAffinityMask(GetCurrentProcess(),1);
RegisterWindowClasses();
int pass = 0, fail = 0;
DWORD startTime = GetTickCount();
for(int i = 0; i < 100; ++i) { //fprintf(stderr,"---\n"); if(test_SendMessage_incoming()) { ++pass; } else { ++fail; } }
printf("%d pass / %d fail in %u msec\n",pass, fail, GetTickCount() - startTime);
return fail == 0 ? 0 : 1; }