xwords/xwords4/relay/crefmgr.h
Eric House 9138cd15b7 fix problems with devices reconnecting when their ACKs didn't arrive
and their slots are empty or have been reassigned: basically we check
if a device goes where it expects, and if not treat it as a new
connection rather than a reconnect, meaning its hostid may change.
Existing device code seems ok with that -- and at any rate I don't
think ACKs get dropped much in the wild.
2013-06-26 00:38:34 -07:00

412 lines
12 KiB
C

/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */
/*
* Copyright 2005-2012 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef _CREFMGR_H_
#define _CREFMGR_H_
#include <list>
#include "cref.h"
#include "dbmgr.h"
#include "mlock.h"
#include "cidlock.h"
typedef map<CookieID,CookieRef*> CookieMap;
class CookieMapIterator;
class CrefInfo {
public:
string m_cookie;
string m_connName;
CookieID m_cid;
int m_nPlayersSought;
int m_nPlayersHere;
XW_RELAY_STATE m_curState;
time_t m_startTime;
int m_nHosts;
int m_langCode;
string m_hostsIds;
string m_hostSeeds;
string m_hostIps;
};
class CrefMgrInfo {
public:
const char* m_ports;
int m_nRoomsFilled;
int m_nCrefsCurrent;
time_t m_startTimeSpawn;
vector<CrefInfo> m_crefInfo;
};
class CRefMgr {
/* Maintain access to CookieRef instances, ultimately to ensure that no
single instance is being acted on by more than one thread at a time,
and that once one is destroyed no additional threads attempt to access
it.
*/
private:
CRefMgr();
public:
static CRefMgr* Get();
~CRefMgr();
void CloseAll();
CookieMapIterator GetCookieIterator();
/* PENDING. These need to go through SafeCref */
void Recycle( CookieID cid );
void Recycle_locked( CookieRef* cref );
void Recycle( const char* connName );
CookieID CookieIdForName( const char* name );
/* For use from ctrl only!!!! */
/* void LockAll() { pthread_rwlock_wrlock( &m_cookieMapRWLock ); } */
/* void UnlockAll() { pthread_rwlock_unlock( &m_cookieMapRWLock ); } */
void MoveSockets( vector<int> sockets, CookieRef* cref );
pthread_mutex_t* GetWriteMutexForSocket( int socket );
void RemoveSocketRefs( const AddrInfo* addr );
void PrintSocketInfo( int socket, string& out );
void IncrementFullCount( void );
int GetNumRoomsFilled( void );
int GetSize( void );
time_t uptime();
void GetStats( CrefMgrInfo& info );
private:
friend class SafeCref;
/* We'll recycle cref instances rather than free and new them. This
solves, inelegantly, a problem where I want to free an instance (while
holding its mutex) but can't know if other threads are trying to obtain
that mutex. It's illegal to destroy a mutex somebody's trying to lock.
So we recycle, let the other thread succeed in locking but then quickly
discover that the cref it got isn't what it wants. See the SafeCref
class. */
list<CookieRef*> m_freeList;
pthread_mutex_t m_freeList_mutex;
void addToFreeList( CookieRef* cref );
CookieRef* getFromFreeList( void );
/* connect case */
CidInfo* getMakeCookieRef( const char* cookie, int nPlayersH,
int nPlayersS, int langCode, int seed,
bool wantsPublic, bool makePublic,
bool* seenSeed );
/* reconnect case; just the stuff we don't have in db */
CidInfo* getMakeCookieRef( const char* connName, const char* cookie,
HostID hid, int nPlayersH,
int nPlayersS, int seed, int langCode,
bool isPublic, bool* isDead );
CidInfo* getMakeCookieRef( const char* const connName, bool* isDead );
CidInfo* getCookieRef( CookieID cid, bool failOk = false );
CidInfo* getCookieRef( const AddrInfo* addr );
bool checkCookieRef_locked( CookieRef* cref );
CidInfo* getCookieRef_impl( CookieID cid );
CookieRef* AddNew( const char* cookie, const char* connName, CookieID cid,
int langCode, int nPlayers, int nAlreadyHere );
CookieRef* FindOpenGameFor( const char* cookie, const char* connName,
HostID hid, int socket, int nPlayersH,
int nPlayersS, int gameSeed, int langCode,
bool wantsPublic, bool* alreadyHere );
CookieID cookieIDForConnName( const char* connName );
CookieID nextCID( const char* connName );
static void heartbeatProc( void* closure );
void checkHeartbeats( time_t now );
pthread_mutex_t m_roomsFilledMutex;
int m_nRoomsFilled;
pthread_rwlock_t m_cookieMapRWLock;
CookieMap m_cookieMap;
time_t m_startTime;
string m_ports;
DBMgr* m_db;
CidLock* m_cidlock;
friend class CookieMapIterator;
}; /* CRefMgr */
class SafeCref {
/* Stack-based class that keeps more than one thread from accessing a
CookieRef instance at a time. */
public:
/* for connect */
SafeCref( const char* cookie, const AddrInfo* addr, int clientVers,
DevID* devID, int nPlayersH, int nPlayersS,
unsigned short gameSeed, int langCode, bool wantsPublic,
bool makePublic );
/* for reconnect */
SafeCref( const char* connName, const char* cookie, HostID hid,
const AddrInfo* addr, int clientVersion, DevID* devID,
int nPlayersH, int nPlayersS, unsigned short gameSeed,
int langCode, bool wantsPublic, bool makePublic );
SafeCref( const char* const connName );
SafeCref( CookieID cid, bool failOk = false );
SafeCref( const AddrInfo* addr );
/* SafeCref( CookieRef* cref ); */
~SafeCref();
bool Forward( HostID src, const AddrInfo* addr, HostID dest,
const unsigned char* buf, int buflen ) {
bool success = IsValid();
if ( success ) {
CookieRef* cref = m_cinfo->GetRef();
assert( 0 != cref->GetCid() );
cref->_Forward( src, addr, dest, buf, buflen );
} else {
logf( XW_LOGINFO, "%s: unable to forward", __func__ );
}
return success;
}
void PutMsg( HostID srcID, const AddrInfo* addr, HostID destID,
const unsigned char* buf, int buflen ) {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
assert( 0 != cref->GetCid() );
cref->_PutMsg( srcID, addr, destID, buf, buflen );
}
}
bool Connect( int nPlayersH, int nPlayersS, int seed ) {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
assert( 0 != cref->GetCid() );
return cref->_Connect( m_clientVersion, m_devID,
nPlayersH, nPlayersS, seed,
m_seenSeed, &m_addr );
} else {
return false;
}
}
bool Reconnect( int nPlayersH, int nPlayersS, int seed, XWREASON* errp ) {
bool success = false;
*errp = XWRELAY_ERROR_NONE;
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
assert( 0 != cref->GetCid() );
if ( m_dead ) {
*errp = XWRELAY_ERROR_DEADGAME;
} else {
success = cref->_Reconnect( m_clientVersion, m_devID,
m_hid, nPlayersH, nPlayersS, seed,
&m_addr, m_dead );
}
}
return success;
}
void Disconnect( const AddrInfo* addr, HostID hostID ) {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
assert( 0 != cref->GetCid() );
cref->_Disconnect( addr, hostID );
}
}
void DeviceGone( HostID hid, int seed )
{
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
assert( 0 != cref->GetCid() );
cref->_DeviceGone( hid, seed );
}
}
bool HandleAck(HostID hostID ) {
bool handled = IsValid();
if ( handled ) {
CookieRef* cref = m_cinfo->GetRef();
assert( 0 != cref->GetCid() );
cref->_HandleAck( hostID );
}
logf( XW_LOGINFO, "%s => %d", __func__, handled );
return handled;
}
void Shutdown() {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
assert( 0 != cref->GetCid() );
cref->_Shutdown();
}
}
void Remove( const AddrInfo* addr ) {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
assert( 0 != cref->GetCid() );
cref->_Remove( addr );
}
}
#ifdef RELAY_HEARTBEAT
bool HandleHeartbeat( HostID id, int socket ) {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
assert( 0 != cref->GetCid() );
cref->_HandleHeartbeat( id, socket );
return true;
} else {
return false;
}
}
void CheckHeartbeats( time_t now ) {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
assert( 0 != cref->GetCid() );
cref->_CheckHeartbeats( now );
}
}
#endif
void PrintCookieInfo( string& out ) {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
cref->_PrintCookieInfo( out );
}
}
void CheckAllConnected() {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
cref->_CheckAllConnected();
}
}
void CheckNotAcked( HostID hid ) {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
cref->_CheckNotAcked( hid );
}
}
const char* Cookie() {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
return cref->Cookie();
} else {
return ""; /* so don't crash.... */
}
}
const char* ConnName() {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
return cref->ConnName();
} else {
return ""; /* so don't crash.... */
}
}
CookieID GetCid() {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
return cref->GetCid();
} else {
return 0; /* so don't crash.... */
}
}
int GetPlayersTotal() {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
return cref->GetPlayersSought();
} else {
return -1; /* so don't crash.... */
}
}
int GetPlayersHere() {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
return cref->GetPlayersHere();
} else {
return -1; /* so don't crash.... */
}
}
const char* StateString() {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
return stateString( cref->CurState() );
} else {
return "";
}
}
void GetHostsConnected( string* hosts, string* seeds, string* addrs ) {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
cref->_FormatHostInfo( hosts, seeds, addrs );
}
}
time_t GetStartTime(void) {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
return cref->GetStarttime();
} else {
return 0;
}
}
bool IsValid() { return m_isValid; }
bool SeenSeed() { return m_seenSeed; }
private:
CidInfo* m_cinfo;
CRefMgr* m_mgr;
AddrInfo m_addr;
int m_clientVersion;
DevID* m_devID;
HostID m_hid;
bool m_isValid;
bool m_locked;
bool m_dead;
bool m_seenSeed;
}; /* SafeCref class */
class CookieMapIterator {
public:
CookieMapIterator(pthread_rwlock_t* rwlock);
~CookieMapIterator() {}
CookieID Next();
private:
RWReadLock m_rwl;
CookieMap::const_iterator _iter;
};
#endif