Hi folks,
Due to my new found belief that all of the flaws in Wine are timing problems, I have found what appears to be a gaping hole in Wine's timing behavior.
Specifically, it appears as though any style of Waitxxx is supposed to yield the processor.
This seems a bit difficult for me to believe, even though we had earlier discovered that Wine was never yielding on a Sleep(0).
I've attached the test program I used to discover this. If you run sleep2 -1 0w the program will start 2 threads, one which spin loops (-1), and one which does a wait on an object for 0 seconds. You'll see in the results that the Wait(...,0) thread gets far fewer loops, and thus must be yielding.
Further, changing that to a 0ws (or even 5ws), which means we're waiting on a signalled object, doesn't change the behavior.
This leads me to suggest the attached patch.
I would appreciate review from other folks, because this seems like a drastic change to me, and it strikes me as equally likely that I've done something stupid.
Cheers,
Jeremy
#include <STDIO.H> #include <stdlib.h> #include <windows.h>
typedef struct { char *arg; HANDLE event; HANDLE handle; DWORD stopat; int wait; int signaled; int priority; int sleeptime; } threadinfo; int quit = 0; HANDLE releaseall; DWORD WINAPI mythread(LPVOID myparm) { threadinfo *info = (threadinfo *) myparm; DWORD last, now; DWORD delta, max_delta = 0, total_delta = 0; long count = 0; long delta_count = 0; long expected_loops = -1; int rc; if (info->sleeptime > 0) expected_loops = 5000 / info->sleeptime; WaitForSingleObject(releaseall, INFINITE); while (! quit) { last = GetTickCount(); if (last > info->stopat) break; count++; if (info->sleeptime >= 0) { if (info->wait) { rc = WaitForSingleObject(info->event, info->sleeptime); if (info->signaled && rc != WAIT_OBJECT_0) printf("whoops!\n"); } else Sleep(info->sleeptime); now = GetTickCount(); if (now - last != (DWORD) info->sleeptime) { delta_count++; delta = now - last; total_delta += delta; if (delta > max_delta) max_delta = delta; } } } printf("%s: [%ld loops|%ld expected|%lu total_delta|%lu max delta|%ld delta_count|%ld avg delta]\n", info->arg, count, expected_loops, total_delta, max_delta, delta_count, delta_count == 0 ? 0 : total_delta / delta_count); return(0); } int main(int argc, char *argv[]) { int i; char *p; DWORD tid; threadinfo threads[20]; DWORD dieat; releaseall = CreateEvent(NULL, TRUE, FALSE, NULL); dieat = GetTickCount() + 5000; memset(threads, '\0', sizeof(threads)); for (i = 1; i < argc && i < sizeof(threads) / sizeof(threads[0]); i++) { threads[i].sleeptime = atoi(argv[i]); if (strchr(argv[i], 's')) threads[i].signaled++; threads[i].handle = CreateThread(NULL, 0, &mythread, (void *)&threads[i], 0, &tid); threads[i].event = CreateEvent(NULL, threads[i].signaled ? TRUE : FALSE, threads[i].signaled ? TRUE : FALSE, NULL); threads[i].stopat = dieat; threads[i].arg = argv[i]; if (strchr(argv[i], 'w')) threads[i].wait++; p = strchr(argv[i], ':'); if (p) { threads[i].priority = atoi(p + 1); SetThreadPriority(threads[i].handle, threads[i].priority); } p = strchr(argv[i], 'e'); if (p) { timeSetEvent(threads[i].sleeptime, 1, threads[i].event, 0, TIME_PERIODIC | TIME_CALLBACK_EVENT_SET); } } printf("Starting threads, waiting for 5 s\n"); fflush(stdout); SetEvent(releaseall); Sleep(5000); quit = 1; Sleep(1000); }
Index: dlls/ntdll/sync.c =================================================================== RCS file: /home/wine/wine/dlls/ntdll/sync.c,v retrieving revision 1.37 diff -u -r1.37 sync.c --- dlls/ntdll/sync.c 11 Oct 2004 20:11:01 -0000 1.37 +++ dlls/ntdll/sync.c 1 Nov 2004 20:59:47 -0000 @@ -586,6 +586,11 @@ call_apcs( (flags & SELECT_ALERTABLE) != 0 ); if (flags & SELECT_ALERTABLE) break; } + + /* A test on Windows 2000 shows that Windows always yields at the + end of a wait, regardless of the disposition of the wait itself */ + NtYieldExecution(); + return ret; }