mirror of
https://github.com/evennia/evennia.git
synced 2026-03-27 18:26:32 +01:00
Working start/reload/reset/stop from launcher
This commit is contained in:
parent
ded5d106b9
commit
5656b841d6
7 changed files with 136 additions and 56 deletions
|
|
@ -192,15 +192,21 @@ class AMPServerClientProtocol(amp.AMPMultiConnectionProtocol):
|
|||
elif operation == amp.PSYNC: # portal_session_sync
|
||||
# force a resync of sessions from the portal side
|
||||
server_sessionhandler.portal_sessions_sync(kwargs.get("sessiondata"))
|
||||
|
||||
elif operation == amp.SRELOAD: # server reload
|
||||
# shut down in reload mode
|
||||
server_sessionhandler.all_sessions_portal_sync()
|
||||
server_sessionhandler.server.shutdown(mode='reload')
|
||||
|
||||
elif operation == amp.SRESET:
|
||||
# shut down in reset mode
|
||||
server_sessionhandler.all_sessions_portal_sync()
|
||||
server_sessionhandler.server.shutdown(mode='reset')
|
||||
|
||||
elif operation == amp.SSHUTD: # server shutdown
|
||||
# shutdown in stop mode
|
||||
server_sessionhandler.server.shutdown(mode='shutdown')
|
||||
|
||||
else:
|
||||
raise Exception("operation %(op)s not recognized." % {'op': operation})
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import shutil
|
|||
import importlib
|
||||
from distutils.version import LooseVersion
|
||||
from argparse import ArgumentParser
|
||||
from subprocess import Popen, check_output, call, CalledProcessError, STDOUT
|
||||
from subprocess import Popen, check_output, call, CalledProcessError, STDOUT, PIPE
|
||||
|
||||
try:
|
||||
import cPickle as pickle
|
||||
|
|
@ -88,6 +88,7 @@ SSTART = chr(15) # server start
|
|||
PSHUTD = chr(16) # portal (+server) shutdown
|
||||
SSHUTD = chr(17) # server-only shutdown
|
||||
PSTATUS = chr(18) # ping server or portal status
|
||||
SRESET = chr(19) # shutdown server in reset mode
|
||||
|
||||
# requirements
|
||||
PYTHON_MIN = '2.7'
|
||||
|
|
@ -519,13 +520,18 @@ def _get_twistd_cmdline(pprofiler, sprofiler):
|
|||
Compile the command line for starting a Twisted application using the 'twistd' executable.
|
||||
|
||||
"""
|
||||
|
||||
portal_cmd = [TWISTED_BINARY,
|
||||
"--logfile={}".format(PORTAL_LOGFILE),
|
||||
"--python={}".format(PORTAL_PY_FILE)]
|
||||
server_cmd = [TWISTED_BINARY,
|
||||
"--logfile={}".format(PORTAL_LOGFILE),
|
||||
"--python={}".format(PORTAL_PY_FILE)]
|
||||
"--logfile={}".format(SERVER_LOGFILE),
|
||||
"--python={}".format(SERVER_PY_FILE)]
|
||||
|
||||
if os.name != 'nt':
|
||||
# PID files only for UNIX
|
||||
portal_cmd.append("--pidfile={}".format(PORTAL_PIDFILE))
|
||||
server_cmd.append("--pidfile={}".format(SERVER_PIDFILE))
|
||||
|
||||
if pprofiler:
|
||||
portal_cmd.extend(["--savestats",
|
||||
"--profiler=cprofiler",
|
||||
|
|
@ -534,6 +540,8 @@ def _get_twistd_cmdline(pprofiler, sprofiler):
|
|||
server_cmd.extend(["--savestats",
|
||||
"--profiler=cprofiler",
|
||||
"--profile={}".format(SPROFILER_LOGFILE)])
|
||||
|
||||
|
||||
return portal_cmd, server_cmd
|
||||
|
||||
|
||||
|
|
@ -552,7 +560,6 @@ def query_status(repeat=False):
|
|||
reactor.stop()
|
||||
|
||||
def _errback(fail):
|
||||
print("status fail: %s", fail)
|
||||
pstatus, sstatus = False, False
|
||||
print("Portal: {}\nServer: {}".format(wmap[pstatus], wmap[sstatus]))
|
||||
reactor.stop()
|
||||
|
|
@ -591,7 +598,7 @@ def wait_for_status(portal_running=True, server_running=True, callback=None, err
|
|||
if errback:
|
||||
errback(prun, srun)
|
||||
else:
|
||||
print("Timeout.")
|
||||
print("Connection to Evennia timed out. Try again.")
|
||||
reactor.stop()
|
||||
else:
|
||||
reactor.callLater(rate, wait_for_status,
|
||||
|
|
@ -613,7 +620,7 @@ def wait_for_status(portal_running=True, server_running=True, callback=None, err
|
|||
if errback:
|
||||
errback(portal_running, server_running)
|
||||
else:
|
||||
print("Timeout.")
|
||||
print("Connection to Evennia timed out. Try again.")
|
||||
reactor.stop()
|
||||
else:
|
||||
reactor.callLater(rate, wait_for_status,
|
||||
|
|
@ -622,14 +629,13 @@ def wait_for_status(portal_running=True, server_running=True, callback=None, err
|
|||
|
||||
return send_instruction(PSTATUS, None, _callback, _errback)
|
||||
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
#
|
||||
# Operational functions
|
||||
#
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
def start_evennia(pprofiler=False, sprofiler=False):
|
||||
"""
|
||||
This will start Evennia anew by launching the Evennia Portal (which in turn
|
||||
|
|
@ -638,8 +644,12 @@ def start_evennia(pprofiler=False, sprofiler=False):
|
|||
"""
|
||||
portal_cmd, server_cmd = _get_twistd_cmdline(pprofiler, sprofiler)
|
||||
|
||||
def _fail(fail):
|
||||
print(fail)
|
||||
reactor.stop()
|
||||
|
||||
def _server_started(*args):
|
||||
print("... Server started.\nEvennia running.", args)
|
||||
print("... Server started.\nEvennia running.")
|
||||
reactor.stop()
|
||||
|
||||
def _portal_started(*args):
|
||||
|
|
@ -653,21 +663,22 @@ def start_evennia(pprofiler=False, sprofiler=False):
|
|||
reactor.stop()
|
||||
else:
|
||||
print("Server starting {}...".format("(under cProfile)" if pprofiler else ""))
|
||||
send_instruction(SSTART, server_cmd, _server_started)
|
||||
send_instruction(SSTART, server_cmd, _server_started, _fail)
|
||||
|
||||
def _portal_not_running(fail):
|
||||
print("Portal starting {}...".format("(under cProfile)" if sprofiler else ""))
|
||||
try:
|
||||
Popen(portal_cmd, env=getenv())
|
||||
Popen(portal_cmd, env=getenv(), bufsize=-1)
|
||||
except Exception as e:
|
||||
print(PROCESS_ERROR.format(component="Portal", traceback=e))
|
||||
reactor.stop()
|
||||
wait_for_status(True, None, _portal_started)
|
||||
|
||||
send_instruction(PSTATUS, None, _portal_running, _portal_not_running)
|
||||
reactor.run()
|
||||
|
||||
|
||||
def reload_evennia(sprofiler=False):
|
||||
def reload_evennia(sprofiler=False, reset=False):
|
||||
"""
|
||||
This will instruct the Portal to reboot the Server component.
|
||||
|
||||
|
|
@ -675,18 +686,23 @@ def reload_evennia(sprofiler=False):
|
|||
_, server_cmd = _get_twistd_cmdline(False, sprofiler)
|
||||
|
||||
def _server_restarted(*args):
|
||||
print("... Server re-started.", args)
|
||||
print("... Server re-started.")
|
||||
reactor.stop()
|
||||
|
||||
def _server_reloaded(*args):
|
||||
print("... Server reloaded.", args)
|
||||
print("... Server {}.".format("reset" if reset else "reloaded"))
|
||||
reactor.stop()
|
||||
|
||||
def _server_not_running(*args):
|
||||
send_instruction(SSTART, server_cmd)
|
||||
wait_for_status(True, True, _server_reloaded)
|
||||
|
||||
def _portal_running(response):
|
||||
_, srun, _, _ = _parse_status(response)
|
||||
if srun:
|
||||
print("Server reloading ...")
|
||||
send_instruction(SRELOAD, server_cmd, _server_reloaded)
|
||||
print("Server {}...".format("resetting" if reset else "reloading"))
|
||||
send_instruction(SRESET if reset else SRELOAD, server_cmd)
|
||||
wait_for_status(True, False, _server_not_running)
|
||||
else:
|
||||
print("Server down. Re-starting ...")
|
||||
send_instruction(SSTART, server_cmd, _server_restarted)
|
||||
|
|
@ -710,7 +726,7 @@ def stop_evennia():
|
|||
reactor.stop()
|
||||
|
||||
def _server_stopped(*args):
|
||||
print("... Server stopped.\nStopping Portal ...", args)
|
||||
print("... Server stopped.\nStopping Portal ...")
|
||||
send_instruction(PSHUTD, {})
|
||||
wait_for_status(False, None, _portal_stopped)
|
||||
|
||||
|
|
@ -718,7 +734,8 @@ def stop_evennia():
|
|||
prun, srun, ppid, spid = _parse_status(response)
|
||||
if srun:
|
||||
print("Server stopping ...")
|
||||
send_instruction(SSHUTD, {}, _server_stopped)
|
||||
send_instruction(SSHUTD, {})
|
||||
wait_for_status(True, False, _server_stopped)
|
||||
else:
|
||||
print("Server already stopped.\nStopping Portal ...")
|
||||
send_instruction(PSHUTD, {})
|
||||
|
|
@ -726,6 +743,7 @@ def stop_evennia():
|
|||
|
||||
def _portal_not_running(fail):
|
||||
print("Evennia is not running.")
|
||||
reactor.stop()
|
||||
|
||||
send_instruction(PSTATUS, None, _portal_running, _portal_not_running)
|
||||
reactor.run()
|
||||
|
|
@ -741,8 +759,8 @@ def stop_server_only():
|
|||
reactor.stop()
|
||||
|
||||
def _portal_running(response):
|
||||
_, server_running = [stat == 'RUNNING' for stat in response['status'].split("|")]
|
||||
if server_running:
|
||||
_, srun, _, _ = _parse_status(response)
|
||||
if srun:
|
||||
print("Server stopping ...")
|
||||
send_instruction(SSHUTD, {})
|
||||
wait_for_status(True, False, _server_stopped)
|
||||
|
|
@ -1239,7 +1257,7 @@ def init_game_directory(path, check_db=True):
|
|||
AMP_INTERFACE = settings.AMP_INTERFACE
|
||||
|
||||
SERVER_PY_FILE = os.path.join(EVENNIA_LIB, "server", "server.py")
|
||||
PORTAL_PY_FILE = os.path.join(EVENNIA_LIB, "portal", "portal", "portal.py")
|
||||
PORTAL_PY_FILE = os.path.join(EVENNIA_LIB, "server", "portal", "portal.py")
|
||||
|
||||
SERVER_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "server.pid")
|
||||
PORTAL_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "portal.pid")
|
||||
|
|
@ -1602,9 +1620,6 @@ def main():
|
|||
"service", metavar="component", nargs='?', default="all",
|
||||
help=("Which component to operate on: "
|
||||
"'server', 'portal' or 'all' (default if not set)."))
|
||||
parser.add_argument(
|
||||
"--status", action='store_true', dest='get_status',
|
||||
default=None, help='Get current server status.')
|
||||
parser.epilog = (
|
||||
"Common usage: evennia start|stop|reload. Django-admin database commands:"
|
||||
"evennia migration|flush|shell|dbshell (see the django documentation for more "
|
||||
|
|
@ -1656,11 +1671,6 @@ def main():
|
|||
print(ERROR_INITSETTINGS)
|
||||
sys.exit()
|
||||
|
||||
if args.get_status:
|
||||
init_game_directory(CURRENT_DIR, check_db=True)
|
||||
query_status()
|
||||
sys.exit()
|
||||
|
||||
if args.dummyrunner:
|
||||
# launch the dummy runner
|
||||
init_game_directory(CURRENT_DIR, check_db=True)
|
||||
|
|
@ -1673,13 +1683,17 @@ def main():
|
|||
# launch menu for operation
|
||||
init_game_directory(CURRENT_DIR, check_db=True)
|
||||
run_menu()
|
||||
elif option in ('sstart', 'sreload', 'sstop', 'ssstop', 'start', 'reload', 'stop'):
|
||||
elif option in ('status', 'sstart', 'sreload', 'sreset', 'sstop', 'ssstop', 'start', 'reload', 'stop'):
|
||||
# operate the server directly
|
||||
init_game_directory(CURRENT_DIR, check_db=True)
|
||||
if option == "status":
|
||||
query_status()
|
||||
if option == "sstart":
|
||||
start_evennia(False, args.profiler)
|
||||
elif option == 'sreload':
|
||||
reload_evennia(args.profiler)
|
||||
elif option == 'sreset':
|
||||
reload_evennia(args.profiler, reset=True)
|
||||
elif option == 'sstop':
|
||||
stop_evennia()
|
||||
elif option == 'ssstop':
|
||||
|
|
|
|||
|
|
@ -346,6 +346,7 @@ def main():
|
|||
del portal_argv[-2]
|
||||
|
||||
# Start processes
|
||||
print("server_argv:", server_argv, portal_argv)
|
||||
start_services(server_argv, portal_argv, doexit=args.doexit)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -89,8 +89,8 @@ def catch_traceback(func):
|
|||
if not _LOGGER:
|
||||
from evennia.utils import logger as _LOGGER
|
||||
_LOGGER.log_trace()
|
||||
print("error", err)
|
||||
raise # make sure the error is visible on the other side of the connection too
|
||||
print(err)
|
||||
return decorator
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import os
|
|||
import sys
|
||||
from twisted.internet import protocol
|
||||
from evennia.server.portal import amp
|
||||
from subprocess import Popen
|
||||
from subprocess import Popen, STDOUT, PIPE
|
||||
from evennia.utils import logger
|
||||
|
||||
|
||||
|
|
@ -48,6 +48,8 @@ class AMPServerFactory(protocol.ServerFactory):
|
|||
self.portal = portal
|
||||
self.protocol = AMPServerProtocol
|
||||
self.broadcasts = []
|
||||
self.server_connection = None
|
||||
self.disconnect_callbacks = {}
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
"""
|
||||
|
|
@ -80,12 +82,45 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
|
||||
"""
|
||||
# start the Server
|
||||
process = Popen(server_twistd_cmd, env=getenv())
|
||||
# store the pid for future reference
|
||||
try:
|
||||
process = Popen(server_twistd_cmd, env=getenv(), bufsize=-1, stdout=PIPE, stderr=STDOUT)
|
||||
except Exception:
|
||||
self.factory.portal.server_process_id = None
|
||||
logger.log_trace()
|
||||
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.read())
|
||||
|
||||
# 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
|
||||
return process.pid
|
||||
|
||||
def connectionLost(self, reason):
|
||||
"""
|
||||
Set up a simple callback mechanism to let the amp-server wait for a connection to close.
|
||||
|
||||
"""
|
||||
callback, args, kwargs = self.factory.disconnect_callbacks.pop(self, (None, None, None))
|
||||
if callback:
|
||||
try:
|
||||
callback(*args, **kwargs)
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
|
||||
def wait_for_disconnect(self, callback, *args, **kwargs):
|
||||
"""
|
||||
Add a callback for when this connection is lost.
|
||||
|
||||
Args:
|
||||
callback (callable): Will be called with *args, **kwargs
|
||||
once this protocol is disconnected.
|
||||
|
||||
"""
|
||||
self.factory.disconnect_callbacks[self] = (callback, args, kwargs)
|
||||
|
||||
def stop_server(self, mode='shutdown'):
|
||||
"""
|
||||
Shut down server in one or more modes.
|
||||
|
|
@ -95,11 +130,11 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
|
||||
"""
|
||||
if mode == 'reload':
|
||||
self.send_AdminPortal2Server(amp.DUMMYSESSION, amp.SRELOAD)
|
||||
if mode == 'reset':
|
||||
self.send_AdminPortal2Server(amp.DUMMYSESSION, amp.SRESET)
|
||||
if mode == 'shutdown':
|
||||
self.send_AdminPortal2Server(amp.DUMMYSESSION, amp.SSHUTD)
|
||||
self.send_AdminPortal2Server(amp.DUMMYSESSION, operation=amp.SRELOAD)
|
||||
elif mode == 'reset':
|
||||
self.send_AdminPortal2Server(amp.DUMMYSESSION, operation=amp.SRESET)
|
||||
elif mode == 'shutdown':
|
||||
self.send_AdminPortal2Server(amp.DUMMYSESSION, operation=amp.SSHUTD)
|
||||
|
||||
# sending amp data
|
||||
|
||||
|
|
@ -148,8 +183,8 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
|
||||
"""
|
||||
# check if the server is connected
|
||||
server_connected = any(1 for prtcl in self.factory.broadcasts
|
||||
if prtcl is not self and prtcl.transport.connected)
|
||||
server_connected = (self.factory.server_connection and
|
||||
self.factory.server_connection.transport.connected)
|
||||
server_pid = self.factory.portal.server_process_id
|
||||
portal_pid = os.getpid()
|
||||
|
||||
|
|
@ -180,14 +215,14 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
def _retval(success, txt):
|
||||
return {"result": amp.dumps((success, txt))}
|
||||
|
||||
server_connected = any(1 for prtcl in self.factory.broadcasts
|
||||
if prtcl is not self and prtcl.transport.connected)
|
||||
server_connected = (self.factory.server_connection and
|
||||
self.factory.server_connection.transport.connected)
|
||||
server_pid = self.factory.portal.server_process_id
|
||||
|
||||
logger.log_msg("AMP SERVER operation == %s received" % (ord(operation)))
|
||||
logger.log_msg("AMP SERVER arguments: %s" % (amp.loads(arguments)))
|
||||
|
||||
if operation == amp.SSTART: # portal start
|
||||
if operation == amp.SSTART: # portal start #15
|
||||
# first, check if server is already running
|
||||
if server_connected:
|
||||
return _retval(False,
|
||||
|
|
@ -195,27 +230,36 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
else:
|
||||
spid = self.start_server(amp.loads(arguments))
|
||||
return _retval(True, "Server started with PID {spid}.".format(spid=spid))
|
||||
elif operation == amp.SRELOAD: # reload server
|
||||
|
||||
elif operation == amp.SRELOAD: # reload server #14
|
||||
if server_connected:
|
||||
self.stop(mode='reload')
|
||||
spid = self.start_server(amp.loads(arguments))
|
||||
return _retval(True, "Server restarted with PID {spid}.".format(spid=spid))
|
||||
# don't restart until the server connection goes down
|
||||
self.stop_server(mode='reload')
|
||||
else:
|
||||
spid = self.start_server(amp.loads(arguments))
|
||||
return _retval(True, "Server started with PID {spid}.".format(spid=spid))
|
||||
elif operation == amp.SRESET: # reload server
|
||||
|
||||
elif operation == amp.SRESET: # reload server #19
|
||||
if server_connected:
|
||||
self.stop_server(mode='reset')
|
||||
spid = self.start_server(amp.loads(arguments))
|
||||
return _retval(True, "Server restarted with PID {spid}.".format(spid=spid))
|
||||
else:
|
||||
spid = self.start_server(amp.loads(arguments))
|
||||
return _retval(True, "Server started with PID {spid}.".format(spid=spid))
|
||||
elif operation == amp.PSHUTD: # portal + server shutdown
|
||||
|
||||
elif operation == amp.SSHUTD: # server-only shutdown #17
|
||||
if server_connected:
|
||||
self.stop_server(mode='shutdown')
|
||||
return _retval(True, "Server stopped.")
|
||||
else:
|
||||
return _retval(False, "Server not running")
|
||||
|
||||
elif operation == amp.PSHUTD: # portal + server shutdown #16
|
||||
if server_connected:
|
||||
self.stop_server(mode='shutdown')
|
||||
return _retval(True, "Server stopped.")
|
||||
self.factory.portal.shutdown(restart=False)
|
||||
|
||||
else:
|
||||
raise Exception("operation %(op)s not recognized." % {'op': operation})
|
||||
# fallback
|
||||
|
|
@ -254,6 +298,9 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
operation = kwargs.pop("operation")
|
||||
portal_sessionhandler = self.factory.portal.sessions
|
||||
|
||||
# store this transport since we know it comes from the Server
|
||||
self.factory.server_connection = self
|
||||
|
||||
if operation == amp.SLOGIN: # server_session_login
|
||||
# a session has authenticated; sync it.
|
||||
session = portal_sessionhandler.get(sessid)
|
||||
|
|
@ -271,12 +318,14 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
portal_sessionhandler.server_disconnect_all(reason=kwargs.get("reason"))
|
||||
|
||||
elif operation == amp.SRELOAD: # server reload
|
||||
self.stop_server(mode='reload')
|
||||
self.start(self.factory.portal.server_twisted_cmd)
|
||||
self.factory.server_connection.wait_for_disconnect(
|
||||
self.start_server, self.factory.portal.server_twisted_cmd)
|
||||
self.stop_server(mode='reload')
|
||||
|
||||
elif operation == amp.SRESET: # server reset
|
||||
self.stop_server(mode='reset')
|
||||
self.start(self.factory.portal.server_twisted_cmd)
|
||||
self.factory.server_connection.wait_for_disconnect(
|
||||
self.start_server, self.factory.portal.server_twisted_cmd)
|
||||
self.stop_server(mode='reset')
|
||||
|
||||
elif operation == amp.SSHUTD: # server-only shutdown
|
||||
self.stop_server(mode='shutdown')
|
||||
|
|
|
|||
|
|
@ -366,6 +366,7 @@ class Evennia(object):
|
|||
once - in both cases the reactor is
|
||||
dead/stopping already.
|
||||
"""
|
||||
print("server.shutdown mode=", mode)
|
||||
if _reactor_stopping and hasattr(self, "shutdown_complete"):
|
||||
# this means we have already passed through this method
|
||||
# once; we don't need to run the shutdown procedure again.
|
||||
|
|
|
|||
|
|
@ -124,6 +124,15 @@ def log_err(errmsg):
|
|||
log_errmsg = log_err
|
||||
|
||||
|
||||
def log_server(servermsg):
|
||||
try:
|
||||
servermsg = str(servermsg)
|
||||
except Exception as e:
|
||||
servermsg = str(e)
|
||||
for line in servermsg.splitlines():
|
||||
log_msg('[SRV] %s' % line)
|
||||
|
||||
|
||||
def log_warn(warnmsg):
|
||||
"""
|
||||
Prints/logs any warnings that aren't critical but should be noted.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue