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 ); }