Renamed runner to evennia_runner
This commit is contained in:
parent
82d583f1e7
commit
baae24b26f
2 changed files with 2 additions and 2 deletions
314
bin/evennia_runner.py
Normal file
314
bin/evennia_runner.py
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
|
||||
This runner is controlled by evennia.py and should normally not be
|
||||
launched directly. It manages the two main Evennia processes (Server
|
||||
and Portal) and most importanly runs a passive, threaded loop that
|
||||
makes sure to restart Server whenever it shuts down.
|
||||
|
||||
Since twistd does not allow for returning an optional exit code we
|
||||
need to handle the current reload state for server and portal with
|
||||
flag-files instead. The files, one each for server and portal either
|
||||
contains True or False indicating if the process should be restarted
|
||||
upon returning, or not. A process returning != 0 will always stop, no
|
||||
matter the value of this file.
|
||||
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from argparse import ArgumentParser
|
||||
from subprocess import Popen
|
||||
import Queue, thread
|
||||
|
||||
try:
|
||||
# check if launched with pypy
|
||||
import __pypy__ as is_pypy
|
||||
except ImportError:
|
||||
is_pypy = False
|
||||
|
||||
SERVER = None
|
||||
PORTAL = None
|
||||
|
||||
EVENNIA_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
EVENNIA_BIN = os.path.join(EVENNIA_ROOT, "bin")
|
||||
EVENNIA_LIB = os.path.join(EVENNIA_ROOT, "evennia")
|
||||
|
||||
SERVER_PY_FILE = os.path.join(EVENNIA_LIB,'server', 'server.py')
|
||||
PORTAL_PY_FILE = os.path.join(EVENNIA_LIB, 'server', 'portal', 'portal.py')
|
||||
|
||||
GAMEDIR = None
|
||||
SERVERDIR = "server"
|
||||
SERVER_PIDFILE = None
|
||||
PORTAL_PIDFILE = None
|
||||
SERVER_RESTART = None
|
||||
PORTAL_RESTART = None
|
||||
SERVER_LOGFILE = None
|
||||
PORTAL_LOGFILE = None
|
||||
HTTP_LOGFILE = None
|
||||
|
||||
# messages
|
||||
|
||||
CMDLINE_HELP = \
|
||||
"""
|
||||
This program manages the running Evennia processes. It is called
|
||||
by evennia and should not be started manually. Its main task is to
|
||||
sit and watch the Server and restart it whenever the user reloads.
|
||||
The runner depends on four files for its operation, two PID files
|
||||
and two RESTART files for Server and Portal respectively; these
|
||||
are stored in the game's server/ directory.
|
||||
"""
|
||||
|
||||
PROCESS_ERROR = \
|
||||
"""
|
||||
{component} process error: {traceback}.
|
||||
"""
|
||||
|
||||
PROCESS_IOERROR = \
|
||||
"""
|
||||
{component} IOError: {traceback}
|
||||
One possible explanation is that 'twistd' was not found.
|
||||
"""
|
||||
|
||||
PROCESS_RESTART = "{component} restarting ..."
|
||||
|
||||
|
||||
# Functions
|
||||
|
||||
def set_restart_mode(restart_file, flag="reload"):
|
||||
"""
|
||||
This sets a flag file for the restart mode.
|
||||
"""
|
||||
with open(restart_file, 'w') as f:
|
||||
f.write(str(flag))
|
||||
|
||||
|
||||
def get_restart_mode(restart_file):
|
||||
"""
|
||||
Parse the server/portal restart status
|
||||
"""
|
||||
if os.path.exists(restart_file):
|
||||
with open(restart_file, 'r') as f:
|
||||
return f.read()
|
||||
return "shutdown"
|
||||
|
||||
|
||||
def get_pid(pidfile):
|
||||
"""
|
||||
Get the PID (Process ID) by trying to access
|
||||
an PID file.
|
||||
"""
|
||||
pid = None
|
||||
if os.path.exists(pidfile):
|
||||
with open(pidfile, 'r') as f:
|
||||
pid = f.read()
|
||||
return pid
|
||||
|
||||
|
||||
def cycle_logfile(logfile):
|
||||
"""
|
||||
Rotate the old log files to <filename>.old
|
||||
"""
|
||||
logfile_old = logfile + '.old'
|
||||
if os.path.exists(logfile):
|
||||
# Cycle the old logfiles to *.old
|
||||
if os.path.exists(logfile_old):
|
||||
# E.g. Windows don't support rename-replace
|
||||
os.remove(logfile_old)
|
||||
os.rename(logfile, logfile_old)
|
||||
|
||||
# Start program management
|
||||
|
||||
|
||||
def start_services(server_argv, portal_argv):
|
||||
"""
|
||||
This calls a threaded loop that launces the Portal and Server
|
||||
and then restarts them when they finish.
|
||||
"""
|
||||
global SERVER, PORTAL
|
||||
processes = Queue.Queue()
|
||||
|
||||
def server_waiter(queue):
|
||||
try:
|
||||
rc = Popen(server_argv).wait()
|
||||
except Exception, e:
|
||||
print PROCESS_ERROR.format(component="Server", traceback=e)
|
||||
return
|
||||
# this signals the controller that the program finished
|
||||
queue.put(("server_stopped", rc))
|
||||
|
||||
def portal_waiter(queue):
|
||||
try:
|
||||
rc = Popen(portal_argv).wait()
|
||||
except Exception, e:
|
||||
print PROCESS_ERROR.format(component="Portal", traceback=e)
|
||||
return
|
||||
# this signals the controller that the program finished
|
||||
queue.put(("portal_stopped", rc))
|
||||
|
||||
if portal_argv:
|
||||
try:
|
||||
if get_restart_mode(PORTAL_RESTART) == "True":
|
||||
# start portal as interactive, reloadable thread
|
||||
PORTAL = thread.start_new_thread(portal_waiter, (processes, ))
|
||||
else:
|
||||
# normal operation: start portal as a daemon;
|
||||
# we don't care to monitor it for restart
|
||||
PORTAL = Popen(portal_argv)
|
||||
except IOError, e:
|
||||
print PROCESS_IOERROR.format(component="Portal", traceback=e)
|
||||
return
|
||||
|
||||
try:
|
||||
if server_argv:
|
||||
# start server as a reloadable thread
|
||||
SERVER = thread.start_new_thread(server_waiter, (processes, ))
|
||||
except IOError, e:
|
||||
print PROCESS_IOERROR.format(component="Server", traceback=e)
|
||||
return
|
||||
|
||||
# Reload loop
|
||||
while True:
|
||||
|
||||
# this blocks until something is actually returned.
|
||||
message, rc = processes.get()
|
||||
|
||||
# restart only if process stopped cleanly
|
||||
if (message == "server_stopped" and int(rc) == 0 and
|
||||
get_restart_mode(SERVER_RESTART) in ("True", "reload", "reset")):
|
||||
print PROCESS_RESTART.format(component="Server")
|
||||
SERVER = thread.start_new_thread(server_waiter, (processes, ))
|
||||
continue
|
||||
|
||||
# normally the portal is not reloaded since it's run as a daemon.
|
||||
if (message == "portal_stopped" and int(rc) == 0 and
|
||||
get_restart_mode(PORTAL_RESTART) == "True"):
|
||||
print PROCESS_RESTART.format(component="Portal")
|
||||
PORTAL = thread.start_new_thread(portal_waiter, (processes, ))
|
||||
continue
|
||||
break
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
This handles the command line input of the runner, usually created by
|
||||
the evennia launcher
|
||||
"""
|
||||
|
||||
parser = ArgumentParser(description=CMDLINE_HELP)
|
||||
parser.add_argument('--noserver', action='store_true', dest='noserver',
|
||||
default=False, help='Do not start Server process')
|
||||
parser.add_argument('--noportal', action='store_true', dest='noportal',
|
||||
default=False, help='Do not start Portal process')
|
||||
parser.add_argument('--iserver', action='store_true', dest='iserver',
|
||||
default=False, help='Server in interactive mode')
|
||||
parser.add_argument('--iportal', action='store_true', dest='iportal',
|
||||
default=False, help='Portal in interactive mode')
|
||||
parser.add_argument('--pserver', action='store_true', dest='pserver',
|
||||
default=False, help='Profile Server')
|
||||
parser.add_argument('--pportal', action='store_true', dest='pportal',
|
||||
default=False, help='Profile Portal')
|
||||
parser.add_argument('--nologcycle', action='store_false', dest='nologcycle',
|
||||
default=True, help='Do not cycle log files')
|
||||
parser.add_argument('gamedir', help="path to game dir")
|
||||
parser.add_argument('twistdbinary', help="path to twistd binary")
|
||||
parser.add_argument('slogfile', help="path to server log file")
|
||||
parser.add_argument('plogfile', help="path to portal log file")
|
||||
parser.add_argument('hlogfile', help="path to http log file")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
global GAMEDIR
|
||||
global SERVER_LOGFILE, PORTAL_LOGFILE, HTTP_LOGFILE
|
||||
global SERVER_PIDFILE, PORTAL_PIDFILE
|
||||
global SERVER_RESTART, PORTAL_RESTART
|
||||
|
||||
GAMEDIR = args.gamedir
|
||||
sys.path.insert(0, os.path.join(GAMEDIR, SERVERDIR))
|
||||
|
||||
SERVER_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "server.pid")
|
||||
PORTAL_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "portal.pid")
|
||||
SERVER_RESTART = os.path.join(GAMEDIR, SERVERDIR, "server.restart")
|
||||
PORTAL_RESTART = os.path.join(GAMEDIR, SERVERDIR, "portal.restart")
|
||||
SERVER_LOGFILE = args.slogfile
|
||||
PORTAL_LOGFILE = args.plogfile
|
||||
HTTP_LOGFILE = args.hlogfile
|
||||
TWISTED_BINARY = args.twistdbinary
|
||||
|
||||
# set up default project calls
|
||||
server_argv = [TWISTED_BINARY,
|
||||
'--nodaemon',
|
||||
'--logfile=%s' % SERVER_LOGFILE,
|
||||
'--pidfile=%s' % SERVER_PIDFILE,
|
||||
'--python=%s' % SERVER_PY_FILE]
|
||||
portal_argv = [TWISTED_BINARY,
|
||||
'--logfile=%s' % PORTAL_LOGFILE,
|
||||
'--pidfile=%s' % PORTAL_PIDFILE,
|
||||
'--python=%s' % PORTAL_PY_FILE]
|
||||
|
||||
# Profiling settings (read file from python shell e.g with
|
||||
# p = pstats.Stats('server.prof')
|
||||
pserver_argv = ['--savestats',
|
||||
'--profiler=cprofile',
|
||||
'--profile=server.prof']
|
||||
pportal_argv = ['--savestats',
|
||||
'--profiler=cprofile',
|
||||
'--profile=portal.prof']
|
||||
|
||||
# Server
|
||||
|
||||
pid = get_pid(SERVER_PIDFILE)
|
||||
if pid and not args.noserver:
|
||||
print "\nEvennia Server is already running as process %(pid)s. Not restarted." % {'pid': pid}
|
||||
args.noserver = True
|
||||
if args.noserver:
|
||||
server_argv = None
|
||||
else:
|
||||
set_restart_mode(SERVER_RESTART, "shutdown")
|
||||
if args.iserver:
|
||||
# don't log to server logfile
|
||||
del server_argv[2]
|
||||
print "\nStarting Evennia Server (output to stdout)."
|
||||
else:
|
||||
if not args.nologcycle:
|
||||
cycle_logfile(SERVER_LOGFILE)
|
||||
print "\nStarting Evennia Server (output to server logfile)."
|
||||
if args.pserver:
|
||||
server_argv.extend(pserver_argv)
|
||||
print "\nRunning Evennia Server under cProfile."
|
||||
|
||||
# Portal
|
||||
|
||||
pid = get_pid(PORTAL_PIDFILE)
|
||||
if pid and not args.noportal:
|
||||
print "\nEvennia Portal is already running as process %(pid)s. Not restarted." % {'pid': pid}
|
||||
args.noportal = True
|
||||
if args.noportal:
|
||||
portal_argv = None
|
||||
else:
|
||||
if args.iportal:
|
||||
# make portal interactive
|
||||
portal_argv[1] = '--nodaemon'
|
||||
set_restart_mode(PORTAL_RESTART, True)
|
||||
print "\nStarting Evennia Portal in non-Daemon mode (output to stdout)."
|
||||
else:
|
||||
if not args.nologcycle:
|
||||
cycle_logfile(PORTAL_LOGFILE)
|
||||
cycle_logfile(HTTP_LOGFILE)
|
||||
set_restart_mode(PORTAL_RESTART, False)
|
||||
print "\nStarting Evennia Portal in Daemon mode (output to portal logfile)."
|
||||
if args.pportal:
|
||||
portal_argv.extend(pportal_argv)
|
||||
print "\nRunning Evennia Portal under cProfile."
|
||||
|
||||
# Windows fixes (Windows don't support pidfiles natively)
|
||||
if os.name == 'nt':
|
||||
if server_argv:
|
||||
del server_argv[-2]
|
||||
if portal_argv:
|
||||
del portal_argv[-2]
|
||||
|
||||
# Start processes
|
||||
start_services(server_argv, portal_argv)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue