mirror of
https://github.com/EQEmu/Server.git
synced 2026-04-05 15:22:37 +00:00
- License was intended to be GPLv3 per earlier commit of GPLv3 LICENSE FILE - This is confirmed by the inclusion of libraries that are incompatible with GPLv2 - This is also confirmed by KLS and the agreement of KLS's predecessors - Added GPLv3 license headers to the compilable source files - Removed Folly licensing in strings.h since the string functions do not match the Folly functions and are standard functions - this must have been left over from previous implementations - Removed individual contributor license headers since the project has been under the "developer" mantle for many years - Removed comments on files that were previously automatically generated since they've been manually modified multiple times and there are no automatic scripts referencing them (removed in 2023)
252 lines
8.4 KiB
C++
252 lines
8.4 KiB
C++
/* EQEmu: EQEmulator
|
|
|
|
Copyright (C) 2001-2026 EQEmu Development Team
|
|
|
|
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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "zone_launch.h"
|
|
|
|
#include "common/eqemu_config.h"
|
|
#include "common/eqemu_logsys.h"
|
|
#include "eqlaunch/worldserver.h"
|
|
|
|
//static const uint32 ZONE_RESTART_DELAY = 10000;
|
|
//static const uint32 ZONE_TERMINATE_WAIT = 10000;
|
|
|
|
int ZoneLaunch::s_running = 0; //the number of zones running under this launcher
|
|
Timer ZoneLaunch::s_startTimer(1); //I do not trust this things state after static initialization
|
|
|
|
ZoneLaunch::ZoneLaunch(WorldServer *world, const char *launcher_name,
|
|
const char *zone_name, uint16 port, const EQEmuConfig *config)
|
|
: m_state(StateStartPending),
|
|
m_world(world),
|
|
m_zone(zone_name),
|
|
m_port(port),
|
|
m_launcherName(launcher_name),
|
|
m_config(config),
|
|
m_timer(config->RestartWait),
|
|
m_ref(ProcLauncher::ProcError),
|
|
m_startCount(0),
|
|
m_killFails(0)
|
|
{
|
|
//trigger the startup timer initially so it boots the first time.
|
|
m_timer.Trigger();
|
|
}
|
|
|
|
ZoneLaunch::~ZoneLaunch() {
|
|
if(IsRunning())
|
|
s_running--;
|
|
}
|
|
|
|
void ZoneLaunch::SendStatus() const {
|
|
m_world->SendStatus(m_zone.c_str(), m_startCount, IsRunning());
|
|
}
|
|
|
|
void ZoneLaunch::Start() {
|
|
auto spec = new ProcLauncher::Spec();
|
|
spec->program = m_config->ZoneExe;
|
|
|
|
if(m_port) {
|
|
std::string arg = m_zone + std::string(":") + std::to_string(m_port);
|
|
spec->args.push_back(arg);
|
|
} else {
|
|
spec->args.push_back(m_zone);
|
|
}
|
|
|
|
spec->args.push_back(m_launcherName);
|
|
spec->handler = this;
|
|
spec->logFile = m_config->LogPrefix + m_zone + m_config->LogSuffix;
|
|
|
|
//spec is consumed, even on failure
|
|
m_ref = ProcLauncher::get()->Launch(spec);
|
|
if(m_ref == ProcLauncher::ProcError) {
|
|
Log(Logs::Detail, Logs::Launcher, "Failure to launch '%s %s %s'. ", m_config->ZoneExe.c_str(), m_zone.c_str(), m_launcherName);
|
|
m_timer.Start(m_config->RestartWait);
|
|
return;
|
|
}
|
|
|
|
m_startCount++;
|
|
m_state = StateStarted;
|
|
s_running++;
|
|
m_killFails = 0;
|
|
|
|
SendStatus();
|
|
|
|
Log(Logs::Detail, Logs::Launcher, "Zone %s has been started.", m_zone.c_str());
|
|
}
|
|
|
|
void ZoneLaunch::Restart() {
|
|
switch(m_state) {
|
|
case StateRestartPending:
|
|
Log(Logs::Detail, Logs::Launcher, "Restart of zone %s requested when a restart is already pending.", m_zone.c_str());
|
|
break;
|
|
case StateStartPending:
|
|
//we havent started yet, do nothing
|
|
Log(Logs::Detail, Logs::Launcher, "Restart of %s before it has started. Ignoring.", m_zone.c_str());
|
|
break;
|
|
case StateStarted:
|
|
//process is running along, kill it off..
|
|
if(m_ref == ProcLauncher::ProcError)
|
|
break; //we have no proc ref... cannot stop..
|
|
if(!ProcLauncher::get()->Terminate(m_ref, true)) {
|
|
//failed to terminate the process, its not likely that it will work if we try again, so give up.
|
|
Log(Logs::Detail, Logs::Launcher, "Failed to terminate zone %s. Giving up and moving to stopped.", m_zone.c_str());
|
|
m_state = StateStopped;
|
|
break;
|
|
}
|
|
Log(Logs::Detail, Logs::Launcher, "Termination signal sent to zone %s.", m_zone.c_str());
|
|
m_timer.Start(m_config->TerminateWait);
|
|
m_state = StateRestartPending;
|
|
break;
|
|
case StateStopPending:
|
|
Log(Logs::Detail, Logs::Launcher, "Restart of zone %s requested when a stop is pending. Ignoring.", m_zone.c_str());
|
|
break;
|
|
case StateStopped:
|
|
//process is already stopped... nothing to do..
|
|
Log(Logs::Detail, Logs::Launcher, "Restart requested when zone %s is already stopped.", m_zone.c_str());
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ZoneLaunch::Stop(bool graceful) {
|
|
switch(m_state) {
|
|
case StateStartPending:
|
|
//we havent started yet, transition directly to stopped.
|
|
Log(Logs::Detail, Logs::Launcher, "Stopping zone %s before it has started.", m_zone.c_str());
|
|
m_state = StateStopped;
|
|
break;
|
|
case StateStarted:
|
|
case StateRestartPending:
|
|
case StateStopPending:
|
|
if(m_ref == ProcLauncher::ProcError)
|
|
break; //we have no proc ref... cannot stop..
|
|
if(!ProcLauncher::get()->Terminate(m_ref, graceful)) {
|
|
//failed to terminate the process, its not likely that it will work if we try again, so give up.
|
|
Log(Logs::Detail, Logs::Launcher, "Failed to terminate zone %s. Giving up and moving to stopped.", m_zone.c_str());
|
|
m_state = StateStopped;
|
|
break;
|
|
}
|
|
Log(Logs::Detail, Logs::Launcher, "Termination signal sent to zone %s.", m_zone.c_str());
|
|
m_timer.Start(m_config->TerminateWait);
|
|
m_state = StateStopPending;
|
|
break;
|
|
case StateStopped:
|
|
//process is already stopped... nothing to do..
|
|
Log(Logs::Detail, Logs::Launcher, "Stop requested when zone %s is already stopped.", m_zone.c_str());
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool ZoneLaunch::Process() {
|
|
switch(m_state) {
|
|
case StateStartPending:
|
|
if(m_timer.Check(false)) {
|
|
//our internal timer says its time to start. Check with the shared timer.
|
|
if(!s_startTimer.Check(false)) {
|
|
//we have to wait on the shared timer now..
|
|
break;
|
|
}
|
|
|
|
//ok, both timers say we can start.
|
|
//disable our internal timer, will get started again if it is needed.
|
|
m_timer.Disable();
|
|
|
|
//actually start up the program
|
|
Log(Logs::Detail, Logs::Launcher, "Starting zone %s", m_zone.c_str());
|
|
Start();
|
|
|
|
//now update the shared timer to reflect the proper start interval.
|
|
if(s_running == 1) {
|
|
//we are the first zone started. wait that interval.
|
|
Log(Logs::Detail, Logs::Launcher, "Waiting %d milliseconds before booting the second zone.", m_config->InitialBootWait);
|
|
s_startTimer.Start(m_config->InitialBootWait);
|
|
} else {
|
|
//just some follow on zone, use that interval.
|
|
Log(Logs::Detail, Logs::Launcher, "Waiting %d milliseconds before booting the next zone.", m_config->ZoneBootInterval);
|
|
s_startTimer.Start(m_config->ZoneBootInterval);
|
|
}
|
|
|
|
} //else, timer still ticking, keep waiting
|
|
break;
|
|
case StateStarted:
|
|
//happy state, do nothing..
|
|
break;
|
|
case StateRestartPending:
|
|
//waiting for notification that our child has died..
|
|
if(m_timer.Check()) {
|
|
//we have timed out, try to kill the child again
|
|
Log(Logs::Detail, Logs::Launcher, "Zone %s refused to die, killing again.", m_zone.c_str());
|
|
Restart();
|
|
}
|
|
break;
|
|
case StateStopPending:
|
|
//waiting for notification that our child has died..
|
|
if(m_timer.Check()) {
|
|
//we have timed out, try to kill the child again
|
|
m_killFails++;
|
|
if(m_killFails > 5) { //should get this number from somewhere..
|
|
Log(Logs::Detail, Logs::Launcher, "Zone %s refused to die, giving up and acting like its dead.", m_zone.c_str());
|
|
m_state = StateStopped;
|
|
s_running--;
|
|
SendStatus();
|
|
} else {
|
|
Log(Logs::Detail, Logs::Launcher, "Zone %s refused to die, killing again.", m_zone.c_str());
|
|
Stop(false);
|
|
}
|
|
}
|
|
break;
|
|
case StateStopped:
|
|
//signal our caller to remove us
|
|
return(false);
|
|
break;
|
|
}
|
|
return(true);
|
|
}
|
|
|
|
//called when the process actually dies off...
|
|
void ZoneLaunch::OnTerminate(const ProcLauncher::ProcRef &ref, const ProcLauncher::Spec *spec) {
|
|
s_running--;
|
|
|
|
switch(m_state) {
|
|
case StateStartPending:
|
|
Log(Logs::Detail, Logs::Launcher, "Zone %s has gone down before we started it..?? Restart timer started.", m_zone.c_str());
|
|
m_state = StateStartPending;
|
|
m_timer.Start(m_config->RestartWait);
|
|
break;
|
|
case StateStarted:
|
|
//something happened to our happy process...
|
|
Log(Logs::Detail, Logs::Launcher, "Zone %s has gone down. Restart timer started.", m_zone.c_str());
|
|
m_state = StateStartPending;
|
|
m_timer.Start(m_config->RestartWait);
|
|
break;
|
|
case StateRestartPending:
|
|
//it finally died, start it on up again
|
|
Log(Logs::Detail, Logs::Launcher, "Zone %s has terminated. Transitioning to starting state.", m_zone.c_str());
|
|
m_state = StateStartPending;
|
|
break;
|
|
case StateStopPending:
|
|
//it finally died, transition to close.
|
|
Log(Logs::Detail, Logs::Launcher, "Zone %s has terminated. Transitioning to stopped state.", m_zone.c_str());
|
|
m_state = StateStopped;
|
|
break;
|
|
case StateStopped:
|
|
//we already thought it was stopped... dont care...
|
|
Log(Logs::Detail, Logs::Launcher, "Notified of zone %s terminating when we thought it was stopped.", m_zone.c_str());
|
|
break;
|
|
}
|
|
|
|
SendStatus();
|
|
}
|
|
|