winmm: Use fd's to synchronize timers

Maarten Lankhorst m.b.lankhorst at gmail.com
Sat Jun 2 09:07:23 CDT 2007


Use fd's for timers, for better kernel scheduling.
-------------- next part --------------
>From b00faabf1c7d2f36855892c63ecc573e313a9320 Mon Sep 17 00:00:00 2001
From: Maarten Lankhorst <m.b.lankhorst at gmail.com>
Date: Sat, 2 Jun 2007 15:49:21 +0200
Subject: [PATCH] winmm: Use fd's to synchronize timers

---
 dlls/winmm/time.c |  233 +++++++++++++++++++++++++++++++---------------------
 1 files changed, 139 insertions(+), 94 deletions(-)

diff --git a/dlls/winmm/time.c b/dlls/winmm/time.c
index 27a4e46..ce89bce 100644
--- a/dlls/winmm/time.c
+++ b/dlls/winmm/time.c
@@ -4,6 +4,7 @@
  * MMSYSTEM time functions
  *
  * Copyright 1993 Martin Ayotte
+ * Copyright 2007 Maarten Lankhorst
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -20,6 +21,8 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  */
 
+/* Wine <= 0.9.38 uses win32 handles instead of unix pipes to sync, for those who are interested */
+
 #include "config.h"
 #include "wine/port.h"
 
@@ -32,6 +35,14 @@
 # include <unistd.h>
 #endif
 
+#ifdef HAVE_POLL_H
+#include <poll.h>
+#endif
+#ifdef HAVE_SYS_POLL_H
+#include <sys/poll.h>
+#endif
+#include <errno.h>
+
 #include "windef.h"
 #include "winbase.h"
 #include "mmsystem.h"
@@ -40,13 +51,15 @@
 
 #include "wine/debug.h"
 
+#define TIME_DESTROY 0x0200
+
 WINE_DEFAULT_DEBUG_CHANNEL(mmtime);
 
 static    HANDLE                TIME_hMMTimer;
 static    LPWINE_TIMERENTRY 	TIME_TimersList;
-static    HANDLE                TIME_hWakeEvent;
 static    CRITICAL_SECTION      TIME_cbcrst;
 static    BOOL                  TIME_TimeToDie = TRUE;
+static    int                   TIME_fdWake[2] = { -1, -1 };
 
 /*
  * Some observations on the behavior of winmm on Windows.
@@ -77,8 +90,7 @@ static    BOOL                  TIME_TimeToDie = TRUE;
 #define MMSYSTIME_MININTERVAL (1)
 #define MMSYSTIME_MAXINTERVAL (65535)
 
-
-static	void	TIME_TriggerCallBack(LPWINE_TIMERENTRY lpTimer)
+static	void	TIME_TriggerCallBack(LPWINE_MM_IDATA iData, LPWINE_TIMERENTRY lpTimer)
 {
     TRACE("%04x:CallBack => lpFunc=%p wTimerID=%04X dwUser=%08X dwTriggerTime %d(delta %d)\n",
 	  GetCurrentThreadId(), lpTimer->lpFunc, lpTimer->wTimerID, lpTimer->dwUser,
@@ -89,13 +101,17 @@ static	void	TIME_TriggerCallBack(LPWINE_TIMERENTRY lpTimer)
      *	    	PostMessage), and must reside in DLL (therefore uses stack of active application). So I
      *       guess current implementation via SetTimer has to be improved upon.
      */
+    EnterCriticalSection(&TIME_cbcrst);
     switch (lpTimer->wFlags & 0x30) {
     case TIME_CALLBACK_FUNCTION:
 	if (lpTimer->wFlags & WINE_TIMER_IS32)
 	    (lpTimer->lpFunc)(lpTimer->wTimerID, 0, lpTimer->dwUser, 0, 0);
 	else if (pFnCallMMDrvFunc16)
-	    pFnCallMMDrvFunc16((DWORD)lpTimer->lpFunc, lpTimer->wTimerID, 0,
-                               lpTimer->dwUser, 0, 0);
+	{
+            LeaveCriticalSection(&iData->cs);
+            pFnCallMMDrvFunc16((DWORD)lpTimer->lpFunc, lpTimer->wTimerID, 0, lpTimer->dwUser, 0, 0);
+            EnterCriticalSection(&iData->cs);
+	}
 	break;
     case TIME_CALLBACK_EVENT_SET:
 	SetEvent((HANDLE)lpTimer->lpFunc);
@@ -108,73 +124,52 @@ static	void	TIME_TriggerCallBack(LPWINE_TIMERENTRY lpTimer)
 	      lpTimer->wFlags, lpTimer->lpFunc);
 	break;
     }
+    LeaveCriticalSection(&TIME_cbcrst);
 }
 
 /**************************************************************************
  *           TIME_MMSysTimeCallback
  */
-static DWORD CALLBACK TIME_MMSysTimeCallback(LPWINE_MM_IDATA iData)
+static int CALLBACK TIME_MMSysTimeCallback(LPWINE_MM_IDATA iData)
 {
-static    int				nSizeLpTimers;
-static    LPWINE_TIMERENTRY		lpTimers;
-
-    LPWINE_TIMERENTRY   timer, *ptimer, *next_ptimer;
-    int			idx;
-    DWORD               cur_time;
-    DWORD               delta_time;
-    DWORD               ret_time = INFINITE;
-    DWORD               adjust_time;
-
+    LPWINE_TIMERENTRY timer, *ptimer, *next_ptimer;
+    DWORD cur_time, delta_time, ret_time = INFINITE, adjust_time;
 
     /* optimize for the most frequent case  - no events */
     if (! TIME_TimersList)
-        return(ret_time);
-
-    /* since timeSetEvent() and timeKillEvent() can be called
-     * from 16 bit code, there are cases where win16 lock is
-     * locked upon entering timeSetEvent(), and then the mm timer
-     * critical section is locked. This function cannot call the
-     * timer callback with the crit sect locked (because callback
-     * may need to acquire Win16 lock, thus providing a deadlock
-     * situation).
-     * To cope with that, we just copy the WINE_TIMERENTRY struct
-     * that need to trigger the callback, and call it without the
-     * mm timer crit sect locked.
-     * the hKillTimeEvent is used to mark the section where we 
-     * handle the callbacks so we can do synchronous kills.
-     * EPP 99/07/13, updated 04/01/10
-     */
-    idx = 0;
+        return -1;
+
     cur_time = GetTickCount();
 
     EnterCriticalSection(&iData->cs);
     for (ptimer = &TIME_TimersList; *ptimer != NULL; ) {
         timer = *ptimer;
         next_ptimer = &timer->lpNext;
-        if (cur_time >= timer->dwTriggerTime)
+        if (timer->wFlags & TIME_DESTROY)
+        { /* Do nothing here, more down here.. */ }
+        else if (cur_time >= timer->dwTriggerTime)
         {
-            if (timer->lpFunc) {
-                if (idx == nSizeLpTimers) {
-                    if (lpTimers) 
-                        lpTimers = (LPWINE_TIMERENTRY)
-                            HeapReAlloc(GetProcessHeap(), 0, lpTimers,
-                                        ++nSizeLpTimers * sizeof(WINE_TIMERENTRY));
-                    else 
-                        lpTimers = (LPWINE_TIMERENTRY)
-                        HeapAlloc(GetProcessHeap(), 0,
-                                  ++nSizeLpTimers * sizeof(WINE_TIMERENTRY));
-                }
-                lpTimers[idx++] = *timer;
-
-            }
+            /* I'm assuming that 32 bits code isn't allowed to call timeSetEvent or timeKillEvent.
+             * Basically the we hold winmm idata critsect, except when calling 16 bits code, then
+             * it holds the kill critical section.
+             *
+             * If someone calls TimeKillEvent, it will try to obtain the winmm idata lock and then
+             * possibly the kill lock. If it can obtain kill lock it just destroys the timer. If
+             * it fails to obtain the kill lock it will either spin if it is meant to kill
+             * synchronously, or set a flag that the thread should destroy it.
+             */
+            if (timer->lpFunc)
+                TIME_TriggerCallBack(iData, timer);
 
             /* Update the time after we make the copy to preserve
                the original trigger time    */
             timer->dwTriggerTime += timer->wDelay;
 
-            /* TIME_ONESHOT is defined as 0 */
-            if (!(timer->wFlags & TIME_PERIODIC))
+            if (timer->wFlags & TIME_DESTROY)
+            { /* Do nothing here, more down here.. */ }
+            else if (!(timer->wFlags & TIME_PERIODIC))
             {
+                /* TIME_ONESHOT is defined as 0 */
                 /* unlink timer from timers list */
                 *ptimer = *next_ptimer;
                 HeapFree(GetProcessHeap(), 0, timer);
@@ -195,29 +190,32 @@ static    LPWINE_TIMERENTRY		lpTimers;
         else
             delta_time = timer->dwTriggerTime - cur_time;
 
+        if (timer->wFlags & TIME_DESTROY)
+        {
+            *ptimer = *next_ptimer;
+            HeapFree(GetProcessHeap(), 0, timer);
+        }
+        else
         /* Determine when we need to return to this function */
-        ret_time = min(ret_time, delta_time);
+            ret_time = min(ret_time, delta_time);
 
         ptimer = next_ptimer;
     }
     LeaveCriticalSection(&iData->cs);
 
-    EnterCriticalSection(&TIME_cbcrst);
-    while (idx > 0) TIME_TriggerCallBack(&lpTimers[--idx]);
-    LeaveCriticalSection(&TIME_cbcrst);
+    if (ret_time == INFINITE)
+        return -1;
 
     /* Finally, adjust the recommended wait time downward
        by the amount of time the processing routines 
        actually took */
     adjust_time = GetTickCount() - cur_time;
     if (adjust_time > ret_time)
-        ret_time = 0;
-    else
-        ret_time -= adjust_time;
+        return 0;
 
     /* We return the amount of time our caller should sleep
        before needing to check in on us again       */
-    return(ret_time);
+    return ret_time - adjust_time;
 }
 
 /**************************************************************************
@@ -226,8 +224,13 @@ static    LPWINE_TIMERENTRY		lpTimers;
 static DWORD CALLBACK TIME_MMSysTimeThread(LPVOID arg)
 {
     LPWINE_MM_IDATA iData = (LPWINE_MM_IDATA)arg;
-    DWORD sleep_time;
-    DWORD rc;
+    int sleep_time;
+    char readme[16];
+    int ret;
+
+    struct pollfd pfd;
+    pfd.fd = TIME_fdWake[0];
+    pfd.events = POLLIN;
 
     TRACE("Starting main winmm thread\n");
 
@@ -239,18 +242,28 @@ static DWORD CALLBACK TIME_MMSysTimeThread(LPVOID arg)
 
     while (! TIME_TimeToDie) 
     {
-	sleep_time = TIME_MMSysTimeCallback(iData);
+        /* Sleep time should always be < 65536 anyway, safe to convert */
+        sleep_time = TIME_MMSysTimeCallback(iData);
 
         if (sleep_time == 0)
             continue;
 
-        rc = WaitForSingleObject(TIME_hWakeEvent, sleep_time);
-        if (rc != WAIT_TIMEOUT && rc != WAIT_OBJECT_0)
-        {   
-            FIXME("Unexpected error %d(%d) in timer thread\n", rc, GetLastError());
-            break;
+        while ((ret = poll(&pfd, 1, sleep_time)) < 0)
+        {
+            if (errno != EINTR && errno != EAGAIN)
+            {
+                ERR("Unexpected error in poll: %s(%d)\n", strerror(errno), errno);
+                goto end;
+            }
         }
+
+        if (ret > 0) 
+            do {
+                ret = read(TIME_fdWake[0], readme, sizeof(*readme));
+            } while (ret > 0);
     }
+
+    end:
     TRACE("Exiting main winmm thread\n");
     return 0;
 }
@@ -261,10 +274,21 @@ static DWORD CALLBACK TIME_MMSysTimeThread(LPVOID arg)
 void	TIME_MMTimeStart(void)
 {
     if (!TIME_hMMTimer) {
-	TIME_TimersList = NULL;
-        TIME_hWakeEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
+        TIME_TimersList = NULL;
+
+        if (pipe(TIME_fdWake) < 0)
+        {
+            TIME_fdWake[0] = TIME_fdWake[1] = -1;
+            ERR("Cannot create pipe: %s\n", strerror(errno));
+        }
+        else
+        {
+            fcntl(TIME_fdWake[0], F_SETFL, O_NONBLOCK);
+            fcntl(TIME_fdWake[1], F_SETFL, O_NONBLOCK);
+        }
+
         TIME_TimeToDie = FALSE;
-	TIME_hMMTimer = CreateThread(NULL, 0, TIME_MMSysTimeThread, &WINMM_IData, 0, NULL);
+        TIME_hMMTimer = CreateThread(NULL, 0, TIME_MMSysTimeThread, &WINMM_IData, 0, NULL);
         SetThreadPriority(TIME_hMMTimer, THREAD_PRIORITY_TIME_CRITICAL);
         InitializeCriticalSection(&TIME_cbcrst);
         TIME_cbcrst.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": WINMM.TIME_cbcrst");
@@ -277,16 +301,18 @@ void	TIME_MMTimeStart(void)
 void	TIME_MMTimeStop(void)
 {
     if (TIME_hMMTimer) {
-
+        const char a='a';
         TIME_TimeToDie = TRUE;
-        SetEvent(TIME_hWakeEvent);
 
+        write(TIME_fdWake[1], &a, sizeof(a));
         /* FIXME: in the worst case, we're going to wait 65 seconds here :-( */
-	WaitForSingleObject(TIME_hMMTimer, INFINITE);
+        WaitForSingleObject(TIME_hMMTimer, INFINITE);
 
-	CloseHandle(TIME_hMMTimer);
-	CloseHandle(TIME_hWakeEvent);
-	TIME_hMMTimer = 0;
+        CloseHandle(TIME_hMMTimer);
+        close(TIME_fdWake[0]);
+        close(TIME_fdWake[1]);
+        TIME_fdWake[0] = TIME_fdWake[1] = -1;
+        TIME_hMMTimer = 0;
         TIME_cbcrst.DebugInfo->Spare[0] = 0;
         DeleteCriticalSection(&TIME_cbcrst);
         TIME_TimersList = NULL;
@@ -317,6 +343,7 @@ WORD	TIME_SetEventInternal(UINT wDelay, UINT wResol,
     WORD 		wNewID = 0;
     LPWINE_TIMERENTRY	lpNewTimer;
     LPWINE_TIMERENTRY	lpTimer;
+    const char c = 'c';
 
     TRACE("(%u, %u, %p, %08X, %04X);\n", wDelay, wResol, lpFunc, dwUser, wFlags);
 
@@ -352,7 +379,7 @@ WORD	TIME_SetEventInternal(UINT wDelay, UINT wResol,
     LeaveCriticalSection(&WINMM_IData.cs);
 
     /* Wake the service thread in case there is work to be done */
-    SetEvent(TIME_hWakeEvent);
+    write(TIME_fdWake[1], &c, sizeof(c));
 
     TRACE("=> %u\n", wNewID + 1);
 
@@ -377,19 +404,33 @@ MMRESULT WINAPI timeSetEvent(UINT wDelay, UINT wResol, LPTIMECALLBACK lpFunc,
  */
 MMRESULT WINAPI timeKillEvent(UINT wID)
 {
-    LPWINE_TIMERENTRY   lpSelf = NULL, *lpTimer;
+    LPWINE_TIMERENTRY lpSelf = NULL, *lpTimer;
+    BOOL cb;
 
     TRACE("(%u)\n", wID);
+
+    /* 16 bit code can call this, but it then we can't obtain the TIME_cbcrst
+     * cb = processing thread in callback ?
+     */
     EnterCriticalSection(&WINMM_IData.cs);
+    cb = !TryEnterCriticalSection(&TIME_cbcrst);
+
     /* remove WINE_TIMERENTRY from list */
     for (lpTimer = &TIME_TimersList; *lpTimer; lpTimer = &(*lpTimer)->lpNext) {
-	if (wID == (*lpTimer)->wTimerID) {
+        if (wID == (*lpTimer)->wTimerID) {
             lpSelf = *lpTimer;
             /* unlink timer of id 'wID' */
-            *lpTimer = (*lpTimer)->lpNext;
-	    break;
-	}
+            break;
+        }
     }
+
+    if (!cb && lpSelf) /* Not in callback */
+        goto destroy_noerr;
+    if (lpSelf && !(lpSelf->wFlags & TIME_KILL_SYNCHRONOUS))
+        lpSelf->wFlags |= TIME_DESTROY;
+    if (!cb)
+        LeaveCriticalSection(&TIME_cbcrst);
+
     LeaveCriticalSection(&WINMM_IData.cs);
 
     if (!lpSelf)
@@ -397,11 +438,24 @@ MMRESULT WINAPI timeKillEvent(UINT wID)
         WARN("wID=%u is not a valid timer ID\n", wID);
         return MMSYSERR_INVALPARAM;
     }
+
     if (lpSelf->wFlags & TIME_KILL_SYNCHRONOUS)
-        EnterCriticalSection(&TIME_cbcrst);
+    {
+        /* If we obtain both locks, thread isn't running since it always holds at least one */
+        EnterCriticalSection(&WINMM_IData.cs);
+        if (TryEnterCriticalSection(&TIME_cbcrst))
+            goto destroy_noerr;
+        Sleep(1);
+        LeaveCriticalSection(&WINMM_IData.cs);
+    }
+
+    return TIMERR_NOERROR;
+
+    destroy_noerr:
+    *lpTimer = (*lpTimer)->lpNext;
     HeapFree(GetProcessHeap(), 0, lpSelf);
-    if (lpSelf->wFlags & TIME_KILL_SYNCHRONOUS)
-        LeaveCriticalSection(&TIME_cbcrst);
+    LeaveCriticalSection(&TIME_cbcrst);
+    LeaveCriticalSection(&WINMM_IData.cs);
     return TIMERR_NOERROR;
 }
 
@@ -464,15 +518,6 @@ MMRESULT WINAPI timeEndPeriod(UINT wPeriod)
  */
 DWORD WINAPI timeGetTime(void)
 {
-#if defined(COMMENTOUTPRIORTODELETING)
-    DWORD       count;
-
-    /* FIXME: releasing the win16 lock here is a temporary hack (I hope)
-     * that lets mciavi32.dll run correctly
-     */
-    if (pFnReleaseThunkLock) pFnReleaseThunkLock(&count);
-    if (pFnRestoreThunkLock) pFnRestoreThunkLock(count);
-#endif
-
+    TRACE("\n");
     return GetTickCount();
 }
-- 
1.4.4.2



More information about the wine-patches mailing list