Mercurial > sdl-ios-xcode
view src/timer/SDL_timer.c @ 4105:84882a89ca50 SDL-1.2
Date: Thu, 27 Dec 2007 07:38:25 +0000
From: John Bartholomew
Subject: [SDL] SDL Semaphore implementation broken on Windows?
Hi,
Over the past couple of days, I've been battling with SDL, SDL_Mixer and SMPEG to try to find an audio hang bug. I believe I've found the problem, which I think is a race condition inside SDL's semaphore implementation (at least the Windows implementation). The semaphore code uses Windows' built in semaphore functions, but it also maintains a separate count value. This count value is updated with bare increment and decrement operations in SemPost and SemWaitTimeout - no locking primitives to protect them.
In tracking down the apparent audio bug, I found that at some point a semaphore's count value was being decremented to -1, which is clearly not a valid value for it to take.
I'm still not certain exactly what sequence of operations is occuring for this to happen, but I believe that overall it's a race condition between a thread calling SemPost (which increments the count) and the thread on the other end calling SemWait (which decrements it).
I will try to make a test case to verify this, but I'm not sure if I'll be able to (threading errors being difficult to reproduce even in the best circumstances).
However, assuming this is the cause of my problems, there is a very
simple fix:
Windows provides InterlockedIncrement() and InterlockedDecrement()
functions to perform increments and decrements which are guaranteed to be atomic. So the fix is in thread/win32/SDL_syssem.c: replace occurrences of --sem->count with InterlockedDecrement(&sem->count); and replace occurrences of ++sem->count with InterlockedIncrement(&sem->count);
This is using SDL v1.2.12, built with VC++ 2008 Express, running on a
Core 2 duo processor.
author | Sam Lantinga <slouken@libsdl.org> |
---|---|
date | Fri, 28 Dec 2007 22:05:17 +0000 |
parents | e02263385643 |
children | 782fd950bd46 c121d94672cb a1b03ba2fcd0 |
line wrap: on
line source
/* SDL - Simple DirectMedia Layer Copyright (C) 1997-2006 Sam Lantinga This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Sam Lantinga slouken@libsdl.org */ #include "SDL_config.h" #include "SDL_timer.h" #include "SDL_timer_c.h" #include "SDL_mutex.h" #include "SDL_systimer.h" /* #define DEBUG_TIMERS */ int SDL_timer_started = 0; int SDL_timer_running = 0; /* Data to handle a single periodic alarm */ Uint32 SDL_alarm_interval = 0; SDL_TimerCallback SDL_alarm_callback; /* Data used for a thread-based timer */ static int SDL_timer_threaded = 0; struct _SDL_TimerID { Uint32 interval; SDL_NewTimerCallback cb; void *param; Uint32 last_alarm; struct _SDL_TimerID *next; }; static SDL_TimerID SDL_timers = NULL; static SDL_mutex *SDL_timer_mutex; static volatile SDL_bool list_changed = SDL_FALSE; /* Set whether or not the timer should use a thread. This should not be called while the timer subsystem is running. */ int SDL_SetTimerThreaded(int value) { int retval; if ( SDL_timer_started ) { SDL_SetError("Timer already initialized"); retval = -1; } else { retval = 0; SDL_timer_threaded = value; } return retval; } int SDL_TimerInit(void) { int retval; retval = 0; if ( SDL_timer_started ) { SDL_TimerQuit(); } if ( ! SDL_timer_threaded ) { retval = SDL_SYS_TimerInit(); } if ( SDL_timer_threaded ) { SDL_timer_mutex = SDL_CreateMutex(); } if ( retval == 0 ) { SDL_timer_started = 1; } return(retval); } void SDL_TimerQuit(void) { SDL_SetTimer(0, NULL); if ( SDL_timer_threaded < 2 ) { SDL_SYS_TimerQuit(); } if ( SDL_timer_threaded ) { SDL_DestroyMutex(SDL_timer_mutex); SDL_timer_mutex = NULL; } SDL_timer_started = 0; SDL_timer_threaded = 0; } void SDL_ThreadedTimerCheck(void) { Uint32 now, ms; SDL_TimerID t, prev, next; SDL_bool removed; SDL_mutexP(SDL_timer_mutex); list_changed = SDL_FALSE; now = SDL_GetTicks(); for ( prev = NULL, t = SDL_timers; t; t = next ) { removed = SDL_FALSE; ms = t->interval - SDL_TIMESLICE; next = t->next; if ( (int)(now - t->last_alarm) > (int)ms ) { struct _SDL_TimerID timer; if ( (now - t->last_alarm) < t->interval ) { t->last_alarm += t->interval; } else { t->last_alarm = now; } #ifdef DEBUG_TIMERS printf("Executing timer %p (thread = %d)\n", t, SDL_ThreadID()); #endif timer = *t; SDL_mutexV(SDL_timer_mutex); ms = timer.cb(timer.interval, timer.param); SDL_mutexP(SDL_timer_mutex); if ( list_changed ) { /* Abort, list of timers modified */ /* FIXME: what if ms was changed? */ break; } if ( ms != t->interval ) { if ( ms ) { t->interval = ROUND_RESOLUTION(ms); } else { /* Remove timer from the list */ #ifdef DEBUG_TIMERS printf("SDL: Removing timer %p\n", t); #endif if ( prev ) { prev->next = next; } else { SDL_timers = next; } SDL_free(t); --SDL_timer_running; removed = SDL_TRUE; } } } /* Don't update prev if the timer has disappeared */ if ( ! removed ) { prev = t; } } SDL_mutexV(SDL_timer_mutex); } static SDL_TimerID SDL_AddTimerInternal(Uint32 interval, SDL_NewTimerCallback callback, void *param) { SDL_TimerID t; t = (SDL_TimerID) SDL_malloc(sizeof(struct _SDL_TimerID)); if ( t ) { t->interval = ROUND_RESOLUTION(interval); t->cb = callback; t->param = param; t->last_alarm = SDL_GetTicks(); t->next = SDL_timers; SDL_timers = t; ++SDL_timer_running; list_changed = SDL_TRUE; } #ifdef DEBUG_TIMERS printf("SDL_AddTimer(%d) = %08x num_timers = %d\n", interval, (Uint32)t, SDL_timer_running); #endif return t; } SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_NewTimerCallback callback, void *param) { SDL_TimerID t; if ( ! SDL_timer_mutex ) { if ( SDL_timer_started ) { SDL_SetError("This platform doesn't support multiple timers"); } else { SDL_SetError("You must call SDL_Init(SDL_INIT_TIMER) first"); } return NULL; } if ( ! SDL_timer_threaded ) { SDL_SetError("Multiple timers require threaded events!"); return NULL; } SDL_mutexP(SDL_timer_mutex); t = SDL_AddTimerInternal(interval, callback, param); SDL_mutexV(SDL_timer_mutex); return t; } SDL_bool SDL_RemoveTimer(SDL_TimerID id) { SDL_TimerID t, prev = NULL; SDL_bool removed; removed = SDL_FALSE; SDL_mutexP(SDL_timer_mutex); /* Look for id in the linked list of timers */ for (t = SDL_timers; t; prev=t, t = t->next ) { if ( t == id ) { if(prev) { prev->next = t->next; } else { SDL_timers = t->next; } SDL_free(t); --SDL_timer_running; removed = SDL_TRUE; list_changed = SDL_TRUE; break; } } #ifdef DEBUG_TIMERS printf("SDL_RemoveTimer(%08x) = %d num_timers = %d thread = %d\n", (Uint32)id, removed, SDL_timer_running, SDL_ThreadID()); #endif SDL_mutexV(SDL_timer_mutex); return removed; } /* Old style callback functions are wrapped through this */ static Uint32 SDLCALL callback_wrapper(Uint32 ms, void *param) { SDL_TimerCallback func = (SDL_TimerCallback) param; return (*func)(ms); } int SDL_SetTimer(Uint32 ms, SDL_TimerCallback callback) { int retval; #ifdef DEBUG_TIMERS printf("SDL_SetTimer(%d)\n", ms); #endif retval = 0; if ( SDL_timer_threaded ) { SDL_mutexP(SDL_timer_mutex); } if ( SDL_timer_running ) { /* Stop any currently running timer */ if ( SDL_timer_threaded ) { while ( SDL_timers ) { SDL_TimerID freeme = SDL_timers; SDL_timers = SDL_timers->next; SDL_free(freeme); } SDL_timer_running = 0; list_changed = SDL_TRUE; } else { SDL_SYS_StopTimer(); SDL_timer_running = 0; } } if ( ms ) { if ( SDL_timer_threaded ) { if ( SDL_AddTimerInternal(ms, callback_wrapper, (void *)callback) == NULL ) { retval = -1; } } else { SDL_timer_running = 1; SDL_alarm_interval = ms; SDL_alarm_callback = callback; retval = SDL_SYS_StartTimer(); } } if ( SDL_timer_threaded ) { SDL_mutexV(SDL_timer_mutex); } return retval; }