https://bugs.winehq.org/show_bug.cgi?id=54005
Kevin Puetz PuetzKevinA@JohnDeere.com changed:
What |Removed |Added ---------------------------------------------------------------------------- CC| |PuetzKevinA@JohnDeere.com
--- Comment #1 from Kevin Puetz PuetzKevinA@JohnDeere.com --- This seems to be potentially flaky for a long time: ole32:clipboard is blacklisted even in the old winezeug system: Looks like was blacklisted for years in the old winezeug system: https://github.com/Winetricks/winezeug/commit/3d19e65ca133d39b1b968b2171d0c6...
But I also just hit this while debugging some other threading/apartment fixes and dug into it a bit. It seems to be a race condition:
set_clipboard_thread will leave a WM_DRAWCLIPBOARD message in the queue, with QS_SendMessage (sent by CloseClipboard as [`SendNotifyMessageW( viewer, WM_DRAWCLIPBOARD, (WPARAM)owner, 0 )`](dlls/user32/clipboard.c:639). But (since this was SendNotifyMessage, that lack of pumping doesn't prevent the thread from completing and exiting (successfully). [`WaitForSingleObject(thread)`](dlls/ole32/tests/clipboard.c:1127) will not pump any messages, so this will still remain in the queue until we get into OleSetClipboard.
The purpose of test_set_clipboard_DRAWCLIPBOARD seems to be to demonstrate that OleSetClipboard will deliver this pending WM_DRAWCLIPBOARD, *before* the clipboard actually returns the new data (the test failing with [`OleIsCurrentClipboard returned ...`](dlls/ole32/tests/clipboard.c:1068) is expecting it to be the old data on the first WM_DRAWCLIPBOARD, and the new clip_data on the second.
But while this will *sometimes* happen, it doesn't seem guaranteed; OleSetClipboard does no explicit message pumping. When it works, it seems to work because OleSetClipboard will call EmptyClipboard, which will [`SendMessageTimeoutW( owner, WM_DESTROYCLIPBOARD, 0, 0, SMTO_ABORTIFHUNG, 5000, NULL )`](dlls/user32/clipboard.c:736). This message is going to the desktop HWND, which is in another thread, so it will go down send_inter_thread_message. [SMTO_ABORTIFHUNG is not SMTO_BLOCK, so this SendMessageTimeoutW will end up with QS_SENDMESSAGE in its wake mask](dlls/user32/message.c:2950).
We've made the calls to SERVER_START_REQ( send_message ), so now both the desktop thread (in explorer.exe) and the "main" thread (running test_set_clipboard_DRAWCLIPBOARD) would be runnable. If the schedule runs test_set_clipboard_DRAWCLIPBOARD first, wait_message_reply will get [wake_bits = QS_SENDMESSAGE and process_sent_messages()](dlls/user32/message.c:2971) will deliver that long-pending WM_DRAWCLIPBOARD, and the test will succeed. If it instead schedules explorer first, the desktop window will process its WM_DESTROYCLIPBOARD first, and when we get back wait_message_reply will get wake_bits = QS_SMRESULT | QS_SENDMESSAGE, and give precedence to QS_SMRESULT, returning with that WM_DRAWCLIPBOARD still on the queue (thus it left to arrive after the new clip_data is set, and the OleIsCurrentClipboard / wm_drawclipboard checks fail).
I'm not sure what the fix is here.
SendMessage certainly allows reentrant calls, and one could reverse the precedence so it would always drain QS_SENDMESSAGE before returning QS_SMRESULT, but that seems like a rather deep change of priority (and I'm not sure how to consistently engineer a "fast" wndproc to probe what windows SendMessage actually does). Or OleSetClipboard could pump WM_DRAWCLIPBOARD explicitly. Or this might simply be an invalid test (that ought not to be expected to succeed).
The test does seem to pass on windows, at least from what I can see on http://test.winehq.org/data/tests/ole32:clipboard.html, but it might just be that the NT scheduler doesn't tend to switch to the desktop window's thread right away. The back and forth between threads and wineserver does seem like the sort of thing where the linux interactive scheduling policies designed for X11 might be working against us here (to make it more likely that send_inter_thread_message immediately wakes the recipient thread).