2008-10-02 16:23:55 -05:00
/*
* Copyright (C) 2005-2008 MaNGOS <http://www.mangosproject.org/>
*
* 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) any later version.
*
* 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
*/
/** \file
\ingroup mangosd
*/
2008-10-04 06:17:19 -05:00
#include <ace/OS_NS_signal.h>
#include "WorldSocketMgr.h"
#include "Common.h"
2008-10-02 16:23:55 -05:00
#include "Master.h"
#include "WorldSocket.h"
#include "WorldRunnable.h"
#include "World.h"
#include "Log.h"
#include "Timer.h"
#include "Policies/SingletonImp.h"
#include "SystemConfig.h"
#include "Config/ConfigEnv.h"
#include "Database/DatabaseEnv.h"
#include "CliRunnable.h"
#include "RASocket.h"
#include "ScriptCalls.h"
#include "Util.h"
2008-10-12 14:03:38 -05:00
#include "IRCClient.h"
2008-10-02 16:23:55 -05:00
#include "sockets/TcpSocket.h"
#include "sockets/Utility.h"
#include "sockets/Parse.h"
#include "sockets/Socket.h"
2008-10-04 06:17:19 -05:00
#include "sockets/SocketHandler.h"
#include "sockets/ListenSocket.h"
2008-10-02 16:23:55 -05:00
#ifdef WIN32
#include "ServiceWin32.h"
extern int m_ServiceStatus ;
#endif
/// \todo Warning disabling not useful under VC++2005. Can somebody say on which compiler it is useful?
#pragma warning(disable:4305)
INSTANTIATE_SINGLETON_1 ( Master );
volatile uint32 Master :: m_masterLoopCounter = 0 ;
class FreezeDetectorRunnable : public ZThread :: Runnable
{
public :
FreezeDetectorRunnable () { _delaytime = 0 ; }
uint32 m_loops , m_lastchange ;
uint32 w_loops , w_lastchange ;
uint32 _delaytime ;
void SetDelayTime ( uint32 t ) { _delaytime = t ; }
void run ( void )
{
if ( ! _delaytime )
return ;
sLog . outString ( "Starting up anti-freeze thread (%u seconds max stuck time)..." , _delaytime / 1000 );
m_loops = 0 ;
w_loops = 0 ;
m_lastchange = 0 ;
w_lastchange = 0 ;
while ( ! World :: m_stopEvent )
{
ZThread :: Thread :: sleep ( 1000 );
uint32 curtime = getMSTime ();
//DEBUG_LOG("anti-freeze: time=%u, counters=[%u; %u]",curtime,Master::m_masterLoopCounter,World::m_worldLoopCounter);
2008-10-04 06:17:19 -05:00
// There is no Master anymore
// TODO: clear the rest of the code
// // normal work
// if(m_loops != Master::m_masterLoopCounter)
// {
// m_lastchange = curtime;
// m_loops = Master::m_masterLoopCounter;
// }
// // possible freeze
// else if(getMSTimeDiff(m_lastchange,curtime) > _delaytime)
// {
// sLog.outError("Main/Sockets Thread hangs, kicking out server!");
// *((uint32 volatile*)NULL) = 0; // bang crash
// }
2008-10-02 16:23:55 -05:00
// normal work
if ( w_loops != World :: m_worldLoopCounter )
{
w_lastchange = curtime ;
w_loops = World :: m_worldLoopCounter ;
}
// possible freeze
else if ( getMSTimeDiff ( w_lastchange , curtime ) > _delaytime )
{
sLog . outError ( "World Thread hangs, kicking out server!" );
* (( uint32 volatile * ) NULL ) = 0 ; // bang crash
}
}
sLog . outString ( "Anti-freeze thread exiting without problems." );
}
};
2008-10-04 06:17:19 -05:00
class RARunnable : public ZThread :: Runnable
{
public :
uint32 numLoops , loopCounter ;
RARunnable ()
{
uint32 socketSelecttime = sWorld . getConfig ( CONFIG_SOCKET_SELECTTIME );
numLoops = ( sConfig . GetIntDefault ( "MaxPingTime" , 30 ) * ( MINUTE * 1000000 / socketSelecttime ));
loopCounter = 0 ;
}
void
checkping ()
{
// ping if need
if (( ++ loopCounter ) == numLoops )
{
loopCounter = 0 ;
sLog . outDetail ( "Ping MySQL to keep connection alive" );
delete WorldDatabase . Query ( "SELECT 1 FROM command LIMIT 1" );
delete loginDatabase . Query ( "SELECT 1 FROM realmlist LIMIT 1" );
delete CharacterDatabase . Query ( "SELECT 1 FROM bugreport LIMIT 1" );
}
}
void
run ( void )
{
SocketHandler h ;
// Launch the RA listener socket
ListenSocket < RASocket > RAListenSocket ( h );
bool usera = sConfig . GetBoolDefault ( "Ra.Enable" , false );
if ( usera )
{
port_t raport = sConfig . GetIntDefault ( "Ra.Port" , 3443 );
std :: string stringip = sConfig . GetStringDefault ( "Ra.IP" , "0.0.0.0" );
ipaddr_t raip ;
if ( ! Utility :: u2ip ( stringip , raip ))
sLog . outError ( "MaNGOS RA can not bind to ip %s" , stringip . c_str ());
else if ( RAListenSocket . Bind ( raip , raport ))
sLog . outError ( "MaNGOS RA can not bind to port %d on %s" , raport , stringip . c_str ());
else
{
h . Add ( & RAListenSocket );
sLog . outString ( "Starting Remote access listner on port %d on %s" , raport , stringip . c_str ());
}
}
// Socket Selet time is in microseconds , not miliseconds!!
uint32 socketSelecttime = sWorld . getConfig ( CONFIG_SOCKET_SELECTTIME );
// if use ra spend time waiting for io, if not use ra ,just sleep
if ( usera )
while ( ! World :: m_stopEvent )
{
h . Select ( 0 , socketSelecttime );
checkping ();
}
else
while ( ! World :: m_stopEvent )
{
ZThread :: Thread :: sleep ( static_cast < unsigned long > ( socketSelecttime / 1000 ));
checkping ();
}
}
};
2008-10-02 16:23:55 -05:00
Master :: Master ()
{
}
Master ::~ Master ()
{
}
/// Main function
int Master :: Run ()
{
sLog . outString ( "%s (core-daemon)" , _FULLVERSION );
sLog . outString ( "<Ctrl-C> to stop. \n " );
sLog . outTitle ( " ______ __" );
sLog . outTitle ( "/ \\ __ _ \\ __ __/ \\ \\ __" );
sLog . outTitle ( " \\ /_/ \\ \\ / _ __ / \\ _ \\ ___ / \\ _ \\ \\ ,_ \\ __ __" );
sLog . outTitle ( " \\ \\ \\ / \\ `'__ \\ / \\ \\ /' _ ` \\ / \\ \\ \\ \\ / / \\ \\ / \\ \\ " );
sLog . outTitle ( " \\ \\ \\ \\ \\ / \\ \\ \\ / \\ \\ / \\ \\ \\ \\ \\ \\ _ \\ \\ \\ _ \\ \\ " );
sLog . outTitle ( " \\ \\ _ \\ \\ _ \\ \\ \\ _ \\ \\ _ \\ \\ _ \\ \\ _ \\ \\ __ \\\\ /`____ \\ " );
sLog . outTitle ( " \\ /_/ \\ /_/ \\ /_/ \\ /_/ \\ /_/ \\ /_/ \\ /__/ `/___/> \\ " );
sLog . outTitle ( " C O R E / \\ ___/" );
sLog . outTitle ( "http://TrinityCore.org \\ /__/ \n " );
/// worldd PID file creation
std :: string pidfile = sConfig . GetStringDefault ( "PidFile" , "" );
if ( ! pidfile . empty ())
{
uint32 pid = CreatePIDFile ( pidfile );
if ( ! pid )
{
sLog . outError ( "Cannot create PID file %s. \n " , pidfile . c_str () );
return 1 ;
}
sLog . outString ( "Daemon PID: %u \n " , pid );
}
///- Start the databases
if ( ! _StartDB ())
return 1 ;
2008-10-12 14:03:38 -05:00
///- Load IRC Config (need DB for gm levels, AutoBroadcast uses world timers)
sIRC . LoadConfig ( sIRC . CfgFile );
2008-10-02 16:23:55 -05:00
///- Initialize the World
sWorld . SetInitialWorldSettings ();
///- Catch termination signals
_HookSignals ();
///- Launch WorldRunnable thread
ZThread :: Thread t ( new WorldRunnable );
t . setPriority (( ZThread :: Priority ) 2 );
// set server online
loginDatabase . PExecute ( "UPDATE realmlist SET color = 0, population = 0 WHERE id = '%d'" , realmID );
2008-10-10 13:37:21 -05:00
// Create table: has_logged_in_before - used for certain custom options
sLog . outBasic ( "ImpConfig: Creating/Checking table 'has_logged_in_before'..." );
CharacterDatabase . PExecute ( "CREATE TABLE IF NOT EXISTS `has_logged_in_before` (`guid` int(11) unsigned NOT NULL default '0', PRIMARY KEY (`guid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='ImpConfig check';" );
sLog . outBasic ( "ImpConfig: Done..." );
2008-10-02 16:23:55 -05:00
#ifdef WIN32
if ( sConfig . GetBoolDefault ( "Console.Enable" , true ) && ( m_ServiceStatus == - 1 ) /* need disable console in service mode*/ )
#else
if ( sConfig . GetBoolDefault ( "Console.Enable" , true ))
#endif
{
///- Launch CliRunnable thread
ZThread :: Thread td1 ( new CliRunnable );
}
2008-10-04 06:17:19 -05:00
ZThread :: Thread td2 ( new RARunnable );
2008-10-02 16:23:55 -05:00
///- Handle affinity for multiple processors and process priority on Windows
#ifdef WIN32
{
HANDLE hProcess = GetCurrentProcess ();
uint32 Aff = sConfig . GetIntDefault ( "UseProcessors" , 0 );
if ( Aff > 0 )
{
ULONG_PTR appAff ;
ULONG_PTR sysAff ;
if ( GetProcessAffinityMask ( hProcess , & appAff , & sysAff ))
{
ULONG_PTR curAff = Aff & appAff ; // remove non accessible processors
if ( ! curAff )
{
sLog . outError ( "Processors marked in UseProcessors bitmask (hex) %x not accessible for mangosd. Accessible processors bitmask (hex): %x" , Aff , appAff );
}
else
{
if ( SetProcessAffinityMask ( hProcess , curAff ))
sLog . outString ( "Using processors (bitmask, hex): %x" , curAff );
else
sLog . outError ( "Can't set used processors (hex): %x" , curAff );
}
}
sLog . outString ();
}
bool Prio = sConfig . GetBoolDefault ( "ProcessPriority" , false );
// if(Prio && (m_ServiceStatus == -1)/* need set to default process priority class in service mode*/)
if ( Prio )
{
if ( SetPriorityClass ( hProcess , HIGH_PRIORITY_CLASS ))
sLog . outString ( "TrinityCore process priority class set to HIGH" );
else
sLog . outError ( "ERROR: Can't set mangosd process priority class." );
sLog . outString ();
}
}
#endif
uint32 realCurrTime , realPrevTime ;
realCurrTime = realPrevTime = getMSTime ();
uint32 socketSelecttime = sWorld . getConfig ( CONFIG_SOCKET_SELECTTIME );
// maximum counter for next ping
uint32 numLoops = ( sConfig . GetIntDefault ( "MaxPingTime" , 30 ) * ( MINUTE * 1000000 / socketSelecttime ));
uint32 loopCounter = 0 ;
2008-10-12 14:03:38 -05:00
// Start up IRC bot
ZThread :: Thread irc ( new IRCClient );
irc . setPriority (( ZThread :: Priority ) 2 );
2008-10-02 16:23:55 -05:00
///- Start up freeze catcher thread
uint32 freeze_delay = sConfig . GetIntDefault ( "MaxCoreStuckTime" , 0 );
if ( freeze_delay )
{
FreezeDetectorRunnable * fdr = new FreezeDetectorRunnable ();
fdr -> SetDelayTime ( freeze_delay * 1000 );
ZThread :: Thread t ( fdr );
t . setPriority ( ZThread :: High );
}
2008-10-04 06:17:19 -05:00
///- Launch the world listener socket
port_t wsport = sWorld . getConfig ( CONFIG_PORT_WORLD );
std :: string bind_ip = sConfig . GetStringDefault ( "BindIP" , "0.0.0.0" );
2008-10-02 16:23:55 -05:00
2008-10-04 06:17:19 -05:00
if ( sWorldSocketMgr -> StartNetwork ( wsport , bind_ip . c_str ()) == - 1 )
{
sLog . outError ( "Failed to start network" );
World :: m_stopEvent = true ;
// go down and shutdown the server
2008-10-02 16:23:55 -05:00
}
2008-10-04 06:17:19 -05:00
sWorldSocketMgr -> Wait ();
2008-10-02 16:23:55 -05:00
// set server offline
loginDatabase . PExecute ( "UPDATE realmlist SET color = 2 WHERE id = '%d'" , realmID );
///- Remove signal handling before leaving
_UnhookSignals ();
// when the main thread closes the singletons get unloaded
// since worldrunnable uses them, it will crash if unloaded after master
t . wait ();
2008-10-04 06:17:19 -05:00
td2 . wait ();
2008-10-02 16:23:55 -05:00
///- Clean database before leaving
clearOnlineAccounts ();
///- Wait for delay threads to end
CharacterDatabase . HaltDelayThread ();
WorldDatabase . HaltDelayThread ();
loginDatabase . HaltDelayThread ();
sLog . outString ( "Halting process..." );
#ifdef WIN32
if ( sConfig . GetBoolDefault ( "Console.Enable" , true ))
{
// this only way to terminate CLI thread exist at Win32 (alt. way exist only in Windows Vista API)
//_exit(1);
// send keyboard input to safely unblock the CLI thread
INPUT_RECORD b [ 5 ];
HANDLE hStdIn = GetStdHandle ( STD_INPUT_HANDLE );
b [ 0 ]. EventType = KEY_EVENT ;
b [ 0 ]. Event . KeyEvent . bKeyDown = TRUE ;
b [ 0 ]. Event . KeyEvent . uChar . AsciiChar = 'X' ;
b [ 0 ]. Event . KeyEvent . wVirtualKeyCode = 'X' ;
b [ 0 ]. Event . KeyEvent . wRepeatCount = 1 ;
b [ 1 ]. EventType = KEY_EVENT ;
b [ 1 ]. Event . KeyEvent . bKeyDown = FALSE ;
b [ 1 ]. Event . KeyEvent . uChar . AsciiChar = 'X' ;
b [ 1 ]. Event . KeyEvent . wVirtualKeyCode = 'X' ;
b [ 1 ]. Event . KeyEvent . wRepeatCount = 1 ;
b [ 2 ]. EventType = KEY_EVENT ;
b [ 2 ]. Event . KeyEvent . bKeyDown = TRUE ;
b [ 2 ]. Event . KeyEvent . dwControlKeyState = 0 ;
b [ 2 ]. Event . KeyEvent . uChar . AsciiChar = '\r' ;
b [ 2 ]. Event . KeyEvent . wVirtualKeyCode = VK_RETURN ;
b [ 2 ]. Event . KeyEvent . wRepeatCount = 1 ;
b [ 2 ]. Event . KeyEvent . wVirtualScanCode = 0x1c ;
b [ 3 ]. EventType = KEY_EVENT ;
b [ 3 ]. Event . KeyEvent . bKeyDown = FALSE ;
b [ 3 ]. Event . KeyEvent . dwControlKeyState = 0 ;
b [ 3 ]. Event . KeyEvent . uChar . AsciiChar = '\r' ;
b [ 3 ]. Event . KeyEvent . wVirtualKeyCode = VK_RETURN ;
b [ 3 ]. Event . KeyEvent . wVirtualScanCode = 0x1c ;
b [ 3 ]. Event . KeyEvent . wRepeatCount = 1 ;
DWORD numb ;
BOOL ret = WriteConsoleInput ( hStdIn , b , 4 , & numb );
}
#endif
// for some unknown reason, unloading scripts here and not in worldrunnable
// fixes a memory leak related to detaching threads from the module
UnloadScriptingModule ();
return sWorld . GetShutdownMask () & SHUTDOWN_MASK_RESTART ? 2 : 0 ;
}
/// Initialize connection to the databases
bool Master :: _StartDB ()
{
///- Get world database info from configuration file
std :: string dbstring ;
if ( ! sConfig . GetString ( "WorldDatabaseInfo" , & dbstring ))
{
sLog . outError ( "Database not specified in configuration file" );
return false ;
}
sLog . outString ( "World Database: %s" , dbstring . c_str ());
///- Initialise the world database
if ( ! WorldDatabase . Initialize ( dbstring . c_str ()))
{
sLog . outError ( "Cannot connect to world database %s" , dbstring . c_str ());
return false ;
}
if ( ! sConfig . GetString ( "CharacterDatabaseInfo" , & dbstring ))
{
sLog . outError ( "Character Database not specified in configuration file" );
return false ;
}
sLog . outString ( "Character Database: %s" , dbstring . c_str ());
///- Initialise the Character database
if ( ! CharacterDatabase . Initialize ( dbstring . c_str ()))
{
sLog . outError ( "Cannot connect to Character database %s" , dbstring . c_str ());
return false ;
}
///- Get login database info from configuration file
if ( ! sConfig . GetString ( "LoginDatabaseInfo" , & dbstring ))
{
sLog . outError ( "Login database not specified in configuration file" );
return false ;
}
///- Initialise the login database
sLog . outString ( "Login Database: %s" , dbstring . c_str () );
if ( ! loginDatabase . Initialize ( dbstring . c_str ()))
{
sLog . outError ( "Cannot connect to login database %s" , dbstring . c_str ());
return false ;
}
///- Get the realm Id from the configuration file
realmID = sConfig . GetIntDefault ( "RealmID" , 0 );
if ( ! realmID )
{
sLog . outError ( "Realm ID not defined in configuration file" );
return false ;
}
sLog . outString ( "Realm running as realm ID %d" , realmID );
///- Clean the database before starting
clearOnlineAccounts ();
QueryResult * result = WorldDatabase . Query ( "SELECT version FROM db_version LIMIT 1" );
if ( result )
{
Field * fields = result -> Fetch ();
sLog . outString ( "Using %s" , fields [ 0 ]. GetString ());
delete result ;
}
else
sLog . outString ( "Using unknown world database." );
return true ;
}
/// Clear 'online' status for all accounts with characters in this realm
void Master :: clearOnlineAccounts ()
{
// Cleanup online status for characters hosted at current realm
/// \todo Only accounts with characters logged on *this* realm should have online status reset. Move the online column from 'account' to 'realmcharacters'?
loginDatabase . PExecute (
"UPDATE account SET online = 0 WHERE online > 0 "
"AND id IN (SELECT acctid FROM realmcharacters WHERE realmid = '%d')" , realmID );
CharacterDatabase . Execute ( "UPDATE characters SET online = 0" );
}
/// Handle termination signals
/** Put the World::m_stopEvent to 'true' if a termination signal is caught **/
void Master :: _OnSignal ( int s )
{
switch ( s )
{
case SIGINT :
case SIGTERM :
#ifdef _WIN32
case SIGBREAK :
#endif
World :: m_stopEvent = true ;
break ;
}
signal ( s , _OnSignal );
}
/// Define hook '_OnSignal' for all termination signals
void Master :: _HookSignals ()
{
signal ( SIGINT , _OnSignal );
signal ( SIGTERM , _OnSignal );
#ifdef _WIN32
signal ( SIGBREAK , _OnSignal );
#endif
}
/// Unhook the signals before leaving
void Master :: _UnhookSignals ()
{
signal ( SIGINT , 0 );
signal ( SIGTERM , 0 );
#ifdef _WIN32
signal ( SIGBREAK , 0 );
#endif
}