- CModel3::StopThreads was rather gracelessly killing threads while they were waiting on their semaphores, which works okay Windows but not on Mac OS-X where it was preventing Supermodel from exiting properly. Hence, have instead altered StopThreads to signal to threads that they should exit and wait until until they have all done so. This is much better and is what I should have done the first time around had I not been so lazy :-)!

- Also fixed a small race condition in WakeSoundBoardThread.  Previously if it happened to be called just before the sound board thread had finished processing a frame's worth of audio but before it had got around to waiting on the sync condition variable then the wake notification would be lost.
This commit is contained in:
Nik Henson 2012-07-23 20:35:10 +00:00
parent f1e93bcd8f
commit 108ac64de8
2 changed files with 128 additions and 48 deletions

View file

@ -2169,6 +2169,10 @@ bool CModel3::StartThreads(void)
if (notifySync == NULL) if (notifySync == NULL)
goto ThreadError; goto ThreadError;
// Reset thread flags
pauseThreads = false;
stopThreads = false;
// Create PPC main board thread, if multi-threading GPU // Create PPC main board thread, if multi-threading GPU
if (g_Config.gpuMultiThreaded) if (g_Config.gpuMultiThreaded)
{ {
@ -2216,8 +2220,8 @@ bool CModel3::PauseThreads(void)
if (!notifyLock->Lock()) if (!notifyLock->Lock())
goto ThreadError; goto ThreadError;
// Wait for all threads to finish their processing // Let threads know that they should pause and wait for all of them to do so
pausedThreads = true; pauseThreads = true;
while (ppcBrdThreadRunning || sndBrdThreadRunning || drvBrdThreadRunning) while (ppcBrdThreadRunning || sndBrdThreadRunning || drvBrdThreadRunning)
{ {
if (!notifySync->Wait(notifyLock)) if (!notifySync->Wait(notifyLock))
@ -2245,7 +2249,7 @@ bool CModel3::ResumeThreads(void)
goto ThreadError; goto ThreadError;
// Let all threads know that they can continue running // Let all threads know that they can continue running
pausedThreads = false; pauseThreads = false;
// Leave notify critical section // Leave notify critical section
if (!notifyLock->Unlock()) if (!notifyLock->Unlock())
@ -2258,26 +2262,73 @@ ThreadError:
return false; return false;
} }
void CModel3::StopThreads(void) bool CModel3::StopThreads(void)
{ {
if (!startedThreads) if (!startedThreads)
return; return true;
// If sound board thread is unsync'd then remove audio callback // If sound board thread is unsync'd then remove audio callback
if (!syncSndBrdThread) if (!syncSndBrdThread)
SetAudioCallback(NULL, NULL); SetAudioCallback(NULL, NULL);
// Pause threads so that can safely delete thread objects // Enter notify critical section
PauseThreads(); if (!notifyLock->Lock())
goto ThreadError;
// Let threads know that they should pause and wait for all of them to do so
pauseThreads = true;
while (ppcBrdThreadRunning || sndBrdThreadRunning || drvBrdThreadRunning)
{
if (!notifySync->Wait(notifyLock))
goto ThreadError;
}
// Now let threads know that they should exit
stopThreads = true;
// Leave notify critical section
if (!notifyLock->Unlock())
goto ThreadError;
// Resume each thread in turn and wait for them to exit
if (ppcBrdThread != NULL)
{
if (ppcBrdThreadSync->Post())
ppcBrdThread->Wait();
}
if (sndBrdThread != NULL)
{
if (syncSndBrdThread)
{
if (sndBrdThreadSync->Post())
sndBrdThread->Wait();
}
else
{
if (WakeSoundBoardThread())
sndBrdThread->Wait();
}
}
if (drvBrdThread != NULL)
{
if (drvBrdThreadSync->Post())
drvBrdThread->Wait();
}
// Delete all thread and synchronization objects
DeleteThreadObjects(); DeleteThreadObjects();
startedThreads = false; startedThreads = false;
return true;
ThreadError:
ErrorLog("Threading error in CModel3::StopThreads: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
g_Config.multiThreaded = false;
return false;
} }
void CModel3::DeleteThreadObjects(void) void CModel3::DeleteThreadObjects(void)
{ {
// Delete (which in turn kills) PPC main board, sound board and drive board threads // Delete PPC main board, sound board and drive board threads
// Note that can do so here safely because threads will always be waiting on their semaphores when this method is called
if (ppcBrdThread != NULL) if (ppcBrdThread != NULL)
{ {
delete ppcBrdThread; delete ppcBrdThread;
@ -2347,40 +2398,37 @@ int CModel3::StartMainBoardThread(void *data)
{ {
// Call method on CModel3 to run PPC main board thread // Call method on CModel3 to run PPC main board thread
CModel3 *model3 = (CModel3*)data; CModel3 *model3 = (CModel3*)data;
model3->RunMainBoardThread(); return model3->RunMainBoardThread();
return 0;
} }
int CModel3::StartSoundBoardThread(void *data) int CModel3::StartSoundBoardThread(void *data)
{ {
// Call method on CModel3 to run sound board thread (unsync'd) // Call method on CModel3 to run sound board thread (unsync'd)
CModel3 *model3 = (CModel3*)data; CModel3 *model3 = (CModel3*)data;
model3->RunSoundBoardThread(); return model3->RunSoundBoardThread();
return 0;
} }
int CModel3::StartSoundBoardThreadSyncd(void *data) int CModel3::StartSoundBoardThreadSyncd(void *data)
{ {
// Call method on CModel3 to run sound board thread (sync'd) // Call method on CModel3 to run sound board thread (sync'd)
CModel3 *model3 = (CModel3*)data; CModel3 *model3 = (CModel3*)data;
model3->RunSoundBoardThreadSyncd(); return model3->RunSoundBoardThreadSyncd();
return 0;
} }
int CModel3::StartDriveBoardThread(void *data) int CModel3::StartDriveBoardThread(void *data)
{ {
// Call method on CModel3 to run drive board thread // Call method on CModel3 to run drive board thread
CModel3 *model3 = (CModel3*)data; CModel3 *model3 = (CModel3*)data;
model3->RunDriveBoardThread(); return model3->RunDriveBoardThread();
return 0;
} }
void CModel3::RunMainBoardThread(void) int CModel3::RunMainBoardThread(void)
{ {
for (;;) for (;;)
{ {
bool wait = true; bool wait = true;
while (wait) bool exit = false;
while (wait && !exit)
{ {
// Wait on PPC main board thread semaphore // Wait on PPC main board thread semaphore
if (!ppcBrdThreadSync->Wait()) if (!ppcBrdThreadSync->Wait())
@ -2390,8 +2438,10 @@ void CModel3::RunMainBoardThread(void)
if (!notifyLock->Lock()) if (!notifyLock->Lock())
goto ThreadError; goto ThreadError;
// Check threads not paused // Check threads are not being stopped or paused
if (!pausedThreads) if (stopThreads)
exit = true;
else if (!pauseThreads)
{ {
wait = false; wait = false;
ppcBrdThreadRunning = true; ppcBrdThreadRunning = true;
@ -2401,6 +2451,8 @@ void CModel3::RunMainBoardThread(void)
if (!notifyLock->Unlock()) if (!notifyLock->Unlock())
goto ThreadError; goto ThreadError;
} }
if (exit)
return 0;
// Process a single frame for PPC main board // Process a single frame for PPC main board
RunMainBoardFrame(); RunMainBoardFrame();
@ -2423,6 +2475,7 @@ void CModel3::RunMainBoardThread(void)
ThreadError: ThreadError:
ErrorLog("Threading error in RunMainBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError()); ErrorLog("Threading error in RunMainBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
g_Config.multiThreaded = false; g_Config.multiThreaded = false;
return 1;
} }
void CModel3::AudioCallback(void *data) void CModel3::AudioCallback(void *data)
@ -2432,40 +2485,47 @@ void CModel3::AudioCallback(void *data)
model3->WakeSoundBoardThread(); model3->WakeSoundBoardThread();
} }
void CModel3::WakeSoundBoardThread(void) bool CModel3::WakeSoundBoardThread(void)
{ {
// Enter sound board notify critical section // Enter sound board notify critical section
if (!sndBrdNotifyLock->Lock()) if (!sndBrdNotifyLock->Lock())
goto ThreadError; goto ThreadError;
// Signal to sound board thread that it should start processing again // Signal to sound board thread that it should start processing again
sndBrdWakeNotify = true;
if (!sndBrdNotifySync->Signal()) if (!sndBrdNotifySync->Signal())
goto ThreadError; goto ThreadError;
// Exit sound board notify critical section // Exit sound board notify critical section
if (!sndBrdNotifyLock->Unlock()) if (!sndBrdNotifyLock->Unlock())
goto ThreadError; goto ThreadError;
return; return true;
ThreadError: ThreadError:
ErrorLog("Threading error in WakeSoundBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError()); ErrorLog("Threading error in WakeSoundBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
g_Config.multiThreaded = false; g_Config.multiThreaded = false;
return false;
} }
void CModel3::RunSoundBoardThread(void) int CModel3::RunSoundBoardThread(void)
{ {
for (;;) for (;;)
{ {
bool wait = true; bool wait = true;
while (wait) bool exit = false;
while (wait && !exit)
{ {
// Enter sound board notify critical section // Enter sound board notify critical section
if (!sndBrdNotifyLock->Lock()) if (!sndBrdNotifyLock->Lock())
goto ThreadError; goto ThreadError;
// Wait for notification from audio callback // Wait for notification from audio callback
if (!sndBrdNotifySync->Wait(sndBrdNotifyLock)) while (!sndBrdWakeNotify)
goto ThreadError; {
if (!sndBrdNotifySync->Wait(sndBrdNotifyLock))
goto ThreadError;
}
sndBrdWakeNotify = false;
// Exit sound board notify critical section // Exit sound board notify critical section
if (!sndBrdNotifyLock->Unlock()) if (!sndBrdNotifyLock->Unlock())
@ -2475,8 +2535,10 @@ void CModel3::RunSoundBoardThread(void)
if (!notifyLock->Lock()) if (!notifyLock->Lock())
goto ThreadError; goto ThreadError;
// Check threads not paused // Check threads are not being stopped or paused
if (!pausedThreads) if (stopThreads)
exit = true;
else if (!pauseThreads)
{ {
wait = false; wait = false;
sndBrdThreadRunning = true; sndBrdThreadRunning = true;
@ -2486,8 +2548,10 @@ void CModel3::RunSoundBoardThread(void)
if (!notifyLock->Unlock()) if (!notifyLock->Unlock())
goto ThreadError; goto ThreadError;
} }
if (exit)
return 0;
// Keep processing frames until paused or audio buffer is full // Keep processing frames until pausing or audio buffer is full
while (true) while (true)
{ {
// Enter main notify critical section // Enter main notify critical section
@ -2495,7 +2559,7 @@ void CModel3::RunSoundBoardThread(void)
if (!notifyLock->Lock()) if (!notifyLock->Lock())
goto ThreadError; goto ThreadError;
paused = pausedThreads; paused = pauseThreads;
// Leave main notify critical section // Leave main notify critical section
if (!notifyLock->Unlock()) if (!notifyLock->Unlock())
@ -2524,14 +2588,16 @@ void CModel3::RunSoundBoardThread(void)
ThreadError: ThreadError:
ErrorLog("Threading error in RunSoundBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError()); ErrorLog("Threading error in RunSoundBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
g_Config.multiThreaded = false; g_Config.multiThreaded = false;
return 1;
} }
void CModel3::RunSoundBoardThreadSyncd(void) int CModel3::RunSoundBoardThreadSyncd(void)
{ {
for (;;) for (;;)
{ {
bool wait = true; bool wait = true;
while (wait) bool exit = false;
while (wait && !exit)
{ {
// Wait on sound board thread semaphore // Wait on sound board thread semaphore
if (!sndBrdThreadSync->Wait()) if (!sndBrdThreadSync->Wait())
@ -2541,8 +2607,10 @@ void CModel3::RunSoundBoardThreadSyncd(void)
if (!notifyLock->Lock()) if (!notifyLock->Lock())
goto ThreadError; goto ThreadError;
// Check threads not paused // Check threads are not being stopped or paused
if (!pausedThreads) if (stopThreads)
exit = true;
else if (!pauseThreads)
{ {
wait = false; wait = false;
sndBrdThreadRunning = true; sndBrdThreadRunning = true;
@ -2552,6 +2620,8 @@ void CModel3::RunSoundBoardThreadSyncd(void)
if (!notifyLock->Unlock()) if (!notifyLock->Unlock())
goto ThreadError; goto ThreadError;
} }
if (exit)
return 0;
// Process a single frame for sound board // Process a single frame for sound board
RunSoundBoardFrame(); RunSoundBoardFrame();
@ -2574,14 +2644,16 @@ void CModel3::RunSoundBoardThreadSyncd(void)
ThreadError: ThreadError:
ErrorLog("Threading error in RunSoundBoardThreadSyncd: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError()); ErrorLog("Threading error in RunSoundBoardThreadSyncd: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
g_Config.multiThreaded = false; g_Config.multiThreaded = false;
return 1;
} }
void CModel3::RunDriveBoardThread(void) int CModel3::RunDriveBoardThread(void)
{ {
for (;;) for (;;)
{ {
bool wait = true; bool wait = true;
while (wait) bool exit = false;
while (wait && !exit)
{ {
// Wait on drive board thread semaphore // Wait on drive board thread semaphore
if (!drvBrdThreadSync->Wait()) if (!drvBrdThreadSync->Wait())
@ -2591,8 +2663,10 @@ void CModel3::RunDriveBoardThread(void)
if (!notifyLock->Lock()) if (!notifyLock->Lock())
goto ThreadError; goto ThreadError;
// Check threads not paused // Check threads are not being stopped or paused
if (!pausedThreads) if (stopThreads)
exit = true;
else if (!pauseThreads)
{ {
wait = false; wait = false;
drvBrdThreadRunning = true; drvBrdThreadRunning = true;
@ -2602,6 +2676,8 @@ void CModel3::RunDriveBoardThread(void)
if (!notifyLock->Unlock()) if (!notifyLock->Unlock())
goto ThreadError; goto ThreadError;
} }
if (exit)
return 0;
// Process a single frame for drive board // Process a single frame for drive board
RunDriveBoardFrame(); RunDriveBoardFrame();
@ -2624,6 +2700,7 @@ void CModel3::RunDriveBoardThread(void)
ThreadError: ThreadError:
ErrorLog("Threading error in RunDriveBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError()); ErrorLog("Threading error in RunDriveBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
g_Config.multiThreaded = false; g_Config.multiThreaded = false;
return 1;
} }
void CModel3::Reset(void) void CModel3::Reset(void)
@ -3139,7 +3216,8 @@ CModel3::CModel3(void)
securityPtr = 0; securityPtr = 0;
startedThreads = false; startedThreads = false;
pausedThreads = false; pauseThreads = false;
stopThreads = false;
ppcBrdThread = NULL; ppcBrdThread = NULL;
sndBrdThread = NULL; sndBrdThread = NULL;
drvBrdThread = NULL; drvBrdThread = NULL;

View file

@ -361,7 +361,7 @@ private:
void RunDriveBoardFrame(void); // Runs drive board for a frame void RunDriveBoardFrame(void); // Runs drive board for a frame
bool StartThreads(void); // Starts all threads bool StartThreads(void); // Starts all threads
void StopThreads(void); // Stops all threads bool StopThreads(void); // Stops all threads
void DeleteThreadObjects(void); // Deletes all threads and synchronization objects void DeleteThreadObjects(void); // Deletes all threads and synchronization objects
static int StartMainBoardThread(void *data); // Callback to start PPC main board thread static int StartMainBoardThread(void *data); // Callback to start PPC main board thread
@ -371,11 +371,11 @@ private:
static void AudioCallback(void *data); // Audio buffer callback static void AudioCallback(void *data); // Audio buffer callback
void WakeSoundBoardThread(void); // Used by audio callback to wake sound board thread when not sync'd with PPC thread bool WakeSoundBoardThread(void); // Used by audio callback to wake sound board thread (when not sync'd with render thread)
void RunMainBoardThread(void); // Runs PPC main board thread (sync'd in step with render thread) int RunMainBoardThread(void); // Runs PPC main board thread (sync'd in step with render thread)
void RunSoundBoardThread(void); // Runs sound board thread (unsync'd with render thread, ie at full speed) int RunSoundBoardThread(void); // Runs sound board thread (not sync'd in step with render thread, ie running at full speed)
void RunSoundBoardThreadSyncd(void); // Runs sound board thread (sync'd in step with render thread) int RunSoundBoardThreadSyncd(void); // Runs sound board thread (sync'd in step with render thread)
void RunDriveBoardThread(void); // Runs drive board thread (sync'd in step with render thread) int RunDriveBoardThread(void); // Runs drive board thread (sync'd in step with render thread)
// Game and hardware information // Game and hardware information
const struct GameInfo *Game; const struct GameInfo *Game;
@ -421,7 +421,8 @@ private:
// Multiple threading // Multiple threading
bool gpusReady; // True if GPUs are ready to render bool gpusReady; // True if GPUs are ready to render
bool startedThreads; // True if threads have been created and started bool startedThreads; // True if threads have been created and started
bool pausedThreads; // True if threads are currently paused bool pauseThreads; // True if threads should pause
bool stopThreads; // True if threads should stop
bool syncSndBrdThread; // True if sound board thread should be sync'd in step with render thread bool syncSndBrdThread; // True if sound board thread should be sync'd in step with render thread
CThread *ppcBrdThread; // PPC main board thread CThread *ppcBrdThread; // PPC main board thread
CThread *sndBrdThread; // Sound board thread CThread *sndBrdThread; // Sound board thread
@ -430,6 +431,7 @@ private:
bool ppcBrdThreadDone; // Flag to indicate PPC main board thread has finished processing bool ppcBrdThreadDone; // Flag to indicate PPC main board thread has finished processing
bool sndBrdThreadRunning; // Flag to indicate sound board thread is currently processing bool sndBrdThreadRunning; // Flag to indicate sound board thread is currently processing
bool sndBrdThreadDone; // Flag to indicate sound board thread has finished processing bool sndBrdThreadDone; // Flag to indicate sound board thread has finished processing
bool sndBrdWakeNotify; // Flag to indicate that sound board thread has been woken by audio callback (when not sync'd with render thread)
bool drvBrdThreadRunning; // Flag to indicate drive board thread is currently processing bool drvBrdThreadRunning; // Flag to indicate drive board thread is currently processing
bool drvBrdThreadDone; // Flag to indicate drive board thread has finished processing bool drvBrdThreadDone; // Flag to indicate drive board thread has finished processing