Make portal possible to start in the foreground too

This commit is contained in:
Griatch 2018-09-25 00:24:45 +02:00
parent 0d3c9ebea3
commit 07d56f562b
3 changed files with 99 additions and 44 deletions

View file

@ -42,7 +42,6 @@ EVENNIA_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(_
import evennia # noqa import evennia # noqa
EVENNIA_LIB = os.path.join(os.path.dirname(os.path.abspath(evennia.__file__))) EVENNIA_LIB = os.path.join(os.path.dirname(os.path.abspath(evennia.__file__)))
EVENNIA_SERVER = os.path.join(EVENNIA_LIB, "server") EVENNIA_SERVER = os.path.join(EVENNIA_LIB, "server")
EVENNIA_RUNNER = os.path.join(EVENNIA_SERVER, "evennia_runner.py")
EVENNIA_TEMPLATE = os.path.join(EVENNIA_LIB, "game_template") EVENNIA_TEMPLATE = os.path.join(EVENNIA_LIB, "game_template")
EVENNIA_PROFILING = os.path.join(EVENNIA_SERVER, "profiling") EVENNIA_PROFILING = os.path.join(EVENNIA_SERVER, "profiling")
EVENNIA_DUMMYRUNNER = os.path.join(EVENNIA_PROFILING, "dummyrunner.py") EVENNIA_DUMMYRUNNER = os.path.join(EVENNIA_PROFILING, "dummyrunner.py")
@ -467,7 +466,8 @@ ARG_OPTIONS = \
stop - shutdown server+portal stop - shutdown server+portal
reboot - shutdown server+portal, then start again reboot - shutdown server+portal, then start again
reset - restart server in 'shutdown' mode reset - restart server in 'shutdown' mode
istart - start server in the foreground (until reload) istart - start server in foreground (until reload)
ipstart - start portal in foreground
sstop - stop only server sstop - stop only server
kill - send kill signal to portal+server (force) kill - send kill signal to portal+server (force)
skill - send kill signal only to server skill - send kill signal only to server
@ -974,6 +974,47 @@ def start_server_interactive():
stop_server_only(when_stopped=_iserver, interactive=True) stop_server_only(when_stopped=_iserver, interactive=True)
def start_portal_interactive():
"""
Start the Portal under control of the launcher process (foreground)
Notes:
In a normal start, the launcher waits for the Portal to start, then
tells it to start the Server. Since we can't do this here, we instead
start the Server first and then starts the Portal - the Server will
auto-reconnect to the Portal. To allow the Server to be reloaded, this
relies on a fixed server server-cmdline stored as a fallback on the
portal application in evennia/server/portal/portal.py.
"""
def _iportal(fail):
portal_twistd_cmd, server_twistd_cmd = _get_twistd_cmdline(False, False)
portal_twistd_cmd.append("--nodaemon")
# starting Server first - it will auto-connect once Portal comes up
if _is_windows():
# Windows requires special care
create_no_window = 0x08000000
Popen(server_twistd_cmd, env=getenv(), bufsize=-1,
creationflags=create_no_window)
else:
Popen(server_twistd_cmd, env=getenv(), bufsize=-1)
print("Starting Portal in interactive mode (stop with Ctrl-C)...")
try:
Popen(portal_twistd_cmd, env=getenv(), stderr=STDOUT).wait()
except KeyboardInterrupt:
print("... Stopped Portal with Ctrl-C.")
else:
print("... Portal stopped (leaving interactive mode).")
def _portal_running(response):
print("Evennia must be shut down completely before running Portal in interactive mode.")
_reactor_stop()
send_instruction(PSTATUS, None, _portal_running, _iportal)
def stop_server_only(when_stopped=None, interactive=False): def stop_server_only(when_stopped=None, interactive=False):
""" """
Only stop the Server-component of Evennia (this is not useful except for debug) Only stop the Server-component of Evennia (this is not useful except for debug)
@ -981,7 +1022,8 @@ def stop_server_only(when_stopped=None, interactive=False):
Args: Args:
when_stopped (callable): This will be called with no arguments when Server has stopped (or when_stopped (callable): This will be called with no arguments when Server has stopped (or
if it had already stopped when this is called). if it had already stopped when this is called).
interactive (bool, optional): Set if this is called as part of the interactive reload mechanism. interactive (bool, optional): Set if this is called as part of the interactive reload
mechanism.
""" """
def _server_stopped(*args): def _server_stopped(*args):
@ -1972,7 +2014,7 @@ def main():
# launch menu for operation # launch menu for operation
init_game_directory(CURRENT_DIR, check_db=True) init_game_directory(CURRENT_DIR, check_db=True)
run_menu() run_menu()
elif option in ('status', 'info', 'start', 'istart', 'reload', 'reboot', elif option in ('status', 'info', 'start', 'istart', 'ipstart', 'reload', 'reboot',
'reset', 'stop', 'sstop', 'kill', 'skill'): 'reset', 'stop', 'sstop', 'kill', 'skill'):
# operate the server directly # operate the server directly
if not SERVER_LOGFILE: if not SERVER_LOGFILE:
@ -1985,6 +2027,8 @@ def main():
start_evennia(args.profiler, args.profiler) start_evennia(args.profiler, args.profiler)
elif option == "istart": elif option == "istart":
start_server_interactive() start_server_interactive()
elif option == "ipstart":
start_portal_interactive()
elif option == 'reload': elif option == 'reload':
reload_evennia(args.profiler) reload_evennia(args.profiler)
elif option == 'reboot': elif option == 'reboot':

View file

@ -336,9 +336,9 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
elif operation == amp.PSHUTD: # portal + server shutdown #16 elif operation == amp.PSHUTD: # portal + server shutdown #16
if server_connected: if server_connected:
self.factory.server_connection.wait_for_disconnect( self.factory.server_connection.wait_for_disconnect(
self.factory.portal.shutdown, restart=False) self.factory.portal.shutdown )
else: else:
self.factory.portal.shutdown(restart=False) self.factory.portal.shutdown()
else: else:
raise Exception("operation %(op)s not recognized." % {'op': operation}) raise Exception("operation %(op)s not recognized." % {'op': operation})
@ -414,7 +414,7 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
elif operation == amp.PSHUTD: # full server+server shutdown elif operation == amp.PSHUTD: # full server+server shutdown
self.factory.server_connection.wait_for_disconnect( self.factory.server_connection.wait_for_disconnect(
self.factory.portal.shutdown, restart=False) self.factory.portal.shutdown)
self.stop_server(mode='shutdown') self.stop_server(mode='shutdown')
elif operation == amp.PSYNC: # portal sync elif operation == amp.PSYNC: # portal sync

View file

@ -12,6 +12,7 @@ from builtins import object
import sys import sys
import os import os
from os.path import dirname, abspath
from twisted.application import internet, service from twisted.application import internet, service
from twisted.internet import protocol, reactor from twisted.internet import protocol, reactor
from twisted.python.log import ILogObserver from twisted.python.log import ILogObserver
@ -113,41 +114,46 @@ class Portal(object):
self.server_restart_mode = "shutdown" self.server_restart_mode = "shutdown"
self.server_info_dict = {} self.server_info_dict = {}
# in non-interactive portal mode, this gets overwritten by
# cmdline sent by the evennia launcher
self.server_twistd_cmd = self._get_backup_server_twistd_cmd()
# set a callback if the server is killed abruptly, # set a callback if the server is killed abruptly,
# by Ctrl-C, reboot etc. # by Ctrl-C, reboot etc.
reactor.addSystemEventTrigger('before', 'shutdown', self.shutdown, _reactor_stopping=True) reactor.addSystemEventTrigger('before', 'shutdown',
self.shutdown, _reactor_stopping=True, _stop_server=True)
def _get_backup_server_twistd_cmd(self):
"""
For interactive Portal mode there is no way to get the server cmdline from the launcher, so
we need to guess it here (it's very likely to not change)
Returns:
server_twistd_cmd (list): An instruction for starting the server, to pass to Popen.
"""
server_twistd_cmd = [
"twistd",
"--python={}".format(os.path.join(dirname(dirname(abspath(__file__))), "server.py"))]
if os.name != 'nt':
gamedir = os.getcwd()
server_twistd_cmd.append("--pidfile={}".format(
os.path.join(gamedir, "server", "server.pid")))
return server_twistd_cmd
def get_info_dict(self): def get_info_dict(self):
"Return the Portal info, for display." "Return the Portal info, for display."
return INFO_DICT return INFO_DICT
def set_restart_mode(self, mode=None): def shutdown(self, _reactor_stopping=False, _stop_server=False):
"""
This manages the flag file that tells the runner if the server
should be restarted or is shutting down.
Args:
mode (bool or None): Valid modes are True/False and None.
If mode is None, no change will be done to the flag file.
"""
if mode is None:
return
with open(PORTAL_RESTART, 'w') as f:
f.write(str(mode))
def shutdown(self, restart=None, _reactor_stopping=False):
""" """
Shuts down the server from inside it. Shuts down the server from inside it.
Args: Args:
restart (bool or None, optional): True/False sets the
flags so the server will be restarted or not. If None, the
current flag setting (set at initialization or previous
runs) is used.
_reactor_stopping (bool, optional): This is set if server _reactor_stopping (bool, optional): This is set if server
is already in the process of shutting down; in this case is already in the process of shutting down; in this case
we don't need to stop it again. we don't need to stop it again.
_stop_server (bool, optional): Only used in portal-interactive mode;
makes sure to stop the Server cleanly.
Note that restarting (regardless of the setting) will not work Note that restarting (regardless of the setting) will not work
if the Portal is currently running in daemon mode. In that if the Portal is currently running in daemon mode. In that
@ -158,8 +164,10 @@ class Portal(object):
# we get here due to us calling reactor.stop below. No need # we get here due to us calling reactor.stop below. No need
# to do the shutdown procedure again. # to do the shutdown procedure again.
return return
self.sessions.disconnect_all() self.sessions.disconnect_all()
self.set_restart_mode(restart) if _stop_server:
self.amp_protocol.stop_server(mode='shutdown')
if not _reactor_stopping: if not _reactor_stopping:
# shutting down the reactor will trigger another signal. We set # shutting down the reactor will trigger another signal. We set
@ -179,9 +187,11 @@ class Portal(object):
application = service.Application('Portal') application = service.Application('Portal')
# custom logging # custom logging
logfile = logger.WeeklyLogFile(os.path.basename(settings.PORTAL_LOG_FILE),
if "--nodaemon" not in sys.argv:
logfile = logger.WeeklyLogFile(os.path.basename(settings.PORTAL_LOG_FILE),
os.path.dirname(settings.PORTAL_LOG_FILE)) os.path.dirname(settings.PORTAL_LOG_FILE))
application.setComponent(ILogObserver, logger.PortalLogObserver(logfile).emit) application.setComponent(ILogObserver, logger.PortalLogObserver(logfile).emit)
# The main Portal server program. This sets up the database # The main Portal server program. This sets up the database
# and is where we store all the other services. # and is where we store all the other services.
@ -331,7 +341,8 @@ if WEBSERVER_ENABLED:
factory.noisy = False factory.noisy = False
factory.protocol = webclient.WebSocketClient factory.protocol = webclient.WebSocketClient
factory.sessionhandler = PORTAL_SESSIONS factory.sessionhandler = PORTAL_SESSIONS
websocket_service = internet.TCPServer(port, WebSocketFactory(factory), interface=w_interface) websocket_service = internet.TCPServer(port, WebSocketFactory(factory),
interface=w_interface)
websocket_service.setName('EvenniaWebSocket%s:%s' % (w_ifacestr, port)) websocket_service.setName('EvenniaWebSocket%s:%s' % (w_ifacestr, port))
PORTAL.services.addService(websocket_service) PORTAL.services.addService(websocket_service)
websocket_started = True websocket_started = True