Reworked the runner program.

This commit is contained in:
Griatch 2015-01-07 20:04:15 +01:00
parent c2e15f33a3
commit 82d583f1e7
2 changed files with 125 additions and 120 deletions

View file

@ -24,7 +24,7 @@ SIG = signal.SIGINT
# Set up the main python paths to Evennia # Set up the main python paths to Evennia
EVENNIA_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) EVENNIA_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
EVENNIA_BIN = os.path.join(EVENNIA_ROOT, "bin") EVENNIA_BIN = os.path.join(EVENNIA_ROOT, "bin")
EVENNIA_LIB = os.path.join(EVENNIA_ROOT, "lib") EVENNIA_LIB = os.path.join(EVENNIA_ROOT, "evennia")
EVENNIA_RUNNER = os.path.join(EVENNIA_BIN, "runner.py") EVENNIA_RUNNER = os.path.join(EVENNIA_BIN, "runner.py")
EVENNIA_TEMPLATE = os.path.join(EVENNIA_ROOT, "game_template") EVENNIA_TEMPLATE = os.path.join(EVENNIA_ROOT, "game_template")
@ -43,6 +43,7 @@ GAMEDIR = CURRENT_DIR
# Operational setup # Operational setup
SERVER_LOGFILE = None SERVER_LOGFILE = None
PORTAL_LOGFILE = None PORTAL_LOGFILE = None
HTTP_LOGFILE = None
SERVER_PIDFILE = None SERVER_PIDFILE = None
PORTAL_PIDFILE = None PORTAL_PIDFILE = None
SERVER_RESTART = None SERVER_RESTART = None
@ -457,13 +458,13 @@ def init_game_directory(path):
# set up the Evennia executables and log file locations # set up the Evennia executables and log file locations
global SERVER_PY_FILE, PORTAL_PY_FILE global SERVER_PY_FILE, PORTAL_PY_FILE
global SERVER_LOGFILE, PORTAL_LOGFILE global SERVER_LOGFILE, PORTAL_LOGFILE, HTTP_LOGFILE
global SERVER_PIDFILE, PORTAL_PIDFILE global SERVER_PIDFILE, PORTAL_PIDFILE
global SERVER_RESTART, PORTAL_RESTART global SERVER_RESTART, PORTAL_RESTART
global EVENNIA_VERSION global EVENNIA_VERSION
SERVER_PY_FILE = os.path.join(settings.LIB_DIR, "server/server.py") SERVER_PY_FILE = os.path.join(EVENNIA_LIB, "server", "server.py")
PORTAL_PY_FILE = os.path.join(settings.LIB_DIR, "portal/server.py") PORTAL_PY_FILE = os.path.join(EVENNIA_LIB, "portal", "portal", "portal.py")
SERVER_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "server.pid") SERVER_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "server.pid")
PORTAL_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "portal.pid") PORTAL_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "portal.pid")
@ -473,6 +474,7 @@ def init_game_directory(path):
SERVER_LOGFILE = settings.SERVER_LOG_FILE SERVER_LOGFILE = settings.SERVER_LOG_FILE
PORTAL_LOGFILE = settings.PORTAL_LOG_FILE PORTAL_LOGFILE = settings.PORTAL_LOG_FILE
HTTP_LOGFILE = settings.HTTP_LOG_FILE
# This also tests the library access # This also tests the library access
from evennia.utils.utils import get_evennia_version from evennia.utils.utils import get_evennia_version
@ -681,13 +683,14 @@ def run_menu():
print "Not a valid option." print "Not a valid option."
def server_operation(mode, service, interactive): def server_operation(mode, service, interactive, profiler):
""" """
Handle argument options given on the command line. Handle argument options given on the command line.
mode - str; start/stop etc mode - str; start/stop etc
service - str; server, portal or all service - str; server, portal or all
interactive - bool; use interactive mode or daemon interactive - bool; use interactive mode or daemon
profiler - run the service under the profiler
""" """
cmdstr = [sys.executable, EVENNIA_RUNNER] cmdstr = [sys.executable, EVENNIA_RUNNER]
@ -700,10 +703,14 @@ def server_operation(mode, service, interactive):
# starting one or many services # starting one or many services
if service == 'server': if service == 'server':
if profiler:
cmdstr.append('--pserver')
if interactive: if interactive:
cmdstr.append('--iserver') cmdstr.append('--iserver')
cmdstr.append('--noportal') cmdstr.append('--noportal')
elif service == 'portal': elif service == 'portal':
if profiler:
cmdstr.append('--pportal')
if interactive: if interactive:
cmdstr.append('--iportal') cmdstr.append('--iportal')
cmdstr.append('--noserver') cmdstr.append('--noserver')
@ -711,11 +718,13 @@ def server_operation(mode, service, interactive):
else: # all else: # all
# for convenience we don't start logging of # for convenience we don't start logging of
# portal, only of server with this command. # portal, only of server with this command.
if profiler:
cmdstr.append('--profile-server') # this is the common case
if interactive: if interactive:
cmdstr.extend(['--iserver']) cmdstr.append('--iserver')
management.call_command('collectstatic', verbosity=1, interactive=False) management.call_command('collectstatic', verbosity=1, interactive=False)
cmdstr.extend([GAMEDIR, TWISTED_BINARY, SERVER_LOGFILE, PORTAL_LOGFILE, HTTP_LOGFILE])
# start the server # start the server
cmdstr.append("start")
Popen(cmdstr) Popen(cmdstr)
elif mode == 'reload': elif mode == 'reload':
@ -805,14 +814,16 @@ def main():
# set up argument parser # set up argument parser
parser = ArgumentParser(description=CMDLINE_HELP) parser = ArgumentParser(description=CMDLINE_HELP)
parser.add_argument('-i', '--interactive', action='store_true',
dest='interactive', default=False,
help="Start given processes in interactive mode.")
parser.add_argument('-v', '--version', action='store_true', parser.add_argument('-v', '--version', action='store_true',
dest='show_version', default=False, dest='show_version', default=False,
help="Show version info.") help="Show version info.")
parser.add_argument('-i', '--interactive', action='store_true',
dest='interactive', default=False,
help="Start given processes in interactive mode.")
parser.add_argument('--init', action='store', dest="init", metavar="name", parser.add_argument('--init', action='store', dest="init", metavar="name",
help="Creates a new game directory 'name' at the current location.") help="Creates a new game directory 'name' at the current location.")
parser.add_argument('-p', '--prof', action='store_true', dest='profiler', default=False,
help="Start given server component under the Python profiler.")
parser.add_argument("mode", metavar="option", nargs='?', default="menu", parser.add_argument("mode", metavar="option", nargs='?', default="menu",
help="Operational mode or management option. Commonly start, stop, reload, migrate, or menu (default).") help="Operational mode or management option. Commonly start, stop, reload, migrate, or menu (default).")
parser.add_argument("service", metavar="component", nargs='?', choices=["all", "server", "portal"], default="all", parser.add_argument("service", metavar="component", nargs='?', choices=["all", "server", "portal"], default="all",
@ -847,7 +858,7 @@ def main():
# operate the server directly # operate the server directly
if mode != "stop": if mode != "stop":
check_database(False) check_database(False)
server_operation(mode, service, args.interactive) server_operation(mode, service, args.interactive, args.profiler)
else: else:
# pass-through to django manager # pass-through to django manager
if mode in ('runserver', 'testserver'): if mode in ('runserver', 'testserver'):

View file

@ -16,10 +16,9 @@ matter the value of this file.
""" """
import os import os
import sys import sys
from optparse import OptionParser from argparse import ArgumentParser
from subprocess import Popen from subprocess import Popen
import Queue, thread import Queue, thread
import django
try: try:
# check if launched with pypy # check if launched with pypy
@ -27,65 +26,54 @@ try:
except ImportError: except ImportError:
is_pypy = False is_pypy = False
# SERVER = None
# System Configuration PORTAL = None
#
SERVER_PIDFILE = "server.pid" EVENNIA_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PORTAL_PIDFILE = "portal.pid" EVENNIA_BIN = os.path.join(EVENNIA_ROOT, "bin")
EVENNIA_LIB = os.path.join(EVENNIA_ROOT, "evennia")
SERVER_RESTART = "server.restart" SERVER_PY_FILE = os.path.join(EVENNIA_LIB,'server', 'server.py')
PORTAL_RESTART = "portal.restart" PORTAL_PY_FILE = os.path.join(EVENNIA_LIB, 'server', 'portal', 'portal.py')
# Set the Python path up so we can get to settings.py from here. GAMEDIR = None
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) SERVERDIR = "server"
os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings' SERVER_PIDFILE = None
PORTAL_PIDFILE = None
SERVER_RESTART = None
PORTAL_RESTART = None
SERVER_LOGFILE = None
PORTAL_LOGFILE = None
HTTP_LOGFILE = None
if not os.path.exists('settings.py'): # messages
print "No settings.py file found. Run evennia.py to create it." CMDLINE_HELP = \
sys.exit() """
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.
"""
django.setup() PROCESS_ERROR = \
"""
{component} process error: {traceback}.
"""
# Get the settings PROCESS_IOERROR = \
from django.conf import settings """
{component} IOError: {traceback}
One possible explanation is that 'twistd' was not found.
"""
# Setup access of the evennia server itself PROCESS_RESTART = "{component} restarting ..."
SERVER_PY_FILE = os.path.join(settings.LIB_DIR, 'server/server.py')
PORTAL_PY_FILE = os.path.join(settings.LIB_DIR, 'server/portal/portal.py')
# Get logfile names
SERVER_LOGFILE = settings.SERVER_LOG_FILE
PORTAL_LOGFILE = settings.PORTAL_LOG_FILE
HTTP_LOGFILE = settings.HTTP_LOG_FILE.strip()
CYCLE_LOGFILES = settings.CYCLE_LOGFILES
# Add this to the environmental variable for the 'twistd' command.
currpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if 'PYTHONPATH' in os.environ:
os.environ['PYTHONPATH'] += (":%s" % currpath)
else:
os.environ['PYTHONPATH'] = currpath
TWISTED_BINARY = 'twistd'
if os.name == 'nt':
TWISTED_BINARY = 'twistd.bat'
err = False
try:
import win32api # Test for for win32api
except ImportError:
err = True
if not os.path.exists(TWISTED_BINARY):
err = True
if err:
print "Twisted binary for Windows is not ready to use. Please run evennia.py."
sys.exit()
# Functions # Functions
def set_restart_mode(restart_file, flag="reload"): def set_restart_mode(restart_file, flag="reload"):
""" """
This sets a flag file for the restart mode. This sets a flag file for the restart mode.
@ -130,9 +118,6 @@ def cycle_logfile(logfile):
# Start program management # Start program management
SERVER = None
PORTAL = None
def start_services(server_argv, portal_argv): def start_services(server_argv, portal_argv):
""" """
@ -146,7 +131,7 @@ def start_services(server_argv, portal_argv):
try: try:
rc = Popen(server_argv).wait() rc = Popen(server_argv).wait()
except Exception, e: except Exception, e:
print "Server process error: %(e)s" % {'e': e} print PROCESS_ERROR.format(component="Server", traceback=e)
return return
# this signals the controller that the program finished # this signals the controller that the program finished
queue.put(("server_stopped", rc)) queue.put(("server_stopped", rc))
@ -155,7 +140,7 @@ def start_services(server_argv, portal_argv):
try: try:
rc = Popen(portal_argv).wait() rc = Popen(portal_argv).wait()
except Exception, e: except Exception, e:
print "Portal process error: %(e)s" % {'e': e} print PROCESS_ERROR.format(component="Portal", traceback=e)
return return
# this signals the controller that the program finished # this signals the controller that the program finished
queue.put(("portal_stopped", rc)) queue.put(("portal_stopped", rc))
@ -170,7 +155,7 @@ def start_services(server_argv, portal_argv):
# we don't care to monitor it for restart # we don't care to monitor it for restart
PORTAL = Popen(portal_argv) PORTAL = Popen(portal_argv)
except IOError, e: except IOError, e:
print "Portal IOError: %s\nA possible explanation for this is that 'twistd' is not found." % e print PROCESS_IOERROR.format(component="Portal", traceback=e)
return return
try: try:
@ -178,7 +163,7 @@ def start_services(server_argv, portal_argv):
# start server as a reloadable thread # start server as a reloadable thread
SERVER = thread.start_new_thread(server_waiter, (processes, )) SERVER = thread.start_new_thread(server_waiter, (processes, ))
except IOError, e: except IOError, e:
print "Server IOError: %s\nA possible explanation for this is that 'twistd' is not found." % e print PROCESS_IOERROR.format(component="Server", traceback=e)
return return
# Reload loop # Reload loop
@ -190,53 +175,64 @@ def start_services(server_argv, portal_argv):
# restart only if process stopped cleanly # restart only if process stopped cleanly
if (message == "server_stopped" and int(rc) == 0 and if (message == "server_stopped" and int(rc) == 0 and
get_restart_mode(SERVER_RESTART) in ("True", "reload", "reset")): get_restart_mode(SERVER_RESTART) in ("True", "reload", "reset")):
print "Evennia Server stopped. Restarting ..." print PROCESS_RESTART.format(component="Server")
SERVER = thread.start_new_thread(server_waiter, (processes, )) SERVER = thread.start_new_thread(server_waiter, (processes, ))
continue continue
# normally the portal is not reloaded since it's run as a daemon. # normally the portal is not reloaded since it's run as a daemon.
if (message == "portal_stopped" and int(rc) == 0 and if (message == "portal_stopped" and int(rc) == 0 and
get_restart_mode(PORTAL_RESTART) == "True"): get_restart_mode(PORTAL_RESTART) == "True"):
print "Evennia Portal stopped in interactive mode. Restarting ..." print PROCESS_RESTART.format(component="Portal")
PORTAL = thread.start_new_thread(portal_waiter, (processes, )) PORTAL = thread.start_new_thread(portal_waiter, (processes, ))
continue continue
break break
# Setup signal handling
def main(): def main():
""" """
This handles the command line input of the runner This handles the command line input of the runner, usually created by
(it's most often called by evennia.py) the evennia launcher
""" """
parser = OptionParser(usage="%prog [options] start", parser = ArgumentParser(description=CMDLINE_HELP)
description="This runner should normally *not* be called directly - it is called automatically from the evennia.py main program. It manages the Evennia game server and portal processes an hosts a threaded loop to restart the Server whenever it is stopped (this constitues Evennia's reload mechanism).") parser.add_argument('--noserver', action='store_true', dest='noserver',
parser.add_option('-s', '--noserver', action='store_true', default=False, help='Do not start Server process')
dest='noserver', default=False, parser.add_argument('--noportal', action='store_true', dest='noportal',
help='Do not start Server process') default=False, help='Do not start Portal process')
parser.add_option('-p', '--noportal', action='store_true', parser.add_argument('--iserver', action='store_true', dest='iserver',
dest='noportal', default=False, default=False, help='Server in interactive mode')
help='Do not start Portal process') parser.add_argument('--iportal', action='store_true', dest='iportal',
parser.add_option('-i', '--iserver', action='store_true', default=False, help='Portal in interactive mode')
dest='iserver', default=False, parser.add_argument('--pserver', action='store_true', dest='pserver',
help='output server log to stdout instead of logfile') default=False, help='Profile Server')
parser.add_option('-d', '--iportal', action='store_true', parser.add_argument('--pportal', action='store_true', dest='pportal',
dest='iportal', default=False, default=False, help='Profile Portal')
help='output portal log to stdout. Does not make portal a daemon.') parser.add_argument('--nologcycle', action='store_false', dest='nologcycle',
parser.add_option('-S', '--profile-server', action='store_true', default=True, help='Do not cycle log files')
dest='sprof', default=False, parser.add_argument('gamedir', help="path to game dir")
help='run server under cProfile') parser.add_argument('twistdbinary', help="path to twistd binary")
parser.add_option('-P', '--profile-portal', action='store_true', parser.add_argument('slogfile', help="path to server log file")
dest='pprof', default=False, parser.add_argument('plogfile', help="path to portal log file")
help='run portal under cProfile') parser.add_argument('hlogfile', help="path to http log file")
options, args = parser.parse_args() args = parser.parse_args()
if not args or args[0] != 'start': global GAMEDIR
# this is so as to avoid runner.py be accidentally launched manually. global SERVER_LOGFILE, PORTAL_LOGFILE, HTTP_LOGFILE
parser.print_help() global SERVER_PIDFILE, PORTAL_PIDFILE
sys.exit() 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 # set up default project calls
server_argv = [TWISTED_BINARY, server_argv = [TWISTED_BINARY,
@ -251,57 +247,57 @@ def main():
# Profiling settings (read file from python shell e.g with # Profiling settings (read file from python shell e.g with
# p = pstats.Stats('server.prof') # p = pstats.Stats('server.prof')
sprof_argv = ['--savestats', pserver_argv = ['--savestats',
'--profiler=cprofile', '--profiler=cprofile',
'--profile=server.prof'] '--profile=server.prof']
pprof_argv = ['--savestats', pportal_argv = ['--savestats',
'--profiler=cprofile', '--profiler=cprofile',
'--profile=portal.prof'] '--profile=portal.prof']
# Server # Server
pid = get_pid(SERVER_PIDFILE) pid = get_pid(SERVER_PIDFILE)
if pid and not options.noserver: if pid and not args.noserver:
print "\nEvennia Server is already running as process %(pid)s. Not restarted." % {'pid': pid} print "\nEvennia Server is already running as process %(pid)s. Not restarted." % {'pid': pid}
options.noserver = True args.noserver = True
if options.noserver: if args.noserver:
server_argv = None server_argv = None
else: else:
set_restart_mode(SERVER_RESTART, "shutdown") set_restart_mode(SERVER_RESTART, "shutdown")
if options.iserver: if args.iserver:
# don't log to server logfile # don't log to server logfile
del server_argv[2] del server_argv[2]
print "\nStarting Evennia Server (output to stdout)." print "\nStarting Evennia Server (output to stdout)."
else: else:
if CYCLE_LOGFILES: if not args.nologcycle:
cycle_logfile(SERVER_LOGFILE) cycle_logfile(SERVER_LOGFILE)
print "\nStarting Evennia Server (output to server logfile)." print "\nStarting Evennia Server (output to server logfile)."
if options.sprof: if args.pserver:
server_argv.extend(sprof_argv) server_argv.extend(pserver_argv)
print "\nRunning Evennia Server under cProfile." print "\nRunning Evennia Server under cProfile."
# Portal # Portal
pid = get_pid(PORTAL_PIDFILE) pid = get_pid(PORTAL_PIDFILE)
if pid and not options.noportal: if pid and not args.noportal:
print "\nEvennia Portal is already running as process %(pid)s. Not restarted." % {'pid': pid} print "\nEvennia Portal is already running as process %(pid)s. Not restarted." % {'pid': pid}
options.noportal = True args.noportal = True
if options.noportal: if args.noportal:
portal_argv = None portal_argv = None
else: else:
if options.iportal: if args.iportal:
# make portal interactive # make portal interactive
portal_argv[1] = '--nodaemon' portal_argv[1] = '--nodaemon'
set_restart_mode(PORTAL_RESTART, True) set_restart_mode(PORTAL_RESTART, True)
print "\nStarting Evennia Portal in non-Daemon mode (output to stdout)." print "\nStarting Evennia Portal in non-Daemon mode (output to stdout)."
else: else:
if CYCLE_LOGFILES: if not args.nologcycle:
cycle_logfile(PORTAL_LOGFILE) cycle_logfile(PORTAL_LOGFILE)
cycle_logfile(HTTP_LOGFILE) cycle_logfile(HTTP_LOGFILE)
set_restart_mode(PORTAL_RESTART, False) set_restart_mode(PORTAL_RESTART, False)
print "\nStarting Evennia Portal in Daemon mode (output to portal logfile)." print "\nStarting Evennia Portal in Daemon mode (output to portal logfile)."
if options.pprof: if args.pportal:
portal_argv.extend(pprof_argv) portal_argv.extend(pportal_argv)
print "\nRunning Evennia Portal under cProfile." print "\nRunning Evennia Portal under cProfile."
# Windows fixes (Windows don't support pidfiles natively) # Windows fixes (Windows don't support pidfiles natively)
@ -315,6 +311,4 @@ def main():
start_services(server_argv, portal_argv) start_services(server_argv, portal_argv)
if __name__ == '__main__': if __name__ == '__main__':
from evennia.utils.utils import check_evennia_dependencies
if check_evennia_dependencies():
main() main()