mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2025-02-15 20:48:00 +01:00
rewrite xwmutex to use a single thread
This commit is contained in:
parent
896ef84230
commit
5347762300
3 changed files with 80 additions and 68 deletions
|
@ -12,6 +12,7 @@ LOCAL_LDLIBS += -llog
|
|||
|
||||
ifeq ($(BUILD_TARGET),debug)
|
||||
LOCAL_DEBUG = -DMEM_DEBUG -DDEBUG -DENABLE_LOGGING -Wno-unused-variable
|
||||
LOCAL_DEBUG += -DWAIT_ALL_SECS=3
|
||||
endif
|
||||
LOCAL_DEFINES += \
|
||||
$(LOCAL_DEBUG) \
|
||||
|
|
|
@ -22,102 +22,113 @@
|
|||
#ifdef DEBUG
|
||||
#include <unistd.h>
|
||||
|
||||
#include "dllist.h"
|
||||
|
||||
// #define WAIT_ALL_SECS 3
|
||||
/* #define WAIT_ALL_SECS to enable checking for ALL mutexes, but don't do that
|
||||
on Android because it kills the app (because locks in mpool are so
|
||||
frequent, I suspect). I'll keep it on on Linux for testing, but even there
|
||||
it limits how many games I can run at once. I really need a cleverer
|
||||
implementation where a single thread checks everything....
|
||||
/* #define WAIT_ALL_SECS to enable checking for ALL mutexes. Better to define
|
||||
it in a platform's Makefile than here.
|
||||
*/
|
||||
|
||||
/* What is this? It's my attempt to, on debug builds and for all or only
|
||||
chosen mutexes, get an assertion when the app doesn't get a lock in the
|
||||
specified number of seconds. It's complicated because I want it to run on
|
||||
Android, which doesn't have pthread_cancel().
|
||||
/* Take 2: A single thread runs forever, sleeping 1 second at a time. On
|
||||
waking it walks a list of pending mutexes, and asserts that none has
|
||||
expired. */
|
||||
|
||||
It works by spawining a thread immediately before calling
|
||||
pthread_mutex_lock(). If the lock succeeds, a flag is set so that the
|
||||
thread won't raise an alarm. If the thread wakes from its sleep to find
|
||||
that that flag isn't yet set, assertion failure...
|
||||
static pthread_t sCheckThread = 0;
|
||||
static pthread_mutex_t sCheckMutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static DLHead* sCheckList = NULL;
|
||||
|
||||
The tricky part is how to share state between the checker thread and its
|
||||
parent (that's calling pthread_mutex_lock().) Normally the parent might
|
||||
pass a pointer to something on the stack to the thread proc, but normally
|
||||
the stack frame will be long gone while the thread proc still wants to use
|
||||
it. So: the flag that the checker thread will check lives in the thread's
|
||||
own frame. The closure it's passed by the parent lets it set a pointer to
|
||||
that flag so the parent can see it (the closure struct being in the
|
||||
caller's scope.) So that the parent doesn't get out of pthread_mutex_lock()
|
||||
before that pointer is set up, it busy-waits on its being set. Sue me: it's
|
||||
debug code, and only active when set up for a particular mutex.
|
||||
*/
|
||||
typedef struct _CheckListData {
|
||||
XP_U32 currentTime;
|
||||
int count; /* for dev/logging only */
|
||||
} CheckListData;
|
||||
|
||||
typedef struct _WaitState {
|
||||
const char* caller;
|
||||
typedef struct _CheckThreadData {
|
||||
DLHead links;
|
||||
XP_U32 expiryTime;
|
||||
const char* file;
|
||||
const char* caller;
|
||||
int lineNo;
|
||||
XP_U16 waitSecs;
|
||||
XP_Bool* flagLoc;
|
||||
} WaitState;
|
||||
} CheckThreadData;
|
||||
|
||||
static void*
|
||||
checkLockProc( void* closure )
|
||||
static ForEachAct
|
||||
checkListProcLocked( const DLHead* elem, void* closure )
|
||||
{
|
||||
WaitState* wsp = (WaitState*)closure;
|
||||
const XP_UCHAR* file = wsp->file;
|
||||
const XP_UCHAR* caller = wsp->caller;
|
||||
XP_U16 waitSecs = wsp->waitSecs;
|
||||
int lineNo = wsp->lineNo;
|
||||
XP_Bool setMe = XP_FALSE; /* caller will change on success */
|
||||
XP_ASSERT( !wsp->flagLoc );
|
||||
wsp->flagLoc = &setMe; /* tells busy-waiting caller to run */
|
||||
|
||||
sleep( waitSecs );
|
||||
if ( !setMe ) {
|
||||
XP_LOGFF( "failed to get mutex in %d secs (caller: %s(), line %d in %s)",
|
||||
waitSecs, caller, lineNo, file );
|
||||
CheckListData* cld = (CheckListData*)closure;
|
||||
CheckThreadData* ctd = (CheckThreadData*)elem;
|
||||
if( cld->currentTime > ctd->expiryTime ) {
|
||||
XP_LOGFF( "FAIL: %s() on line %d in %s unable to lock mutex",
|
||||
ctd->caller, ctd->lineNo, ctd->file );
|
||||
XP_ASSERT(0);
|
||||
}
|
||||
++cld->count;
|
||||
return FEA_OK;
|
||||
}
|
||||
|
||||
static void*
|
||||
checkProc( void* XP_UNUSED(closure) )
|
||||
{
|
||||
for ( int ii = 0; ; ++ii ) {
|
||||
sleep(1);
|
||||
pthread_mutex_lock( &sCheckMutex );
|
||||
CheckListData cld = { .currentTime = (XP_U32)time(NULL), };
|
||||
(void)dll_map( sCheckList, checkListProcLocked, NULL, &cld );
|
||||
pthread_mutex_unlock( &sCheckMutex );
|
||||
/* Don't log from this thread on Android.
|
||||
PENDING what's the #ifdef to check? Add one?
|
||||
XP_LOGFF( "pass %d: checked %d pending", ii, cld.count );
|
||||
*/
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
addCheckee( CheckThreadData* ctd )
|
||||
{
|
||||
pthread_mutex_lock( &sCheckMutex );
|
||||
if ( 0 == sCheckThread ) {
|
||||
(void)pthread_create( &sCheckThread, NULL, checkProc, NULL );
|
||||
}
|
||||
sCheckList = dll_insert( sCheckList, &ctd->links, NULL );
|
||||
pthread_mutex_unlock( &sCheckMutex );
|
||||
}
|
||||
|
||||
static void
|
||||
removeCheckee( CheckThreadData* ctd )
|
||||
{
|
||||
pthread_mutex_lock( &sCheckMutex );
|
||||
XP_ASSERT( !!sCheckList );
|
||||
XP_ASSERT( 0 != sCheckThread );
|
||||
sCheckList = dll_remove( sCheckList, &ctd->links );
|
||||
pthread_mutex_unlock( &sCheckMutex );
|
||||
}
|
||||
|
||||
void
|
||||
mtx_lock_prv( MutexState* state, XP_U16 waitSecs,
|
||||
const char* file, const char* caller, int lineNo )
|
||||
{
|
||||
if ( 0 == waitSecs ) {
|
||||
waitSecs =state->waitSecs;
|
||||
waitSecs = state->waitSecs;
|
||||
}
|
||||
|
||||
WaitState ws = {
|
||||
.waitSecs = waitSecs,
|
||||
.file = file,
|
||||
.caller = caller,
|
||||
.lineNo = lineNo,
|
||||
};
|
||||
CheckThreadData ctd = {};
|
||||
if ( 0 < waitSecs ) {
|
||||
XP_ASSERT( !ws.flagLoc );
|
||||
pthread_t waitThread;
|
||||
(void)pthread_create( &waitThread, NULL, checkLockProc, &ws );
|
||||
int count = 0;
|
||||
while ( !ws.flagLoc ) {
|
||||
usleep(500); /* wait for thread to start */
|
||||
if ( 0 == (++count%10) ) {
|
||||
// XP_LOGFF( "count %d; flagLoc still null", count );
|
||||
}
|
||||
}
|
||||
ctd.expiryTime = waitSecs + (XP_U32)time(NULL),
|
||||
ctd.file = file;
|
||||
ctd.caller = caller;
|
||||
ctd.lineNo = lineNo;
|
||||
addCheckee( &ctd );
|
||||
}
|
||||
|
||||
pthread_mutex_lock( &state->mutex );
|
||||
if ( 0 < ws.waitSecs ) {
|
||||
*ws.flagLoc = XP_TRUE;
|
||||
|
||||
if ( 0 < waitSecs ) {
|
||||
removeCheckee( &ctd );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
mtx_unlock_prv( MutexState* state, XP_U16 XP_UNUSED(waitSecs),
|
||||
const char* XP_UNUSED(caller) )
|
||||
mtx_unlock_prv( MutexState* state )
|
||||
{
|
||||
// XP_LOGFF( "(caller=%s, waitSecs=%d)", caller, waitSecs );
|
||||
pthread_mutex_unlock( &state->mutex );
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -27,13 +27,13 @@
|
|||
|
||||
void mtx_lock_prv(MutexState* state, XP_U16 waitSecs, const char* file,
|
||||
const char* caller, int lineNo);
|
||||
void mtx_unlock_prv(MutexState* state, XP_U16 waitSecs, const char* caller);
|
||||
void mtx_unlock_prv(MutexState* state);
|
||||
# define WITH_MUTEX_CHECKED(STATE, SECS) { \
|
||||
MutexState* _state = (STATE); \
|
||||
XP_U16 _waitSecs = (SECS); \
|
||||
mtx_lock_prv(_state, _waitSecs, __FILE__, __func__, __LINE__)
|
||||
# define WITH_MUTEX(STATE) WITH_MUTEX_CHECKED(STATE, 0)
|
||||
# define END_WITH_MUTEX() mtx_unlock_prv(_state, _waitSecs, __func__); \
|
||||
# define END_WITH_MUTEX() mtx_unlock_prv(_state); \
|
||||
}
|
||||
#else
|
||||
# define WITH_MUTEX(STATE) { \
|
||||
|
|
Loading…
Add table
Reference in a new issue