diff --git a/evennia/server/amp_client.py b/evennia/server/amp_client.py index 474f028e93..8b9f9d4e8e 100644 --- a/evennia/server/amp_client.py +++ b/evennia/server/amp_client.py @@ -4,6 +4,7 @@ Portal. This module sets up the Client-side communication. """ +import os from evennia.server.portal import amp from twisted.internet import protocol from evennia.utils import logger @@ -105,7 +106,8 @@ class AMPServerClientProtocol(amp.AMPMultiConnectionProtocol): super(AMPServerClientProtocol, self).connectionMade() # first thing we do is to request the Portal to sync all sessions # back with the Server side. We also need the startup mode (reload, reset, shutdown) - self.send_AdminServer2Portal(amp.DUMMYSESSION, operation=amp.PSYNC, info_dict=info_dict) + self.send_AdminServer2Portal( + amp.DUMMYSESSION, operation=amp.PSYNC, spid=os.getpid(), info_dict=info_dict) def data_to_portal(self, command, sessid, **kwargs): """ diff --git a/evennia/server/evennia_launcher.py b/evennia/server/evennia_launcher.py index 635b504071..b07e3bb562 100644 --- a/evennia/server/evennia_launcher.py +++ b/evennia/server/evennia_launcher.py @@ -679,16 +679,14 @@ def query_status(callback=None): callback(response) else: pstatus, sstatus, ppid, spid, pinfo, sinfo = _parse_status(response) - # note - the server reports its pid, but this is likely to be a - # thread and can't be relied on, so we use the pid file instead - print("Portal: {} (pid {})\nServer: {} (pid {})".format( - wmap[pstatus], get_pid(PORTAL_PIDFILE), - wmap[sstatus], get_pid(SERVER_PIDFILE))) + print("Evennia Portal: {}{}\n Server: {}{}".format( + wmap[pstatus], " (pid {})".format(get_pid(PORTAL_PIDFILE, ppid)) if pstatus else "", + wmap[sstatus], " (pid {})".format(get_pid(SERVER_PIDFILE, spid)) if sstatus else "")) _reactor_stop() def _errback(fail): pstatus, sstatus = False, False - print("Portal: {}\nServer: {}".format(wmap[pstatus], wmap[sstatus])) + print("Portal: {}\nServer: {}".format(wmap[pstatus], wmap[sstatus])) _reactor_stop() send_instruction(PSTATUS, None, _callback, _errback) @@ -1355,22 +1353,23 @@ def getenv(): return env -def get_pid(pidfile): +def get_pid(pidfile, default=None): """ Get the PID (Process ID) by trying to access an PID file. Args: pidfile (str): The path of the pid file. + default (int, optional): What to return if file does not exist. Returns: - pid (str or None): The process id. + pid (str): The process id or `default`. """ if os.path.exists(pidfile): with open(pidfile, 'r') as f: pid = f.read() return pid - return None + return default def del_pid(pidfile): @@ -1418,7 +1417,7 @@ def kill(pidfile, component='Server', callback=None, errback=None, killsignal=SI # We must catch and ignore the interrupt sent. pass else: - # Linux can send the SIGINT signal directly + # Linux/Unix can send the SIGINT signal directly # to the specified PID. os.kill(int(pid), killsignal) diff --git a/evennia/server/portal/amp_server.py b/evennia/server/portal/amp_server.py index a11b52d58b..9baa7ba86f 100644 --- a/evennia/server/portal/amp_server.py +++ b/evennia/server/portal/amp_server.py @@ -54,7 +54,6 @@ class AMPServerFactory(protocol.ServerFactory): self.protocol = AMPServerProtocol self.broadcasts = [] self.server_connection = None - self.server_info_dict = None self.launcher_connection = None self.disconnect_callbacks = {} self.server_connect_callbacks = [] @@ -89,7 +88,7 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol): super(AMPServerProtocol, self).connectionLost(reason) if self.factory.server_connection == self: self.factory.server_connection = None - self.factory.server_info_dict = None + self.factory.portal.server_info_dict = {} if self.factory.launcher_connection == self: self.factory.launcher_connection = None @@ -112,7 +111,7 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol): server_connected = bool(self.factory.server_connection and self.factory.server_connection.transport.connected) portal_info_dict = self.factory.portal.get_info_dict() - server_info_dict = self.factory.server_info_dict + server_info_dict = self.factory.portal.server_info_dict server_pid = self.factory.portal.server_process_id portal_pid = os.getpid() return (True, server_connected, portal_pid, server_pid, portal_info_dict, server_info_dict) @@ -151,7 +150,11 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol): """ # start the Server + process = None with open(settings.SERVER_LOG_FILE, 'a') as logfile: + # we link stdout to a file in order to catch + # eventual errors happening before the Server has + # opened its logger. try: if os.name == 'nt': # Windows requires special care @@ -159,24 +162,20 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol): process = Popen(server_twistd_cmd, env=getenv(), bufsize=-1, stdout=logfile, stderr=STDOUT, creationflags=create_no_window) + else: process = Popen(server_twistd_cmd, env=getenv(), bufsize=-1, stdout=logfile, stderr=STDOUT) except Exception: - self.factory.portal.server_process_id = None logger.log_trace() - logfile.flush() - return 0 - # there is a short window before the server logger is up where we must - # catch the stdout of the Server or eventual tracebacks will be lost. - # with process.stdout as out: - # logger.log_server(out.readlines()) - # store the pid and launch argument for future reference - self.factory.portal.server_process_id = process.pid self.factory.portal.server_twistd_cmd = server_twistd_cmd logfile.flush() + if process: + # avoid zombie-process + process.wait() return process.pid + return 0 def wait_for_disconnect(self, callback, *args, **kwargs): """ @@ -414,7 +413,8 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol): elif operation == amp.PSYNC: # portal sync # Server has (re-)connected and wants the session data from portal - self.factory.server_info_dict = kwargs.get("info_dict", {}) + self.factory.portal.server_info_dict = kwargs.get("info_dict", {}) + self.factory.portal.server_process_id = kwargs.get("spid", None) # this defaults to 'shutdown' or whatever value set in server_stop server_restart_mode = self.factory.portal.server_restart_mode diff --git a/evennia/server/portal/portal.py b/evennia/server/portal/portal.py index 6645f8964d..bbeec30fc5 100644 --- a/evennia/server/portal/portal.py +++ b/evennia/server/portal/portal.py @@ -39,11 +39,6 @@ except Exception: PORTAL_SERVICES_PLUGIN_MODULES = [mod_import(module) for module in make_iter(settings.PORTAL_SERVICES_PLUGIN_MODULES)] LOCKDOWN_MODE = settings.LOCKDOWN_MODE -PORTAL_PIDFILE = "" -if os.name == 'nt': - # For Windows we need to handle pid files manually. - PORTAL_PIDFILE = os.path.join(settings.GAME_DIR, "server", 'portal.pid') - # ------------------------------------------------------------- # Evennia Portal settings # ------------------------------------------------------------- @@ -113,8 +108,10 @@ class Portal(object): self.sessions = PORTAL_SESSIONS self.sessions.portal = self self.process_id = os.getpid() + self.server_process_id = None self.server_restart_mode = "shutdown" + self.server_info_dict = {} # set a callback if the server is killed abruptly, # by Ctrl-C, reboot etc. @@ -163,9 +160,7 @@ class Portal(object): return self.sessions.disconnect_all() self.set_restart_mode(restart) - if os.name == 'nt' and os.path.exists(PORTAL_PIDFILE): - # for Windows we need to remove pid files manually - os.remove(PORTAL_PIDFILE) + if not _reactor_stopping: # shutting down the reactor will trigger another signal. We set # a flag to avoid loops. diff --git a/evennia/server/server.py b/evennia/server/server.py index ba17015022..3006245843 100644 --- a/evennia/server/server.py +++ b/evennia/server/server.py @@ -51,9 +51,9 @@ SERVER_STARTSTOP_MODULE = mod_import(settings.AT_SERVER_STARTSTOP_MODULE) SERVER_SERVICES_PLUGIN_MODULES = [mod_import(module) for module in make_iter(settings.SERVER_SERVICES_PLUGIN_MODULES)] -#------------------------------------------------------------ +# ------------------------------------------------------------ # Evennia Server settings -#------------------------------------------------------------ +# ------------------------------------------------------------ SERVERNAME = settings.SERVERNAME VERSION = get_evennia_version()