From 85d484a8816fa170a8e0df9537e21aa1c60f51b2 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 20 Jun 2011 18:13:15 -0700 Subject: [PATCH] major mod to deal with devices that fail to receive ACK and then reconnect. I was putting both (i.e. the same device twice) in the same game. Now I detect this based on the seed being duplicated and treat the device as having failed to ACK then proceed with the CONNECT as if it were new. Tested pretty heavily but only with two-device games. --- xwords4/relay/cref.cpp | 195 +++++++++++++++++++++++++------------- xwords4/relay/cref.h | 29 ++++-- xwords4/relay/crefmgr.cpp | 37 +++++--- xwords4/relay/crefmgr.h | 12 ++- xwords4/relay/dbmgr.cpp | 121 +++++++++++++++++++++-- xwords4/relay/dbmgr.h | 14 ++- xwords4/relay/states.cpp | 26 ++--- xwords4/relay/states.h | 16 ++-- xwords4/relay/xwrelay.cpp | 17 ++-- 9 files changed, 333 insertions(+), 134 deletions(-) diff --git a/xwords4/relay/cref.cpp b/xwords4/relay/cref.cpp index 54cff7ebd..1de6f5df5 100644 --- a/xwords4/relay/cref.cpp +++ b/xwords4/relay/cref.cpp @@ -86,14 +86,13 @@ CookieRef::ReInit( const char* cookie, const char* connName, CookieID id, m_cookie = cookie==NULL?"":cookie; m_connName = connName==NULL?"":connName; m_cookieID = id; - m_curState = XWS_INITED; + m_curState = XWS_EMPTY; m_nPlayersSought = nPlayers; m_nPlayersHere = nAlreadyHere; m_locking_thread = 0; m_starttime = uptime(); m_in_handleEvents = false; m_langCode = langCode; - m_nPendingAcks = 0; if ( RelayConfigs::GetConfigs()->GetValueFor( "SEND_DELAY_MILLIS", &m_delayMicros ) ) { @@ -104,8 +103,13 @@ CookieRef::ReInit( const char* cookie, const char* connName, CookieID id, RelayConfigs::GetConfigs()->GetValueFor( "HEARTBEAT", &m_heatbeat ); logf( XW_LOGINFO, "initing cref for cookie %s, connName %s", m_cookie.c_str(), m_connName.c_str() ); -} + unsigned int ii; + for ( ii = 0; ii < sizeof(m_timers)/sizeof(m_timers[0]); ++ii ) { + m_timers[ii].m_this = NULL; + m_timers[ii].m_hid = ii + 1; + } +} CookieRef::CookieRef( const char* cookie, const char* connName, CookieID id, int langCode, int nPlayersT, int nAlreadyHere ) @@ -176,19 +180,31 @@ CookieRef::Unlock() { } bool -CookieRef::_Connect( int socket, int nPlayersH, int nPlayersS, int seed ) +CookieRef::_Connect( int socket, int nPlayersH, int nPlayersS, int seed, + bool seenSeed ) { bool connected = false; - if ( AlreadyHere( seed, socket ) ) { - connected = true; /* but drop the packet */ - /* } else if ( AlreadyHere( seed, -1 ) ) { */ - /* dupe packet on different socket; need host record */ - } else if ( CRefMgr::Get()->Associate( socket, this ) ) { - pushConnectEvent( socket, nPlayersH, nPlayersS, seed ); - handleEvents(); - connected = HasSocket_locked( socket ); - } else { - logf( XW_LOGINFO, "dropping connect event; already connected" ); + HostID prevHostID = HOST_ID_NONE; + bool alreadyHere = AlreadyHere( seed, socket, &prevHostID ); + + if ( alreadyHere ) { + if ( seenSeed ) { /* we need to get rid of the current entry, then + proceed as if this were a new connection */ + assert( HOST_ID_NONE != prevHostID ); + postDropDevice( prevHostID ); + } else { + connected = true; /* but drop the packet */ + } + } + + if ( !connected ) { + if ( CRefMgr::Get()->Associate( socket, this ) ) { + pushConnectEvent( socket, nPlayersH, nPlayersS, seed ); + handleEvents(); + connected = HasSocket_locked( socket ); + } else { + logf( XW_LOGINFO, "dropping connect event; already connected" ); + } } return connected; } @@ -212,7 +228,6 @@ CookieRef::_Reconnect( int socket, HostID hid, int nPlayersH, int nPlayersS, void CookieRef::_HandleAck( HostID hostID ) { - assert( m_nPendingAcks > 0 && m_nPendingAcks <= 4 ); CRefEvent evt( XWE_GOTONEACK ); evt.u.ack.srcID = hostID; m_eventQueue.push_back( evt ); @@ -288,22 +303,19 @@ CookieRef::SocketForHost( HostID dest ) } bool -CookieRef::AlreadyHere( unsigned short seed, int socket ) +CookieRef::AlreadyHere( unsigned short seed, int socket, HostID* prevHostID ) { logf( XW_LOGINFO, "%s(seed=%x,socket=%d)", __func__, seed, socket ); bool here = false; vector::iterator iter; for ( iter = m_sockets.begin(); iter != m_sockets.end(); ++iter ) { - if ( iter->m_seed == seed ) { /* client already registered */ - if ( iter->m_socket == socket ) { - /* dup packet */ - here = true; - } else { - logf( XW_LOGINFO, "%s: seeds match; nuking existing record" - " for socket %d b/c assumed closed", __func__, - iter->m_socket ); - m_sockets.erase( iter ); + here = iter->m_seed == seed; /* client already registered */ + if ( here ) { + if ( iter->m_socket != socket ) { /* not just a dupe packet */ + logf( XW_LOGINFO, "%s: seeds match; socket %d assumed closed", + __func__, iter->m_socket ); + *prevHostID = iter->m_hostID; } break; } @@ -370,11 +382,10 @@ CookieRef::removeSocket( int socket ) if ( iter->m_socket == socket ) { if ( iter->m_ackPending ) { logf( XW_LOGINFO, - "Never got ack; removing %d players from DB", - iter->m_nPlayersH ); - DBMgr::Get()->RmDevice( ConnName(), iter->m_hostID ); + "Never got ack; removing hid %d from DB", + iter->m_hostID ); + DBMgr::Get()->RmDeviceByHid( ConnName(), iter->m_hostID ); m_nPlayersHere -= iter->m_nPlayersH; - --m_nPendingAcks; } m_sockets.erase(iter); --count; @@ -577,22 +588,24 @@ CookieRef::handleEvents() switch( takeAction ) { - case XWA_SEND_CONNRSP: - if ( increasePlayerCounts( &evt, false ) ) { - setAllConnectedTimer(); - sendResponse( &evt, takeAction != XWA_SEND_1ST_RERSP ); - setAckTimer(); + case XWA_SEND_CONNRSP: + { + HostID hid; + if ( increasePlayerCounts( &evt, false, &hid ) ) { + setAllConnectedTimer(); + sendResponse( &evt, takeAction != XWA_SEND_1ST_RERSP ); + setAckTimer( hid ); + } } break; - case XWA_NOTEACKCHECK: case XWA_NOTEACK: - modPending( &evt, true ); + updateAck( evt.u.ack.srcID, true ); postCheckAllHere(); break; case XWA_DROPDEVICE: - modPending( &evt, false ); + updateAck( evt.u.ack.srcID, false ); break; /* case XWA_SEND_1ST_RERSP: */ @@ -603,7 +616,7 @@ CookieRef::handleEvents() /* break; */ case XWA_SEND_RERSP: - increasePlayerCounts( &evt, true ); + increasePlayerCounts( &evt, true, NULL ); sendResponse( &evt, false ); sendAnyStored( &evt ); postCheckAllHere(); @@ -708,6 +721,16 @@ CookieRef::handleEvents() } m_curState = nextState; + +#ifdef DEBUG + if ( XWS_EMPTY == m_curState ) { + assert( 0 == m_sockets.size() ); + + int nTotal, nHere; + GetPlayerCounts( ConnName(), &nTotal, &nHere ); + assert( 0 == nHere ); + } +#endif } else { logf( XW_LOGERROR, "Killing cref b/c unable to find transition " "from %s on event %s", stateString(m_curState), @@ -779,7 +802,7 @@ CookieRef::send_stored_messages( HostID dest, int socket ) } /* send_stored_messages */ bool -CookieRef::increasePlayerCounts( CRefEvent* evt, bool reconn ) +CookieRef::increasePlayerCounts( CRefEvent* evt, bool reconn, HostID* hidp ) { int nPlayersH = evt->u.con.nPlayersH; int socket = evt->u.con.socket; @@ -808,9 +831,12 @@ CookieRef::increasePlayerCounts( CRefEvent* evt, bool reconn ) } evt->u.con.srcID = DBMgr::Get()->AddDevice( ConnName(), evt->u.con.srcID, - nPlayersH, seed ); + nPlayersH, seed, reconn ); HostID hostid = evt->u.con.srcID; + if ( NULL != hidp ) { + *hidp = hostid; + } /* first add the rec here, whether it'll get ack'd or not */ logf( XW_LOGINFO, "%s: remembering pair: hostid=%x, " @@ -831,36 +857,53 @@ CookieRef::increasePlayerCounts( CRefEvent* evt, bool reconn ) } /* increasePlayerCounts */ void -CookieRef::modPending( const CRefEvent* evt, bool keep ) +CookieRef::updateAck( HostID hostID, bool keep ) { - HostID hostID = evt->u.ack.srcID; + assert( hostID >= HOST_ID_SERVER ); + assert( hostID <= 4 ); + int socket = 0; + + cancelAckTimer( hostID ); + vector::iterator iter; for ( iter = m_sockets.begin(); iter != m_sockets.end(); ++iter ) { if ( iter->m_ackPending && iter->m_hostID == hostID ) { - --m_nPendingAcks; if ( keep ) { iter->m_ackPending = false; + DBMgr::Get()->NoteAckd( ConnName(), hostID ); } else { - DBMgr::Get()->RmDevice( ConnName(), iter->m_hostID ); - m_sockets.erase( iter ); + socket = iter->m_socket; } break; } } + + if ( 0 != socket ) { + removeSocket( socket ); + } + printSeeds(__func__); } void CookieRef::postCheckAllHere() { - if ( m_nPendingAcks == 0 && DBMgr::Get()->GameFull( ConnName() ) ) { - /* && m_nPlayersHere == m_nPlayersSought ) { /\* complete! *\/ */ + if ( DBMgr::Get()->AllDevsAckd( ConnName() ) ) { CRefEvent evt( XWE_ALLHERE ); m_eventQueue.push_back( evt ); } } +void +CookieRef::postDropDevice( HostID hostID ) +{ + CRefEvent evt( XWE_ACKTIMEOUT ); + evt.u.ack.srcID = hostID; + m_eventQueue.push_back( evt ); + handleEvents(); +} + void CookieRef::setAllConnectedTimer() { @@ -872,23 +915,39 @@ CookieRef::setAllConnectedTimer() } void -CookieRef::setAckTimer( void ) +CookieRef::setAckTimer( HostID hid ) { - logf( XW_LOGINFO, "%s()", __func__ ); + ASSERT_LOCKED(); + logf( XW_LOGINFO, "%s(%d)", __func__, hid ); + + assert( hid >= HOST_ID_SERVER ); + assert( hid <= 4 ); + --hid; + + assert( NULL == m_timers[hid].m_this ); + m_timers[hid].m_this = this; + time_t inHowLong; if ( RelayConfigs::GetConfigs()->GetValueFor( "DEVACK", &inHowLong ) ) { TimerMgr::GetTimerMgr()->SetTimer( inHowLong, - s_checkAck, this, 0 ); - ++m_nPendingAcks; + s_checkAck, &m_timers[hid], 0 ); } else { logf( XW_LOGINFO, "not setting timer" ); } } void -CookieRef::cancelAckTimer( void ) +CookieRef::cancelAckTimer( HostID hid ) { - TimerMgr::GetTimerMgr()->ClearTimer( s_checkAck, this ); + ASSERT_LOCKED(); + logf( XW_LOGINFO, "%s(%d)", __func__, hid ); + + assert( hid >= HOST_ID_SERVER ); + assert( hid <= 4 ); + --hid; + m_timers[hid].m_this = NULL; + + // TimerMgr::GetTimerMgr()->ClearTimer( s_checkAck, this ); } void @@ -1231,9 +1290,13 @@ CookieRef::s_checkAllConnected( void* closure ) /* static */ void CookieRef::s_checkAck( void* closure ) { - CookieRef* self = (CookieRef*)closure; - SafeCref scr(self); - scr.CheckNotAcked(); + AckTimer* at = (AckTimer*)closure; + CookieRef* self = at->m_this; + if ( NULL != self ) { + at->m_this = NULL; + SafeCref scr(self); + scr.CheckNotAcked( at->m_hid ); + } } void @@ -1247,14 +1310,13 @@ CookieRef::_CheckAllConnected() } void -CookieRef::_CheckNotAcked() +CookieRef::_CheckNotAcked( HostID hid ) { - logf( XW_LOGINFO, "%s", __func__ ); - if ( m_nPendingAcks > 0 ) { - CRefEvent newEvt( XWE_ACKTIMEOUT ); - m_eventQueue.push_back( newEvt ); - handleEvents(); - } + logf( XW_LOGINFO, "%s(hid=%d)", __func__, hid ); + CRefEvent newEvt( XWE_ACKTIMEOUT ); + newEvt.u.ack.srcID = hid; + m_eventQueue.push_back( newEvt ); + handleEvents(); } void @@ -1264,10 +1326,11 @@ CookieRef::printSeeds( const char* caller ) char buf[64] = {0}; vector::iterator iter; for ( iter = m_sockets.begin(); iter != m_sockets.end(); ++iter ) { - len += snprintf( &buf[len], sizeof(buf)-len, "%.4x/%d ", - iter->m_seed, iter->m_socket ); + len += snprintf( &buf[len], sizeof(buf)-len, "%.4x/%d/%c ", + iter->m_seed, iter->m_socket, + iter->m_ackPending?'a':'A' ); } - logf( XW_LOGINFO, "seeds/sockets after %s(): %s", caller, buf ); + logf( XW_LOGINFO, "seeds/sockets/ack'd after %s(): %s", caller, buf ); } void diff --git a/xwords4/relay/cref.h b/xwords4/relay/cref.h index c8c9af94a..4c5224c3c 100644 --- a/xwords4/relay/cref.h +++ b/xwords4/relay/cref.h @@ -58,6 +58,12 @@ HostRec(HostID hostID, int socket, int nPlayersH, int seed, bool ackPending ) bool m_ackPending; }; +struct AckTimer { +public: + HostID m_hid; + class CookieRef* m_this; +}; + class CookieRef { private: @@ -95,7 +101,7 @@ class CookieRef { HostID HostForSocket( int sock ); /* connect case */ - bool AlreadyHere( unsigned short seed, int socket ); + bool AlreadyHere( unsigned short seed, int socket, HostID* prevHostID ); /* reconnect case */ bool AlreadyHere( HostID hid, unsigned short seed, int socket ); @@ -110,7 +116,8 @@ class CookieRef { static void Delete( CookieID id ); static void Delete( const char* name ); - bool _Connect( int socket, int nPlayersH, int nPlayersS, int seed ); + bool _Connect( int socket, int nPlayersH, int nPlayersS, int seed, + bool seenSeed ); void _Reconnect( int socket, HostID srcID, int nPlayersH, int nPlayersS, int seed, bool gameDead ); void _HandleAck( HostID hostID ); @@ -122,9 +129,9 @@ class CookieRef { void _Forward( HostID src, HostID dest, unsigned char* buf, int buflen ); void _Remove( int socket ); void _CheckAllConnected(); - void _CheckNotAcked(); + void _CheckNotAcked( HostID hid ); - bool ShouldDie() { return m_curState == XWS_DEAD; } + bool ShouldDie() { return m_curState == XWS_EMPTY; } XW_RELAY_STATE CurState() { return m_curState; } void logf( XW_LogLevel level, const char* format, ... ); @@ -202,18 +209,21 @@ class CookieRef { void sendResponse( const CRefEvent* evt, bool initial ); void sendAnyStored( const CRefEvent* evt ); void initPlayerCounts( const CRefEvent* evt ); - bool increasePlayerCounts( CRefEvent* evt, bool reconn ); - void modPending( const CRefEvent* evt, bool keep ); + bool increasePlayerCounts( CRefEvent* evt, bool reconn, HostID* hidp ); + void updateAck( HostID hostID, bool keep ); + void dropPending( int seed ); void postCheckAllHere(); + void postDropDevice( HostID hostID ); + bool hostAlreadyHere( int seed, int socket ); void reducePlayerCounts( int socket ); void setAllConnectedTimer(); void cancelAllConnectedTimer(); - void setAckTimer(); - void cancelAckTimer(); + void setAckTimer( HostID hid ); + void cancelAckTimer( HostID hid ); void forward_or_store( const CRefEvent* evt ); void send_denied( const CRefEvent* evt, XWREASON why ); @@ -271,7 +281,8 @@ class CookieRef { int m_langCode; time_t m_starttime; - int m_nPendingAcks; + + AckTimer m_timers[4]; pthread_mutex_t m_mutex; diff --git a/xwords4/relay/crefmgr.cpp b/xwords4/relay/crefmgr.cpp index 9efb7fab2..f65abd540 100644 --- a/xwords4/relay/crefmgr.cpp +++ b/xwords4/relay/crefmgr.cpp @@ -180,7 +180,8 @@ CRefMgr::GetStats( CrefMgrInfo& mgrInfo ) info.m_langCode = cref->GetLangCode(); SafeCref sc(cref); - sc.GetHostsConnected( &info.m_hostsIds, &info.m_hostSeeds, &info.m_hostIps ); + sc.GetHostsConnected( &info.m_hostsIds, &info.m_hostSeeds, + &info.m_hostIps ); mgrInfo.m_crefInfo.push_back( info ); } @@ -231,25 +232,31 @@ CRefMgr::getFromFreeList( void ) CookieRef* CRefMgr::getMakeCookieRef( const char* cookie, HostID hid, int socket, int nPlayersH, int nPlayersT, int langCode, - int gameSeed, bool wantsPublic, - bool makePublic ) + int seed, bool wantsPublic, + bool makePublic, bool* seenSeed ) { CookieRef* cref; + CookieID cid; + char connNameBuf[MAX_CONNNAME_LEN+1] = {0}; + int alreadyHere = 0; /* We have a cookie from a new connection or from a reconnect. This may be the first time it's been seen, or there may be a game currently in - the XW_ST_CONNECTING state, or it may be a dupe of a connect packet. - If there's a game, cool. Otherwise add a new one. Pass the connName - which will be used if set, but if not set we'll be generating another - later when the game is complete. + the XW_ST_CONNECTING state, or it may be a dupe of a connect packet on + the same or a different socket. If there's a game, cool. Otherwise add + a new one. Pass the connName which will be used if set, but if not set + we'll be generating another later when the game is complete. */ - char connNameBuf[MAX_CONNNAME_LEN+1] = {0}; - int alreadyHere = 0; - CookieID cid = m_db->FindOpen( cookie, langCode, nPlayersT, nPlayersH, - wantsPublic, - connNameBuf, sizeof(connNameBuf), - &alreadyHere ); + *seenSeed = m_db->SeenSeed( cookie, seed, langCode, nPlayersT, + wantsPublic, connNameBuf, + sizeof(connNameBuf), &alreadyHere, &cid ); + if ( !*seenSeed ) { + cid = m_db->FindOpen( cookie, langCode, nPlayersT, nPlayersH, + wantsPublic, connNameBuf, sizeof(connNameBuf), + &alreadyHere ); + } + if ( cid > 0 ) { cref = getCookieRef_impl( cid ); } else { @@ -657,12 +664,14 @@ SafeCref::SafeCref( const char* cookie, int socket, int nPlayersH, int nPlayersS : m_cref( NULL ) , m_mgr( CRefMgr::Get() ) , m_isValid( false ) + , m_seenSeed( false ) { CookieRef* cref; cref = m_mgr->getMakeCookieRef( cookie, 0, socket, nPlayersH, nPlayersS, langCode, - gameSeed, wantsPublic, makePublic ); + gameSeed, wantsPublic, makePublic, + &m_seenSeed ); if ( cref != NULL ) { m_locked = cref->Lock(); m_cref = cref; diff --git a/xwords4/relay/crefmgr.h b/xwords4/relay/crefmgr.h index 08c024ecd..5ffefd4c6 100644 --- a/xwords4/relay/crefmgr.h +++ b/xwords4/relay/crefmgr.h @@ -138,7 +138,8 @@ class CRefMgr { CookieRef* getMakeCookieRef( const char* cookie, HostID hid, int socket, int nPlayersH, int nPlayersS, int langCode, int seed, - bool wantsPublic, bool makePublic ); + bool wantsPublic, bool makePublic, + bool* seenSeed ); /* reconnect case; just the stuff we don't have in db */ CookieRef* getMakeCookieRef( const char* connName, const char* cookie, @@ -219,7 +220,8 @@ class SafeCref { bool Connect( int socket, int nPlayersH, int nPlayersS, int seed ) { if ( IsValid() ) { assert( 0 != m_cref->GetCookieID() ); - return m_cref->_Connect( socket, nPlayersH, nPlayersS, seed ); + return m_cref->_Connect( socket, nPlayersH, nPlayersS, seed, + m_seenSeed ); } else { return false; } @@ -300,9 +302,9 @@ class SafeCref { m_cref->_CheckAllConnected(); } } - void CheckNotAcked() { + void CheckNotAcked( HostID hid ) { if ( IsValid() ) { - m_cref->_CheckNotAcked(); + m_cref->_CheckNotAcked( hid ); } } const char* Cookie() { @@ -366,6 +368,7 @@ class SafeCref { } bool IsValid() { return m_isValid; } + bool SeenSeed() { return m_seenSeed; } private: CookieRef* m_cref; @@ -373,6 +376,7 @@ class SafeCref { bool m_isValid; bool m_locked; bool m_dead; + bool m_seenSeed; }; /* SafeCref class */ diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index 38bf1926a..1415f2037 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -39,6 +39,8 @@ static DBMgr* s_instance = NULL; static void formatParams( char* paramValues[], int nParams, const char* fmt, char* buf, int bufLen, ... ); +static int here_less_seed( const char* seeds, int perDeviceSum, + unsigned short seed ); /* static */ DBMgr* DBMgr::Get() @@ -130,6 +132,48 @@ DBMgr::FindGame( const char* connName, char* cookieBuf, int bufLen, logf( XW_LOGINFO, "%s(%s)=>%d", __func__, connName, cid ); return cid; +} /* FindGame */ + +bool +DBMgr::SeenSeed( const char* cookie, unsigned short seed, + int langCode, int nPlayersT, bool wantsPublic, + char* connNameBuf, int bufLen, int* nPlayersHP, + CookieID* cid ) +{ + int nParams = 5; + char* paramValues[nParams]; + char buf[512]; + formatParams( paramValues, nParams, + "%s"DELIM"%d"DELIM"%d"DELIM"%d"DELIM"%s", buf, sizeof(buf), + cookie, langCode, nPlayersT, seed, + wantsPublic?"TRUE":"FALSE" ); + + const char* cmd = "SELECT cid, connName, seeds, sum_array(nPerDevice) FROM " + GAMES_TABLE + " WHERE NOT dead" + " AND room ILIKE $1" + " AND lang = $2" + " AND nTotal = $3" + " AND $4 = ANY(seeds)" + " AND $5 = pub" + " ORDER BY ctime DESC" + " LIMIT 1"; + + PGresult* result = PQexecParams( getThreadConn(), cmd, + nParams, NULL, + paramValues, + NULL, NULL, 0 ); + bool found = 1 == PQntuples( result ); + if ( found ) { + *cid = atoi( PQgetvalue( result, 0, 0 ) ); + *nPlayersHP = here_less_seed( PQgetvalue( result, 0, 2 ), + atoi( PQgetvalue( result, 0, 3 ) ), + seed ); + snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) ); + } + PQclear( result ); + logf( XW_LOGINFO, "%s(%4X)=>%s", __func__, seed, found?"true":"false" ); + return found; } CookieID @@ -174,9 +218,9 @@ DBMgr::FindOpen( const char* cookie, int lang, int nPlayersT, int nPlayersH, } /* FindOpen */ bool -DBMgr::GameFull( const char* const connName ) +DBMgr::AllDevsAckd( const char* const connName ) { - const char* cmd = "SELECT ntotal=sum_array(nperdevice) from " GAMES_TABLE + const char* cmd = "SELECT ntotal=sum_array(nperdevice) AND 'A'=ALL(ack) from " GAMES_TABLE " WHERE connName='%s'"; char query[256]; snprintf( query, sizeof(query), cmd, connName ); @@ -187,12 +231,13 @@ DBMgr::GameFull( const char* const connName ) assert( nTuples <= 1 ); bool full = 't' == PQgetvalue( result, 0, 0 )[0]; PQclear( result ); + logf( XW_LOGINFO, "%s=>%d", __func__, full ); return full; } HostID DBMgr::AddDevice( const char* connName, HostID curID, int nToAdd, - unsigned short seed ) + unsigned short seed, bool ackd ) { HostID newID = curID; @@ -208,29 +253,79 @@ DBMgr::AddDevice( const char* connName, HostID curID, int nToAdd, assert( newID <= 4 ); const char* fmt = "UPDATE " GAMES_TABLE " SET nPerDevice[%d] = %d," - " seeds[%d] = %d, mtimes[%d]='now'" + " seeds[%d] = %d, mtimes[%d]='now', ack[%d]=\'%c\'" " WHERE connName = '%s'"; char query[256]; - snprintf( query, sizeof(query), fmt, newID, nToAdd, newID, seed, newID, connName ); + snprintf( query, sizeof(query), fmt, newID, nToAdd, newID, seed, newID, + newID, ackd?'A':'a', connName ); logf( XW_LOGINFO, "%s: query: %s", __func__, query ); execSql( query ); return newID; +} /* AddDevice */ + +void +DBMgr::NoteAckd( const char* const connName, HostID id ) +{ + char query[256]; + const char* fmt = "UPDATE " GAMES_TABLE " SET ack[%d]='A'" + " WHERE connName = '%s'"; + snprintf( query, sizeof(query), fmt, id, connName ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query ); + + execSql( query ); } bool -DBMgr::RmDevice( const char* connName, HostID hid ) +DBMgr::RmDeviceByHid( const char* connName, HostID hid ) { const char* fmt = "UPDATE " GAMES_TABLE " SET nPerDevice[%d] = 0, " - "seeds[%d] = 0, mtimes[%d]='now' WHERE connName = '%s'"; + "seeds[%d] = 0, ack[%d]='-', mtimes[%d]='now' WHERE connName = '%s'"; char query[256]; - snprintf( query, sizeof(query), fmt, hid, hid, hid, connName ); + snprintf( query, sizeof(query), fmt, hid, hid, hid, hid, connName ); logf( XW_LOGINFO, "%s: query: %s", __func__, query ); return execSql( query ); } +void +DBMgr::RmDeviceBySeed( const char* const connName, unsigned short seed ) +{ + char seeds[128] = {0}; + const char* fmt = "SELECT seeds FROM " GAMES_TABLE + " WHERE connName = '%s'" + " AND %d = ANY(seeds)"; + char query[256]; + snprintf( query, sizeof(query), fmt, connName, seed ); + logf( XW_LOGINFO, "%s: query: %s", __func__, query ); + PGresult* result = PQexec( getThreadConn(), query ); + if ( 1 == PQntuples( result ) ) { + snprintf( seeds, sizeof(seeds), "%s", PQgetvalue( result, 0, 0 ) ); + } + PQclear( result ); + + if ( 0 != seeds[0] ) { + char *saveptr = NULL; + int ii; + char* str; + for ( str = seeds, ii = 0; ; str = NULL, ++ii ) { + char* tok = strtok_r( str, "{},", &saveptr ); + if ( NULL == tok ) { + break; + } else { + int asint = atoi( tok ); + if ( asint == seed ) { + RmDeviceByHid( connName, ii + 1 ); + break; + } + } + } + } else { + assert(0); /* but don't ship with this!!!! */ + } +} /* RmDeviceSeed */ + bool DBMgr::HaveDevice( const char* connName, HostID hid, int seed ) { @@ -537,7 +632,15 @@ formatParams( char* paramValues[], int nParams, const char* fmt, char* buf, } } va_end(ap); -} +} + +static int +here_less_seed( const char* seeds, int sumPerDevice, unsigned short seed ) +{ + logf( XW_LOGINFO, "%s: find %x in \"%s\", sub from \"%d\"", __func__, + seed, seeds, sumPerDevice ); + return sumPerDevice - 1; /* FIXME */ +} static void destr_function( void* conn ) diff --git a/xwords4/relay/dbmgr.h b/xwords4/relay/dbmgr.h index 30a60a10a..b7afa41f2 100644 --- a/xwords4/relay/dbmgr.h +++ b/xwords4/relay/dbmgr.h @@ -43,14 +43,22 @@ class DBMgr { CookieID FindGame( const char* connName, char* cookieBuf, int bufLen, int* langP, int* nPlayersTP, int* nPlayersHP, bool* isDead ); + + bool SeenSeed( const char* cookie, unsigned short seed, + int langCode, int nPlayersT, bool wantsPublic, + char* connNameBuf, int bufLen, int* nPlayersHP, + CookieID* cid ); + CookieID FindOpen( const char* cookie, int lang, int nPlayersT, int nPlayersH, bool wantsPublic, char* connNameBuf, int bufLen, int* nPlayersHP ); - bool GameFull( const char* const connName ); + bool AllDevsAckd( const char* const connName ); HostID AddDevice( const char* const connName, HostID curID, - int nToAdd, unsigned short seed ); - bool RmDevice( const char* const connName, HostID id ); + int nToAdd, unsigned short seed, bool unAckd ); + void NoteAckd( const char* const connName, HostID id ); + bool RmDeviceByHid( const char* const connName, HostID id ); + void RmDeviceBySeed( const char* const connName, unsigned short seed ); bool HaveDevice( const char* const connName, HostID id, int seed ); void AddCID( const char* const connName, CookieID cid ); void ClearCID( const char* connName ); diff --git a/xwords4/relay/states.cpp b/xwords4/relay/states.cpp index 20fd4165a..75c0f5066 100644 --- a/xwords4/relay/states.cpp +++ b/xwords4/relay/states.cpp @@ -61,7 +61,7 @@ typedef struct StateTable { */ static StateTable g_stateTable[] = { -{ XWS_INITED, XWE_DEVCONNECT, XWA_SEND_CONNRSP, XWS_WAITMORE }, +{ XWS_EMPTY, XWE_DEVCONNECT, XWA_SEND_CONNRSP, XWS_WAITMORE }, { XWS_WAITMORE, XWE_DEVCONNECT, XWA_SEND_CONNRSP, XWS_WAITMORE }, { XWS_WAITMORE, XWE_GOTONEACK, XWA_NOTEACK, XWS_WAITMORE }, { XWS_WAITMORE, XWE_ACKTIMEOUT, XWA_DROPDEVICE, XWS_WAITMORE }, @@ -73,6 +73,7 @@ static StateTable g_stateTable[] = { { XWS_ALLCONND, XWE_RECONNECT, XWA_SEND_RERSP, XWS_ALLCONND }, { XWS_ALLCONND, XWE_ALLHERE, XWA_NONE, XWS_ALLCONND }, { XWS_ALLCONND, XWE_REMOVESOCKET, XWA_REMOVESOCK_1, XWS_ALLCONND }, +{ XWS_ALLCONND, XWE_GOTONEACK, XWA_NONE, XWS_ALLCONND }, /* { XWS_WAITMORE, XWE_GAMEFULL, XWA_SENDALLHERE, XWS_ALLCONND }, */ /* { XWS_WAITMORE, XWE_CHECKFULL, XWA_, XWS_WAITMORE }, */ @@ -107,12 +108,14 @@ static StateTable g_stateTable[] = { /* { XWS_MSGONLY, XWE_NOMOREMSGS, XWA_NONE, XWS_DEAD }, */ /* { XWS_MSGONLY, XWE_NOMOREMSGS, XWA_NONE, XWS_DEAD }, */ -{ XWS_ANY, XWE_NOMORESOCKETS, XWA_NONE, XWS_DEAD }, -{ XWS_ANY, XWE_SHUTDOWN, XWA_SHUTDOWN, XWS_DEAD }, +{ XWS_ANY, XWE_NOMORESOCKETS, XWA_NONE, XWS_EMPTY }, +{ XWS_ANY, XWE_SHUTDOWN, XWA_SHUTDOWN, XWS_EMPTY }, +/* drop timeout (unless we're in XWS_WAITMORE; see above) */ +{ XWS_ANY, XWE_ACKTIMEOUT, XWA_NONE, XWS_SAME }, /* This doesn't make sense. Can't go straight to ALLCOND if don't have all players/devices. */ -{ XWS_INITED, XWE_RECONNECT, XWA_SEND_RERSP, XWS_WAITMORE }, +{ XWS_EMPTY, XWE_RECONNECT, XWA_SEND_RERSP, XWS_WAITMORE }, { XWS_MSGONLY, XWE_RECONNECT, XWA_SEND_RERSP, XWS_WAITMORE }, /* { XWS_MISSING, XWE_RECONNECT, XWA_SEND_RERSP, XWS_CHK_ALLHERE_2 }, */ @@ -134,7 +137,7 @@ static StateTable g_stateTable[] = { { XWS_MISSING, XWE_HEARTRCVD, XWA_NOTEHEART, XWS_MISSING }, #endif -{ XWS_INITED, XWE_DEVGONE, XWA_RMDEV, XWS_DEAD }, +{ XWS_EMPTY, XWE_DEVGONE, XWA_RMDEV, XWS_EMPTY }, { XWS_WAITMORE, XWE_DEVGONE, XWA_RMDEV, XWS_WAITMORE }, /* This should be impossible unless device allows deleting an open/connected game */ @@ -143,20 +146,20 @@ static StateTable g_stateTable[] = { { XWS_ALLCONND, XWE_GAMEDEAD, XWA_TELLGAMEDEAD, XWS_ALLCONND }, /* Connect timer */ -{ XWS_WAITMORE, XWE_CONNTIMER, XWA_TIMERDISCONN, XWS_DEAD }, +{ XWS_WAITMORE, XWE_CONNTIMER, XWA_TIMERDISCONN, XWS_EMPTY }, /* { XWS_MISSING, XWE_CONNTIMER, XWA_NONE, XWS_MISSING }, */ { XWS_ALLCONND, XWE_CONNTIMER, XWA_NONE, XWS_ALLCONND }, { XWS_WAITMORE, XWE_NOTIFYDISCON, XWA_NOTIFYDISCON, XWS_WAITMORE }, /* { XWS_ALLCONND, XWE_NOTIFYDISCON, XWA_NOTIFYDISCON, XWS_MISSING }, */ /* { XWS_MISSING, XWE_NOTIFYDISCON, XWA_NOTIFYDISCON, XWS_MISSING }, */ -{ XWS_DEAD, XWE_NOTIFYDISCON, XWA_NOTIFYDISCON, XWS_DEAD }, +{ XWS_EMPTY, XWE_NOTIFYDISCON, XWA_NOTIFYDISCON, XWS_EMPTY }, /* This is our bread-n-butter */ { XWS_ALLCONND, XWE_FORWARDMSG, XWA_FWD, XWS_ALLCONND }, /* { XWS_MISSING, XWE_FORWARDMSG, XWA_FWD, XWS_MISSING }, */ -{ XWS_DEAD, XWE_REMOVESOCKET, XWA_REMOVESOCK_1, XWS_DEAD } +{ XWS_EMPTY, XWE_REMOVESOCKET, XWA_REMOVESOCK_1, XWS_EMPTY } }; @@ -172,7 +175,8 @@ getFromTable( XW_RELAY_STATE curState, XW_RELAY_EVENT curEvent, if ( stp->stateStart == curState || stp->stateStart == XWS_ANY ) { if ( stp->stateEvent == curEvent || stp->stateEvent == XWE_ANY ) { *takeAction = stp->stateAction; - *nextState = stp->stateEnd; + *nextState = + stp->stateEnd == XWS_SAME? curState : stp->stateEnd; found = true; break; } @@ -192,11 +196,10 @@ stateString( XW_RELAY_STATE state ) switch( state ) { CASESTR(XWS_NONE); CASESTR(XWS_ANY); - CASESTR(XWS_INITED); + CASESTR(XWS_EMPTY); CASESTR(XWS_WAITMORE); CASESTR(XWS_WAITING_ACKS); CASESTR(XWS_ALLCONND); - CASESTR(XWS_DEAD); /* CASESTR(XWS_MISSING); */ CASESTR(XWS_MSGONLY); /* CASESTR(XWS_CHK_ALLHERE); */ @@ -264,7 +267,6 @@ actString( XW_RELAY_ACTION act ) CASESTR(XWA_SEND_INITRSP); CASESTR(XWA_SEND_CONNRSP); CASESTR(XWA_NOTEACK); - CASESTR(XWA_NOTEACKCHECK); /* CASESTR(XWA_ADDDEVICE); */ CASESTR(XWA_DROPDEVICE); CASESTR(XWA_SNDALLHERE_2); diff --git a/xwords4/relay/states.h b/xwords4/relay/states.h index 13336ea48..6ffeda9a0 100644 --- a/xwords4/relay/states.h +++ b/xwords4/relay/states.h @@ -27,6 +27,7 @@ typedef enum { XWS_NONE ,XWS_ANY /* wildcard */ + ,XWS_SAME /* wildcard: means use same state as current */ /* ,XWS_CHKCOUNTS_INIT */ /* from initial state, check if all players are here. Success should be an error, @@ -40,11 +41,13 @@ enum { /* ,XWS_CHK_ALLHERE_2 */ /* same as above, but triggered by a reconnect rather than a connect request */ - ,XWS_INITED /* Relay's running and the object's been - created, but nobody's signed up yet. This - is a very short-lived state since an - incoming connection is why the object was - created. */ + ,XWS_EMPTY /* Relay's running and the object's been + created, and nobody's signed up yet. The + object should never be in this state except + immediately after created, just before + deleted, or transitionally, as after a + device is removed prior to replacing its + record with another. */ ,XWS_WAITMORE /* At least one device has connected, but no packets have yet arrived to be @@ -63,8 +66,6 @@ enum { /* ,XWS_ROOMCHK */ /* do we have room for as many players as are being provided */ - - ,XWS_DEAD /* About to kill the object */ } XW_RELAY_STATE; @@ -129,7 +130,6 @@ typedef enum { // ,XWA_ADDDEVICE /* got ack, so device is in for sure */ ,XWA_NOTEACK - ,XWA_NOTEACKCHECK ,XWA_DROPDEVICE /* no ack; remove all traces of device */ ,XWA_SEND_INITRSP /* response to first to connect */ diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index 253eea105..58ad5c65d 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -314,8 +314,6 @@ processConnect( unsigned char* bufp, int bufLen, int socket ) unsigned char* end = bufp + bufLen; bool success = false; - logf( XW_LOGINFO, "%s()", __func__ ); - cookie[0] = '\0'; unsigned char flags = *bufp++; @@ -324,7 +322,7 @@ processConnect( unsigned char* bufp, int bufLen, int socket ) /* HostID srcID; */ unsigned char nPlayersH; unsigned char nPlayersT; - unsigned short gameSeed; + unsigned short seed; unsigned char langCode; unsigned char makePublic, wantsPublic; if ( readStr( &bufp, end, cookie, sizeof(cookie) ) @@ -333,10 +331,11 @@ processConnect( unsigned char* bufp, int bufLen, int socket ) /* && getNetByte( &bufp, end, &srcID ) */ && getNetByte( &bufp, end, &nPlayersH ) && getNetByte( &bufp, end, &nPlayersT ) - && getNetShort( &bufp, end, &gameSeed ) + && getNetShort( &bufp, end, &seed ) && getNetByte( &bufp, end, &langCode ) ) { - logf( XW_LOGINFO, "%s(): langCode=%d; nPlayersT=%d; wantsPublic=%d", __func__, - langCode, nPlayersT, wantsPublic ); + logf( XW_LOGINFO, "%s(): langCode=%d; nPlayersT=%d; " + "wantsPublic=%d; seed=%.4X", + __func__, langCode, nPlayersT, wantsPublic, seed ); /* Make sure second thread can't create new cref for same cookie this one just handled.*/ @@ -344,10 +343,10 @@ processConnect( unsigned char* bufp, int bufLen, int socket ) MutexLock ml( &s_newCookieLock ); SafeCref scr( cookie, socket, nPlayersH, nPlayersT, - gameSeed, langCode, wantsPublic, makePublic ); + seed, langCode, wantsPublic, makePublic ); /* nPlayersT etc could be slots in SafeCref to avoid passing here */ - success = scr.Connect( socket, nPlayersH, nPlayersT, gameSeed ); + success = scr.Connect( socket, nPlayersH, nPlayersT, seed ); } else { err = XWRELAY_ERROR_BADPROTO; } @@ -442,7 +441,7 @@ processDisconnect( unsigned char* bufp, int bufLen, int socket ) static void killSocket( int socket ) { - logf( XW_LOGINFO, "killSocket(%d)", socket ); + logf( XW_LOGINFO, "%s(%d)", __func__, socket ); CRefMgr::Get()->RemoveSocketRefs( socket ); }