etqw-sdk/source/idlib/sys/threading/SysSignal_posix.cpp

148 lines
4.3 KiB
C++

// Copyright (C) 2007 Id Software, Inc.
//
#include "../../precompiled.h"
#include "SysSignal.h"
#include <sys/time.h>
#include <time.h>
/*
=============
sdSysSignal::Create
=============
*/
void sdSysSignal::Create( signalHandle_t &handle ) {
pthread_mutexattr_t attr;
pthread_mutexattr_init( &attr );
pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_ERRORCHECK );
pthread_mutex_init( &handle.mutex, &attr );
pthread_mutexattr_destroy( &attr );
pthread_cond_init( &handle.cond, NULL );
handle.signaled = false;
handle.waiting = false;
}
/*
=============
sdSysSignal::Destroy
=============
*/
void sdSysSignal::Destroy( signalHandle_t& handle ) {
pthread_mutex_destroy( &handle.mutex );
pthread_cond_destroy( &handle.cond );
}
/*
=============
sdSysSignal::Set
=============
*/
void sdSysSignal::Set( signalHandle_t& handle ) {
pthread_mutex_lock( &handle.mutex );
if ( handle.waiting ) {
pthread_cond_signal( &handle.cond );
} else {
// emulate windows behaviour: if no thread is waiting, leave the signal on so next Wait() keeps going
handle.signaled = true;
}
pthread_mutex_unlock( &handle.mutex );
}
/*
=============
sdSysSignal::Clear
=============
*/
void sdSysSignal::Clear( signalHandle_t& handle ) {
assert( false ); // unused
handle.signaled = false;
}
/*
=============
sdSysSignal::Wait
=============
*/
bool sdSysSignal::Wait( signalHandle_t& handle, int timeout ) {
int rc;
struct timespec ts;
struct timeval tp;
pthread_mutex_lock( &handle.mutex );
assert( !handle.waiting ); // Wait() from multiple threads?
if ( handle.signaled ) {
// emulate windows behaviour: signal has been raised already. clear and keep going
handle.signaled = false;
} else {
handle.waiting = true;
if ( timeout == sdSignal::WAIT_INFINITE ) {
pthread_cond_wait( &handle.cond, &handle.mutex );
} else {
rc = gettimeofday( &tp, NULL );
assert( rc == 0 );
ts.tv_sec = tp.tv_sec + timeout / 1000;
ts.tv_nsec = tp.tv_usec * 1000 + ( timeout % 1000 ) * 1000000;
pthread_cond_timedwait( &handle.cond, &handle.mutex, &ts );
}
handle.waiting = false;
}
pthread_mutex_unlock( &handle.mutex );
return true;
}
/*
=============
sdSysSignal::SignalAndWait
the windows implementation uses SignalObjectAndWait to obtain an atomic set and wait operation
a race condition is possible if implemented with just a Set followed by a Wait
for instance in the initial use case of this code, for synchronization of the bot thread and the game thread
signal == botSignal, handle = gameSignal
the bot signal gets raised, the game thread runs, triggers the game thread signal and starts waiting on the bot signal again
all before the bot thread starts waiting on the game signal. when it does, both threads are deadlocked
we use the Wait() implementation, and only fire up botSignal once we've acquired the gameSignal mutex
the game thread always triggers gameSignal before (possibly) waiting on botSignal
so if it tries to do that in between the botSignal firing and the gameSignal wait,
it will have to wait on the gameSignal mutex until pthread_cond_wait releases it upon waiting on gameSignal condition
in that case that will just trigger the condition right away, with no deadlock possibility
see pthread_cond_wait documentation for precise details
(in particular the "releasing mutex upon waiting" part)
=============
*/
bool sdSysSignal::SignalAndWait( signalHandle_t& signal, signalHandle_t& handle, int timeout ) {
int rc;
struct timespec ts;
struct timeval tp;
pthread_mutex_lock( &handle.mutex );
assert( !handle.waiting ); // Wait() from multiple threads?
Set( signal );
if ( handle.signaled ) {
// emulate windows behaviour: signal has been raised already. clear and keep going
handle.signaled = false;
} else {
handle.waiting = true;
if ( timeout == sdSignal::WAIT_INFINITE ) {
pthread_cond_wait( &handle.cond, &handle.mutex );
} else {
rc = gettimeofday( &tp, NULL );
assert( rc == 0 );
ts.tv_sec = tp.tv_sec + timeout / 1000;
ts.tv_nsec = tp.tv_usec * 1000 + ( timeout % 1000 ) * 1000000;
pthread_cond_timedwait( &handle.cond, &handle.mutex, &ts );
}
handle.waiting = false;
}
pthread_mutex_unlock( &handle.mutex );
return true;
}