diff --git a/xwords4/common/device.c b/xwords4/common/device.c index f5cdd1fa7..7ed8316eb 100644 --- a/xwords4/common/device.c +++ b/xwords4/common/device.c @@ -29,6 +29,7 @@ #include "nli.h" #include "dbgutil.h" #include "timers.h" +#include "xwmutex.h" #ifdef DEBUG # define MAGIC_INITED 0x8283413F @@ -73,7 +74,13 @@ typedef struct _DevCtxt { XP_U16 devCount; WSData* webSendData; XP_U32 mWebSendKey; - pthread_mutex_t webSendMutex; + MutexState webSendMutex; + + struct { + MutexState mutex; + cJSON* msgs; /* pending acks saved here */ + TimerKey key; + } ackTimer; PhoniesDataCodes* pd; @@ -538,41 +545,64 @@ dispatchMsgs( XW_DUtilCtxt* dutil, XWEnv xwe, XP_U8 proto, XWStreamCtxt* stream, } } +static void +onAckSendTimer( void* closure, XWEnv xwe, XP_Bool fired ) +{ + XP_LOGFF( "(fired: %s)", boolToStr(fired) ); + XW_DUtilCtxt* dutil = (XW_DUtilCtxt*)closure; + DevCtxt* dc = load( dutil, xwe ); + cJSON* ackMsgs; + WITH_MUTEX( &dc->ackTimer.mutex ); + ackMsgs = dc->ackTimer.msgs; + dc->ackTimer.msgs = NULL; + dc->ackTimer.key = 0; + END_WITH_MUTEX(); + + XP_ASSERT( 0 < cJSON_GetArraySize( ackMsgs ) ); + + if ( fired ) { + cJSON* params = cJSON_CreateObject(); + cJSON_AddItemToObject( params, "msgs", ackMsgs ); + dutil_sendViaWeb( dutil, xwe, 0, "ack2", params ); + cJSON_Delete( params ); + } else { + XP_LOGFF( "Dropping ack messages -- but should store them!" ); + cJSON_Delete( ackMsgs ); + } +} + +static void +setAckSendTimerLocked( XW_DUtilCtxt* dutil, XWEnv xwe, DevCtxt* dc ) +{ + if ( 0 == dc->ackTimer.key ) { + XP_U32 inWhenMS = 10 * 1000; + dc->ackTimer.key = tmr_set( dutil, xwe, inWhenMS, onAckSendTimer, + dutil ); + XP_ASSERT( 0 != dc->ackTimer.key ); + } +} + static void ackMQTTMsg( XW_DUtilCtxt* dutil, XWEnv xwe, const XP_UCHAR* topic, XP_U32 gameID, const XP_U8* buf, XP_U16 len ) { - cJSON* params = cJSON_CreateObject(); -#if 1 - cJSON* msgs = cJSON_CreateArray(); - /* This belongs in a loop */ - { - cJSON* msg = cJSON_CreateObject(); - cJSON_AddStringToObject( msg, "topic", topic ); - - Md5SumBuf sb; - dutil_md5sum( dutil, xwe, buf, len, &sb ); - cJSON_AddStringToObject( msg, "sum", sb.buf ); - - cJSON_AddNumberToObject( msg, "gid", gameID ); - - cJSON_AddItemToArray(msgs, msg); - } - cJSON_AddItemToObject(params, "msgs", msgs ); - - dutil_sendViaWeb( dutil, xwe, 0, "ack2", params ); -#else - cJSON_AddStringToObject( params, "topic", topic ); + cJSON* msg = cJSON_CreateObject(); + cJSON_AddStringToObject( msg, "topic", topic ); Md5SumBuf sb; dutil_md5sum( dutil, xwe, buf, len, &sb ); - cJSON_AddStringToObject( params, "sum", sb.buf ); + cJSON_AddStringToObject( msg, "sum", sb.buf ); - cJSON_AddNumberToObject( params, "gid", gameID ); + cJSON_AddNumberToObject( msg, "gid", gameID ); - dutil_sendViaWeb( dutil, xwe, 0, "ack", params ); -#endif - cJSON_Delete( params ); + DevCtxt* dc = load( dutil, xwe ); + WITH_MUTEX( &dc->ackTimer.mutex ); + if ( !dc->ackTimer.msgs ) { + dc->ackTimer.msgs = cJSON_CreateArray(); + } + cJSON_AddItemToArray( dc->ackTimer.msgs, msg ); + setAckSendTimerLocked( dutil, xwe, dc ); + END_WITH_MUTEX(); } void @@ -674,14 +704,14 @@ popForKey( XW_DUtilCtxt* dutil, XWEnv xwe, XP_U32 key ) WSData* item = NULL; DevCtxt* dc = load( dutil, xwe ); - pthread_mutex_lock( &dc->webSendMutex ); + WITH_MUTEX(&dc->webSendMutex); GetByKeyData gbkd = { .resultKey = key, }; dc->webSendData = (WSData*)dll_map( &dc->webSendData->links, getByKeyProc, NULL, &gbkd ); item = gbkd.found; - pthread_mutex_unlock( &dc->webSendMutex ); + END_WITH_MUTEX(); XP_LOGFFV( "(key: %d) => %p", key, item ); return item; } @@ -690,11 +720,11 @@ static XP_U32 addWithKey( XW_DUtilCtxt* dutil, XWEnv xwe, WSData* wsdp ) { DevCtxt* dc = load( dutil, xwe ); - pthread_mutex_lock( &dc->webSendMutex ); + WITH_MUTEX(&dc->webSendMutex); wsdp->resultKey = ++dc->mWebSendKey; dc->webSendData = (WSData*) dll_insert( &dc->webSendData->links, &wsdp->links, NULL ); - pthread_mutex_unlock( &dc->webSendMutex ); + END_WITH_MUTEX(); XP_LOGFFV( "(%p) => %d", wsdp, wsdp->resultKey ); return wsdp->resultKey; } @@ -752,9 +782,9 @@ dvc_onWebSendResult( XW_DUtilCtxt* dutil, XWEnv xwe, XP_U32 resultKey, static void freeWSState( XW_DUtilCtxt* dutil, DevCtxt* dc ) { - pthread_mutex_lock( &dc->webSendMutex ); + WITH_MUTEX( &dc->webSendMutex ); dll_removeAll( &dc->webSendData->links, delWSDatum, dutil ); - pthread_mutex_unlock( &dc->webSendMutex ); + END_WITH_MUTEX(); } typedef struct _PhoniesMapState { @@ -1147,7 +1177,9 @@ dvc_init( XW_DUtilCtxt* dutil, XWEnv xwe ) DevCtxt* dc = dutil->devCtxt = XP_CALLOC( dutil->mpool, sizeof(*dc) ); dc->webSendData = NULL; dc->mWebSendKey = 0; - pthread_mutex_init( &dc->webSendMutex, NULL ); + + mtx_init( &dc->webSendMutex, XP_FALSE ); + mtx_init( &dc->ackTimer.mutex, XP_FALSE ); loadPhoniesData( dutil, xwe, dc ); @@ -1161,10 +1193,10 @@ void dvc_cleanup( XW_DUtilCtxt* dutil, XWEnv xwe ) { DevCtxt* dc = freePhonyState( dutil, xwe ); - freeWSState( dutil, dc ); - pthread_mutex_destroy( &dc->webSendMutex ); + mtx_destroy( &dc->webSendMutex ); + mtx_destroy( &dc->ackTimer.mutex ); XP_FREEP( dutil->mpool, &dc ); } diff --git a/xwords4/common/movestak.c b/xwords4/common/movestak.c index b9d5b56d0..1477595ea 100644 --- a/xwords4/common/movestak.c +++ b/xwords4/common/movestak.c @@ -300,7 +300,7 @@ pushEntry( StackCtxt* stack, const StackEntry* entry ) #ifdef DEBUG_HASHING XP_U32 origHash = stack_getHash( stack ); - StackEntry prevTop; + StackEntry prevTop = {}; if ( 1 < stack->nPlayers && stack_getNthEntry( stack, stack->nEntries - 1, &prevTop ) ) { XP_ASSERT( stack->inDuplicateMode || prevTop.playerNum != entry->playerNum ); diff --git a/xwords4/common/stats.c b/xwords4/common/stats.c index 6e92d718b..fdef70567 100644 --- a/xwords4/common/stats.c +++ b/xwords4/common/stats.c @@ -33,8 +33,8 @@ typedef struct StatsState { static const XP_UCHAR* STATtoStr(STAT stat); static XP_U32* loadCounts( XW_DUtilCtxt* dutil, XWEnv xwe ); -static void storeCounts( XW_DUtilCtxt* dutil, XWEnv xwe ); -static void setStoreTimer( XW_DUtilCtxt* dutil, XWEnv xwe ); +static void storeCountsLocked( XW_DUtilCtxt* dutil, XWEnv xwe ); +static void setStoreTimerLocked( XW_DUtilCtxt* dutil, XWEnv xwe ); void sts_init( XW_DUtilCtxt* dutil ) @@ -66,7 +66,7 @@ sts_increment( XW_DUtilCtxt* dutil, XWEnv xwe, STAT stat ) } ++ss->statsVals[stat]; - setStoreTimer( dutil, xwe ); + setStoreTimerLocked( dutil, xwe ); END_WITH_MUTEX(); } } @@ -104,7 +104,7 @@ sts_clearAll( XW_DUtilCtxt* dutil, XWEnv xwe ) ss->statsVals = XP_CALLOC( dutil->mpool, sizeof(*ss->statsVals) * STAT_NSTATS ); - storeCounts( dutil, xwe ); + storeCountsLocked( dutil, xwe ); END_WITH_MUTEX(); } @@ -133,7 +133,7 @@ STATtoStr(STAT stat) } static void -storeCounts( XW_DUtilCtxt* dutil, XWEnv xwe ) +storeCountsLocked( XW_DUtilCtxt* dutil, XWEnv xwe ) { StatsState* ss = dutil->statsState; XP_ASSERT( !!ss ); @@ -188,7 +188,7 @@ onStoreTimer( void* closure, XWEnv xwe, XP_Bool fired ) XP_ASSERT( !!ss ); WITH_MUTEX( &ss->mutex ); - storeCounts( dutil, xwe ); + storeCountsLocked( dutil, xwe ); ss->timerSet = XP_FALSE; END_WITH_MUTEX(); LOG_RETURN_VOID(); @@ -196,7 +196,7 @@ onStoreTimer( void* closure, XWEnv xwe, XP_Bool fired ) #endif static void -setStoreTimer( XW_DUtilCtxt* dutil, XWEnv xwe ) +setStoreTimerLocked( XW_DUtilCtxt* dutil, XWEnv xwe ) { #ifdef DUTIL_TIMERS StatsState* ss = dutil->statsState; diff --git a/xwords4/linux/scripts/netGamesTest.py b/xwords4/linux/scripts/netGamesTest.py index 5221d0e3f..01bb41386 100755 --- a/xwords4/linux/scripts/netGamesTest.py +++ b/xwords4/linux/scripts/netGamesTest.py @@ -245,7 +245,8 @@ class Device(): # called by thread proc def _launchProc(self): assert not self.endTime - self.endTime = datetime.datetime.now() + datetime.timedelta(seconds = 5) + self.endTime = datetime.datetime.now() + \ + datetime.timedelta(seconds = self.args.MIN_APP_LIFE) args = [ self.script, '--close-stdin', '--skip-user-errors' ] if not self.args.USE_GTK: args.append('--curses') @@ -913,6 +914,8 @@ def mkParser(): parser.add_argument('--timer-seconds', dest='TIMER_SECS', default=10, type=int, help='Enable game timer with game this many seconds long') + parser.add_argument('--min-app-life', dest='MIN_APP_LIFE', default=5, type=int, + help='Minimum number of seconds app will run after each launch') parser.add_argument('--with-sms', dest = 'WITH_SMS', action = 'store_true') parser.add_argument('--without-sms', dest = 'WITH_SMS', default = False, action = 'store_false')