Make portal possible to start in the foreground too

This commit is contained in:
Griatch 2018-09-25 00:24:45 +02:00
parent 384067479b
commit d1d9ea7a0a
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
EVENNIA_LIB = os.path.join(os.path.dirname(os.path.abspath(evennia.__file__)))
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_PROFILING = os.path.join(EVENNIA_SERVER, "profiling")
EVENNIA_DUMMYRUNNER = os.path.join(EVENNIA_PROFILING, "dummyrunner.py")
@ -462,18 +461,19 @@ SERVER_INFO = \
ARG_OPTIONS = \
"""Actions on installed server. One of:
start - launch server+portal if not running
reload - restart server in 'reload' mode
stop - shutdown server+portal
reboot - shutdown server+portal, then start again
reset - restart server in 'shutdown' mode
istart - start server in the foreground (until reload)
sstop - stop only server
kill - send kill signal to portal+server (force)
skill - send kill signal only to server
status - show server and portal run state
info - show server and portal port info
menu - show a menu of options
start - launch server+portal if not running
reload - restart server in 'reload' mode
stop - shutdown server+portal
reboot - shutdown server+portal, then start again
reset - restart server in 'shutdown' mode
istart - start server in foreground (until reload)
ipstart - start portal in foreground
sstop - stop only server
kill - send kill signal to portal+server (force)
skill - send kill signal only to server
status - show server and portal run state
info - show server and portal port info
menu - show a menu of options
Others, like migrate, test and shell is passed on to Django."""
# ------------------------------------------------------------
@ -974,6 +974,47 @@ def start_server_interactive():
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):
"""
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:
when_stopped (callable): This will be called with no arguments when Server has stopped (or
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):
@ -1972,7 +2014,7 @@ def main():
# launch menu for operation
init_game_directory(CURRENT_DIR, check_db=True)
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'):
# operate the server directly
if not SERVER_LOGFILE:
@ -1985,6 +2027,8 @@ def main():
start_evennia(args.profiler, args.profiler)
elif option == "istart":
start_server_interactive()
elif option == "ipstart":
start_portal_interactive()
elif option == 'reload':
reload_evennia(args.profiler)
elif option == 'reboot':

View file

@ -336,9 +336,9 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
elif operation == amp.PSHUTD: # portal + server shutdown #16
if server_connected:
self.factory.server_connection.wait_for_disconnect(
self.factory.portal.shutdown, restart=False)
self.factory.portal.shutdown )
else:
self.factory.portal.shutdown(restart=False)
self.factory.portal.shutdown()
else:
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
self.factory.server_connection.wait_for_disconnect(
self.factory.portal.shutdown, restart=False)
self.factory.portal.shutdown)
self.stop_server(mode='shutdown')
elif operation == amp.PSYNC: # portal sync

View file

@ -12,6 +12,7 @@ from builtins import object
import sys
import os
from os.path import dirname, abspath
from twisted.application import internet, service
from twisted.internet import protocol, reactor
from twisted.python.log import ILogObserver
@ -113,41 +114,46 @@ class Portal(object):
self.server_restart_mode = "shutdown"
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,
# 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):
"Return the Portal info, for display."
return INFO_DICT
def set_restart_mode(self, mode=None):
"""
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):
def shutdown(self, _reactor_stopping=False, _stop_server=False):
"""
Shuts down the server from inside it.
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
is already in the process of shutting down; in this case
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
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
# to do the shutdown procedure again.
return
self.sessions.disconnect_all()
self.set_restart_mode(restart)
if _stop_server:
self.amp_protocol.stop_server(mode='shutdown')
if not _reactor_stopping:
# shutting down the reactor will trigger another signal. We set
@ -179,9 +187,11 @@ class Portal(object):
application = service.Application('Portal')
# custom logging
logfile = logger.WeeklyLogFile(os.path.basename(settings.PORTAL_LOG_FILE),
os.path.dirname(settings.PORTAL_LOG_FILE))
application.setComponent(ILogObserver, logger.PortalLogObserver(logfile).emit)
if "--nodaemon" not in sys.argv:
logfile = logger.WeeklyLogFile(os.path.basename(settings.PORTAL_LOG_FILE),
os.path.dirname(settings.PORTAL_LOG_FILE))
application.setComponent(ILogObserver, logger.PortalLogObserver(logfile).emit)
# The main Portal server program. This sets up the database
# and is where we store all the other services.
@ -331,7 +341,8 @@ if WEBSERVER_ENABLED:
factory.noisy = False
factory.protocol = webclient.WebSocketClient
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))
PORTAL.services.addService(websocket_service)
websocket_started = True