diff --git a/evennia/commands/default/system.py b/evennia/commands/default/system.py index 594c615f5a..e3c248be93 100644 --- a/evennia/commands/default/system.py +++ b/evennia/commands/default/system.py @@ -118,8 +118,8 @@ class CmdShutdown(COMMAND_DEFAULT_CLASS): announcement += "%s\n" % self.args logger.log_info('Server shutdown by %s.' % self.caller.name) SESSIONS.announce_all(announcement) - SESSIONS.server.shutdown(mode='shutdown') SESSIONS.portal_shutdown() + SESSIONS.server.shutdown(mode='shutdown') class CmdPy(COMMAND_DEFAULT_CLASS): diff --git a/evennia/server/amp.py b/evennia/server/amp.py index 948ec3fc65..a6d45e26af 100644 --- a/evennia/server/amp.py +++ b/evennia/server/amp.py @@ -50,6 +50,7 @@ SSHUTD = chr(7) # server shutdown SSYNC = chr(8) # server session sync SCONN = chr(11) # server creating new connection (for irc/imc2 bots etc) PCONNSYNC = chr(12) # portal post-syncing a session +PDISCONNALL = chr(13) # portal session disconnect all AMP_MAXLEN = amp.MAX_VALUE_LENGTH # max allowed data length in AMP protocol (cannot be changed) BATCH_RATE = 250 # max commands/sec before switching to batch-sending @@ -495,6 +496,10 @@ class AMPProtocol(amp.AMP): session = server_sessionhandler[sessid] server_sessionhandler.portal_disconnect(session) + elif operation == PDISCONNALL: # portal_disconnect_all + # portal orders all sessions to close + server_sessionhandler.portal_disconnect_all() + elif operation == PSYNC: # portal_session_sync # force a resync of sessions when portal reconnects to # server (e.g. after a server reboot) the data kwarg diff --git a/evennia/server/portal/portal.py b/evennia/server/portal/portal.py index 97962f686c..17c26c2c25 100644 --- a/evennia/server/portal/portal.py +++ b/evennia/server/portal/portal.py @@ -168,8 +168,7 @@ class Portal(object): # we get here due to us calling reactor.stop below. No need # to do the shutdown procedure again. return - for session in self.sessions.itervalues(): - session.disconnect() + self.sessions.disconnect_all() self.set_restart_mode(restart) if os.name == 'nt' and os.path.exists(PORTAL_PIDFILE): # for Windows we need to remove pid files manually diff --git a/evennia/server/portal/portalsessionhandler.py b/evennia/server/portal/portalsessionhandler.py index a5f85d4e4e..bdd353d8a8 100644 --- a/evennia/server/portal/portalsessionhandler.py +++ b/evennia/server/portal/portalsessionhandler.py @@ -8,7 +8,8 @@ from time import time from collections import deque from twisted.internet import reactor from django.conf import settings -from evennia.server.sessionhandler import SessionHandler, PCONN, PDISCONN, PCONNSYNC +from evennia.server.sessionhandler import SessionHandler, PCONN, PDISCONN, \ + PCONNSYNC, PDISCONNALL from evennia.utils.logger import log_trace # module import @@ -23,6 +24,10 @@ _ERROR_COMMAND_OVERFLOW = settings.COMMAND_RATE_WARNING _CONNECTION_QUEUE = deque() +class DummySession(object): + sessid = 0 +DUMMYSESSION = DummySession() + #------------------------------------------------------------ # Portal-SessionHandler class #------------------------------------------------------------ @@ -152,6 +157,10 @@ class PortalSessionHandler(SessionHandler): Args: session (PortalSession): Session to disconnect. + delete (bool, optional): Delete the session from + the handler. Only time to not do this is when + this is called from a loop, such as from + self.disconnect_all(). """ global _CONNECTION_QUEUE @@ -161,7 +170,7 @@ class PortalSessionHandler(SessionHandler): _CONNECTION_QUEUE.remove(session) return - if session.sessid in self: + if session.sessid in self and not hasattr(self, "_disconnect_all"): # if this was called directly from the protocol, the # connection is already dead and we just need to cleanup del self[session.sessid] @@ -170,6 +179,23 @@ class PortalSessionHandler(SessionHandler): self.portal.amp_protocol.send_AdminPortal2Server(session, operation=PDISCONN) + def disconnect_all(self): + """ + Disconnect all sessions, informing the Server. + """ + def _callback(result, sessionhandler): + # we set a watchdog to stop self.disconnect from deleting + # sessions while we are looping over them. + sessionhandler._disconnect_all = True + for session in sessionhandler.values(): + session.disconnect(session) + del sessionhandler._disconnect_all + + # inform Server; wait until finished sending before we continue + # removing all the sessions. + self.portal.amp_protocol.send_AdminPortal2Server(DUMMYSESSION, + operation=PDISCONNALL).addCallback(_callback, self) + def server_connect(self, protocol_path="", config=dict()): """ Called by server to force the initialization of a new protocol diff --git a/evennia/server/server.py b/evennia/server/server.py index 8285aaed6d..e4525f5651 100644 --- a/evennia/server/server.py +++ b/evennia/server/server.py @@ -506,7 +506,7 @@ if WEBSERVER_ENABLED: # Start a django-compatible webserver. from twisted.python import threadpool - from evennia.server.webserver import DjangoWebRoot, WSGIWebServer, NonLoggingSite + from evennia.server.webserver import DjangoWebRoot, WSGIWebServer, Website # start a thread pool and define the root url (/) as a wsgi resource # recognized by Django @@ -522,7 +522,7 @@ if WEBSERVER_ENABLED: # custom overloads web_root = WEB_PLUGINS_MODULE.at_webserver_root_creation(web_root) - web_site = NonLoggingSite(web_root, logPath=settings.HTTP_LOG_FILE) + web_site = Website(web_root, logPath=settings.HTTP_LOG_FILE) for proxyport, serverport in WEBSERVER_PORTS: # create the webserver (we only need the port for this) diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index 0651d4f9c5..0f17b6d359 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -54,6 +54,7 @@ SSHUTD = chr(7) # server shutdown SSYNC = chr(8) # server session sync SCONN = chr(11) # server portal connection (for bots) PCONNSYNC = chr(12) # portal post-syncing session +PDISCONNALL = chr(13) # portal session discnnect all # i18n from django.utils.translation import ugettext as _ @@ -342,6 +343,19 @@ class ServerSessionHandler(SessionHandler): # Portal already knows. self.disconnect(session, reason="", sync_portal=False) + def portal_disconnect_all(self): + """ + Called from Portal when Portal is closing down. All + Sessions should die. The Portal should not be informed. + + """ + # set a watchdog to avoid self.disconnect from deleting + # the session while we are looping over them + self._disconnect_all = True + for session in self.values: + session.disconnect() + del self._disconnect_all + # server-side access methods def start_bot_session(self, protocol_path, configdict): @@ -459,7 +473,8 @@ class ServerSessionHandler(SessionHandler): session.at_disconnect() sessid = session.sessid - del self[sessid] + if sessid in self and not hasattr(self, "_disconnect_all"): + del self[sessid] if sync_portal: # inform portal that session should be closed. self.server.amp_protocol.send_AdminServer2Portal(session, diff --git a/evennia/server/webserver.py b/evennia/server/webserver.py index b60f7565a3..249b492a4f 100644 --- a/evennia/server/webserver.py +++ b/evennia/server/webserver.py @@ -140,7 +140,7 @@ class DjangoWebRoot(resource.Resource): # Site with deactivateable logging # -class NonLoggingSite(server.Site): +class Website(server.Site): """ This class will only log http requests if settings.DEBUG is True. """