2011-09-03 10:22:19 +00:00
#!/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 .
"""
2012-03-30 23:57:04 +02:00
import os
2011-09-03 10:22:19 +00:00
import sys
from optparse import OptionParser
2012-05-01 14:19:54 +02:00
from subprocess import Popen
import Queue , thread
2011-09-03 10:22:19 +00:00
2013-01-31 14:34:52 -08:00
try :
2013-02-01 21:51:38 +01:00
# check if launched with pypy
2013-01-31 14:34:52 -08:00
import __pypy__ as is_pypy
except ImportError :
is_pypy = False
2011-09-03 10:22:19 +00:00
#
# System Configuration
2012-03-30 23:57:04 +02:00
#
2011-09-03 10:22:19 +00:00
SERVER_PIDFILE = " server.pid "
PORTAL_PIDFILE = " portal.pid "
SERVER_RESTART = " server.restart "
PORTAL_RESTART = " portal.restart "
# Set the Python path up so we can get to settings.py from here.
sys . path . insert ( 0 , os . path . dirname ( os . path . dirname ( os . path . abspath ( __file__ ) ) ) )
os . environ [ ' DJANGO_SETTINGS_MODULE ' ] = ' game.settings '
if not os . path . exists ( ' settings.py ' ) :
2012-02-15 18:56:27 +01:00
print " No settings.py file found. Run evennia.py to create it. "
2011-09-03 10:22:19 +00:00
sys . exit ( )
2012-03-30 23:57:04 +02:00
2011-09-03 10:22:19 +00:00
# Get the settings
from django . conf import settings
# Setup access of the evennia server itself
SERVER_PY_FILE = os . path . join ( settings . SRC_DIR , ' server/server.py ' )
2013-05-22 18:40:16 +02:00
PORTAL_PY_FILE = os . path . join ( settings . SRC_DIR , ' server/portal/portal.py ' )
2011-09-03 10:22:19 +00:00
# Get logfile names
SERVER_LOGFILE = settings . SERVER_LOG_FILE
PORTAL_LOGFILE = settings . PORTAL_LOG_FILE
2013-06-03 17:03:31 +02:00
HTTP_LOGFILE = settings . HTTP_LOG_FILE . strip ( )
CYCLE_LOGFILES = settings . CYCLE_LOGFILES
2011-09-03 10:22:19 +00:00
# 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
2012-03-30 23:57:04 +02:00
TWISTED_BINARY = ' twistd '
2011-09-03 10:22:19 +00:00
if os . name == ' nt ' :
TWISTED_BINARY = ' twistd.bat '
2012-03-30 23:57:04 +02:00
err = False
2011-09-03 10:22:19 +00:00
try :
import win32api # Test for for win32api
except ImportError :
2012-03-30 23:57:04 +02:00
err = True
2011-09-03 10:22:19 +00:00
if not os . path . exists ( TWISTED_BINARY ) :
2012-03-30 23:57:04 +02:00
err = True
2011-09-03 10:22:19 +00:00
if err :
2012-02-15 18:56:27 +01:00
print " Twisted binary for Windows is not ready to use. Please run evennia.py. "
2011-09-03 10:22:19 +00:00
sys . exit ( )
2012-03-30 23:57:04 +02:00
# Functions
2011-09-03 10:22:19 +00:00
2013-11-14 19:31:17 +01:00
2012-10-28 22:02:22 +01:00
def set_restart_mode ( restart_file , flag = " reload " ) :
2011-09-03 10:22:19 +00:00
"""
2012-03-30 23:57:04 +02:00
This sets a flag file for the restart mode .
"""
2012-10-28 22:02:22 +01:00
with open ( restart_file , ' w ' ) as f :
f . write ( str ( flag ) )
2011-09-03 10:22:19 +00:00
2013-11-14 19:31:17 +01:00
2011-09-03 10:22:19 +00:00
def get_restart_mode ( restart_file ) :
"""
Parse the server / portal restart status
"""
if os . path . exists ( restart_file ) :
2012-10-28 22:02:22 +01:00
with open ( restart_file , ' r ' ) as f :
return f . read ( )
return " shutdown "
2011-09-03 10:22:19 +00:00
2013-11-14 19:31:17 +01:00
2011-09-03 10:22:19 +00:00
def get_pid ( pidfile ) :
"""
Get the PID ( Process ID ) by trying to access
2012-03-30 23:57:04 +02:00
an PID file .
2011-09-03 10:22:19 +00:00
"""
2012-03-30 23:57:04 +02:00
pid = None
2011-09-03 10:22:19 +00:00
if os . path . exists ( pidfile ) :
2012-10-28 22:02:22 +01:00
with open ( pidfile , ' r ' ) as f :
pid = f . read ( )
2012-03-30 23:57:04 +02:00
return pid
2011-09-03 10:22:19 +00:00
2013-11-14 19:31:17 +01:00
2013-06-03 17:03:31 +02:00
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 )
2012-03-30 23:57:04 +02:00
# Start program management
2011-09-03 10:22:19 +00:00
SERVER = None
2012-03-30 23:57:04 +02:00
PORTAL = None
2011-09-03 10:22:19 +00:00
2013-11-14 19:31:17 +01:00
2011-09-03 10:22:19 +00:00
def start_services ( server_argv , portal_argv ) :
"""
This calls a threaded loop that launces the Portal and Server
2012-03-30 23:57:04 +02:00
and then restarts them when they finish .
2011-09-03 10:22:19 +00:00
"""
2012-03-30 23:57:04 +02:00
global SERVER , PORTAL
2011-09-03 10:22:19 +00:00
processes = Queue . Queue ( )
2012-03-30 23:57:04 +02:00
def server_waiter ( queue ) :
try :
2011-09-03 10:22:19 +00:00
rc = Popen ( server_argv ) . wait ( )
except Exception , e :
2012-02-15 18:56:27 +01:00
print " Server process error: %(e)s " % { ' e ' : e }
2012-07-06 10:09:53 +02:00
return
2013-11-14 19:31:17 +01:00
# this signals the controller that the program finished
queue . put ( ( " server_stopped " , rc ) )
2011-09-03 10:22:19 +00:00
2012-03-30 23:57:04 +02:00
def portal_waiter ( queue ) :
try :
2011-09-03 10:22:19 +00:00
rc = Popen ( portal_argv ) . wait ( )
except Exception , e :
2012-02-15 18:56:27 +01:00
print " Portal process error: %(e)s " % { ' e ' : e }
2012-07-07 01:52:53 +02:00
return
2013-11-14 19:31:17 +01:00
# this signals the controller that the program finished
queue . put ( ( " portal_stopped " , rc ) )
2012-03-30 23:57:04 +02:00
if portal_argv :
2012-07-07 01:52:53 +02:00
try :
2012-10-28 22:02:22 +01:00
if get_restart_mode ( PORTAL_RESTART ) == " True " :
2012-07-07 01:52:53 +02:00
# start portal as interactive, reloadable thread
PORTAL = thread . start_new_thread ( portal_waiter , ( processes , ) )
else :
2013-11-14 19:31:17 +01:00
# normal operation: start portal as a daemon;
# we don't care to monitor it for restart
2012-07-07 01:52:53 +02:00
PORTAL = Popen ( portal_argv )
except IOError , e :
print " Portal IOError: %s \n A possible explanation for this is that ' twistd ' is not found. " % e
return
2011-09-03 10:22:19 +00:00
2013-01-31 15:40:20 -08:00
try :
if server_argv :
# start server as a reloadable thread
SERVER = thread . start_new_thread ( server_waiter , ( processes , ) )
except IOError , e :
print " Server IOError: %s \n A possible explanation for this is that ' twistd ' is not found. " % e
return
2012-03-30 23:57:04 +02:00
# Reload loop
2011-09-03 10:22:19 +00:00
while True :
2012-03-30 23:57:04 +02:00
2011-09-03 10:22:19 +00:00
# this blocks until something is actually returned.
2012-03-30 23:57:04 +02:00
message , rc = processes . get ( )
2011-09-03 10:22:19 +00:00
# restart only if process stopped cleanly
2013-11-14 19:31:17 +01:00
if ( message == " server_stopped " and int ( rc ) == 0 and
get_restart_mode ( SERVER_RESTART ) in ( " True " , " reload " , " reset " ) ) :
2012-03-30 23:57:04 +02:00
print " Evennia Server stopped. Restarting ... "
2011-09-03 10:22:19 +00:00
SERVER = thread . start_new_thread ( server_waiter , ( processes , ) )
2012-03-30 23:57:04 +02:00
continue
2011-09-03 10:22:19 +00:00
# normally the portal is not reloaded since it's run as a daemon.
2013-11-14 19:31:17 +01:00
if ( message == " portal_stopped " and int ( rc ) == 0 and
get_restart_mode ( PORTAL_RESTART ) == " True " ) :
2012-02-15 18:56:27 +01:00
print " Evennia Portal stopped in interactive mode. Restarting ... "
2012-03-30 23:57:04 +02:00
PORTAL = thread . start_new_thread ( portal_waiter , ( processes , ) )
continue
break
2011-09-03 10:22:19 +00:00
# Setup signal handling
def main ( ) :
"""
2013-11-14 19:31:17 +01:00
This handles the command line input of the runner
( it ' s most often called by evennia.py)
2011-09-03 10:22:19 +00:00
"""
2012-03-30 23:57:04 +02:00
2011-09-03 10:22:19 +00:00
parser = OptionParser ( usage = " % prog [options] start " ,
2013-11-14 19:31:17 +01:00
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). " )
2012-03-30 23:57:04 +02:00
parser . add_option ( ' -s ' , ' --noserver ' , action = ' store_true ' ,
2011-09-03 10:22:19 +00:00
dest = ' noserver ' , default = False ,
2012-02-15 18:56:27 +01:00
help = ' Do not start Server process ' )
2012-03-30 23:57:04 +02:00
parser . add_option ( ' -p ' , ' --noportal ' , action = ' store_true ' ,
2011-09-03 10:22:19 +00:00
dest = ' noportal ' , default = False ,
2012-02-15 18:56:27 +01:00
help = ' Do not start Portal process ' )
2012-03-30 23:57:04 +02:00
parser . add_option ( ' -i ' , ' --iserver ' , action = ' store_true ' ,
2011-09-03 10:22:19 +00:00
dest = ' iserver ' , default = False ,
2012-02-15 18:56:27 +01:00
help = ' output server log to stdout instead of logfile ' )
2012-03-30 23:57:04 +02:00
parser . add_option ( ' -d ' , ' --iportal ' , action = ' store_true ' ,
2011-09-03 10:22:19 +00:00
dest = ' iportal ' , default = False ,
2012-02-15 18:56:27 +01:00
help = ' output portal log to stdout. Does not make portal a daemon. ' )
2012-02-03 00:15:25 +01:00
parser . add_option ( ' -S ' , ' --profile-server ' , action = ' store_true ' ,
dest = ' sprof ' , default = False ,
help = ' run server under cProfile ' )
parser . add_option ( ' -P ' , ' --profile-portal ' , action = ' store_true ' ,
dest = ' pprof ' , default = False ,
help = ' run portal under cProfile ' )
2011-09-03 10:22:19 +00:00
options , args = parser . parse_args ( )
if not args or args [ 0 ] != ' start ' :
2012-07-06 10:09:53 +02:00
# this is so as to avoid runner.py be accidentally launched manually.
2011-09-03 10:22:19 +00:00
parser . print_help ( )
sys . exit ( )
2012-03-30 23:57:04 +02:00
# set up default project calls
server_argv = [ TWISTED_BINARY ,
2011-09-03 10:22:19 +00:00
' --nodaemon ' ,
' --logfile= %s ' % SERVER_LOGFILE ,
2012-03-30 23:57:04 +02:00
' --pidfile= %s ' % SERVER_PIDFILE ,
2011-09-03 10:22:19 +00:00
' --python= %s ' % SERVER_PY_FILE ]
portal_argv = [ TWISTED_BINARY ,
' --logfile= %s ' % PORTAL_LOGFILE ,
2012-03-30 23:57:04 +02:00
' --pidfile= %s ' % PORTAL_PIDFILE ,
' --python= %s ' % PORTAL_PY_FILE ]
2012-02-03 00:15:25 +01:00
# Profiling settings (read file from python shell e.g with
# p = pstats.Stats('server.prof')
sprof_argv = [ ' --savestats ' ,
' --profiler=cprofile ' ,
' --profile=server.prof ' ]
pprof_argv = [ ' --savestats ' ,
' --profiler=cprofile ' ,
' --profile=portal.prof ' ]
2012-03-30 23:57:04 +02:00
# Server
2011-09-03 10:22:19 +00:00
pid = get_pid ( SERVER_PIDFILE )
if pid and not options . noserver :
2012-02-15 18:56:27 +01:00
print " \n Evennia Server is already running as process %(pid)s . Not restarted. " % { ' pid ' : pid }
2011-09-03 10:22:19 +00:00
options . noserver = True
if options . noserver :
2012-03-30 23:57:04 +02:00
server_argv = None
2011-09-03 10:22:19 +00:00
else :
2012-10-28 22:02:22 +01:00
set_restart_mode ( SERVER_RESTART , " shutdown " )
2011-09-03 10:22:19 +00:00
if options . iserver :
# don't log to server logfile
del server_argv [ 2 ]
2012-02-15 18:56:27 +01:00
print " \n Starting Evennia Server (output to stdout). "
2011-09-03 10:22:19 +00:00
else :
2013-06-03 17:03:31 +02:00
if CYCLE_LOGFILES :
cycle_logfile ( SERVER_LOGFILE )
2012-02-15 18:56:27 +01:00
print " \n Starting Evennia Server (output to server logfile). "
2012-02-05 17:39:43 +01:00
if options . sprof :
server_argv . extend ( sprof_argv )
print " \n Running Evennia Server under cProfile. "
2012-03-30 23:57:04 +02:00
# Portal
2011-09-03 10:22:19 +00:00
pid = get_pid ( PORTAL_PIDFILE )
if pid and not options . noportal :
2012-03-30 23:57:04 +02:00
print " \n Evennia Portal is already running as process %(pid)s . Not restarted. " % { ' pid ' : pid }
options . noportal = True
2011-09-03 10:22:19 +00:00
if options . noportal :
2012-03-30 23:57:04 +02:00
portal_argv = None
2011-09-03 10:22:19 +00:00
else :
if options . iportal :
# make portal interactive
portal_argv [ 1 ] = ' --nodaemon '
set_restart_mode ( PORTAL_RESTART , True )
2012-02-15 18:56:27 +01:00
print " \n Starting Evennia Portal in non-Daemon mode (output to stdout). "
2011-09-03 10:22:19 +00:00
else :
2013-06-03 17:03:31 +02:00
if CYCLE_LOGFILES :
cycle_logfile ( PORTAL_LOGFILE )
cycle_logfile ( HTTP_LOGFILE )
2011-09-03 10:22:19 +00:00
set_restart_mode ( PORTAL_RESTART , False )
2012-02-15 18:56:27 +01:00
print " \n Starting Evennia Portal in Daemon mode (output to portal logfile). "
2012-02-05 17:39:43 +01:00
if options . pprof :
2012-02-26 13:45:07 +01:00
portal_argv . extend ( pprof_argv )
2012-02-05 17:39:43 +01:00
print " \n Running Evennia Portal under cProfile. "
2011-09-03 10:22:19 +00:00
# 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 )
2012-03-30 23:57:04 +02:00
2011-09-03 10:22:19 +00:00
if __name__ == ' __main__ ' :
from src . utils . utils import check_evennia_dependencies
if check_evennia_dependencies ( ) :
main ( )