diff --git a/evennia/scripts/manager.py b/evennia/scripts/manager.py index e1af9acaca..bd0f612099 100644 --- a/evennia/scripts/manager.py +++ b/evennia/scripts/manager.py @@ -133,7 +133,7 @@ class ScriptDBManager(TypedObjectManager): return nr_deleted def validate(self, scripts=None, obj=None, key=None, dbref=None, - init_mode=False): + init_mode=None): """ This will step through the script database and make sure all objects run scripts that are still valid in the context @@ -153,7 +153,7 @@ class ScriptDBManager(TypedObjectManager): particular id. init_mode (str, optional): This is used during server upstart and can have three values: - - `False` (no init mode). Called during run. + - `None` (no init mode). Called during run. - `"reset"` - server reboot. Kill non-persistent scripts - `"reload"` - server reload. Keep non-persistent scripts. Returns: diff --git a/evennia/scripts/monitorhandler.py b/evennia/scripts/monitorhandler.py index da5e2eeaa8..1f9df1a4cb 100644 --- a/evennia/scripts/monitorhandler.py +++ b/evennia/scripts/monitorhandler.py @@ -71,11 +71,16 @@ class MonitorHandler(object): restored_monitors = dbunserialize(restored_monitors) for (obj, fieldname, idstring, path, persistent, kwargs) in restored_monitors: try: - if not persistent and not server_reload: + if not server_reload and not persistent: # this monitor will not be restarted continue + if "session" in kwargs and not kwargs["session"]: + # the session was removed because it no longer + # exists. Don't restart the monitor. + continue modname, varname = path.rsplit(".", 1) callback = variable_from_module(modname, varname) + if obj and hasattr(obj, fieldname): self.monitors[obj][fieldname][idstring] = (callback, persistent, kwargs) except Exception: @@ -116,6 +121,13 @@ class MonitorHandler(object): persistent (bool, optional): If False, the monitor will survive a server reload but not a cold restart. This is default. + Kwargs: + session (Session): If this keyword is given, the monitorhandler will + correctly analyze it and remove the monitor if after a reload/reboot + the session is no longer valid. + any (any): Any other kwargs are passed on to the callback. Remember that + all kwargs must be possible to pickle! + """ if not fieldname.startswith("db_") or not hasattr(obj, fieldname): # an Attribute - we track its db_value field diff --git a/evennia/server/inputfuncs.py b/evennia/server/inputfuncs.py index 3aa91341aa..721e501102 100644 --- a/evennia/server/inputfuncs.py +++ b/evennia/server/inputfuncs.py @@ -355,7 +355,10 @@ def _on_monitor_change(**kwargs): obj = kwargs["obj"] name = kwargs["name"] session = kwargs["session"] - session.msg(monitor={"name": name, "value": _GA(obj, fieldname)}) + # the session may be None if the char quits and someone + # else then edits the object + if session: + session.msg(monitor={"name": name, "value": _GA(obj, fieldname)}) def monitor(session, *args, **kwargs): diff --git a/evennia/server/server.py b/evennia/server/server.py index 12bad1862c..866ad2a7f8 100644 --- a/evennia/server/server.py +++ b/evennia/server/server.py @@ -278,17 +278,10 @@ class Evennia(object): [o.at_init() for o in ObjectDB.get_all_cached_instances()] [p.at_init() for p in PlayerDB.get_all_cached_instances()] - with open(SERVER_RESTART, 'r') as f: - mode = f.read() - if mode in ('True', 'reload'): - from evennia.scripts.monitorhandler import MONITOR_HANDLER - MONITOR_HANDLER.restore() - - from evennia.scripts.tickerhandler import TICKER_HANDLER - TICKER_HANDLER.restore(mode in ('True', 'reload')) + mode = self.getset_restart_mode() # call correct server hook based on start file value - if mode in ('True', 'reload'): + if mode == 'reload': # True was the old reload flag, kept for compatibilty self.at_server_reload_start() elif mode == 'reset': @@ -301,15 +294,19 @@ class Evennia(object): # always call this regardless of start type self.at_server_start() - def set_restart_mode(self, mode=None): + def getset_restart_mode(self, mode=None): """ This manages the flag file that tells the runner if the server is - reloading, resetting or shutting down. Valid modes are - 'reload', 'reset', 'shutdown' and None. - If mode is None, no change will be done to the flag file. + reloading, resetting or shutting down. + + Args: + mode (string or None, optional): Valid values are + 'reload', 'reset', 'shutdown' and `None`. If mode is `None`, + no change will be done to the flag file. + Returns: + mode (str): The currently active restart mode, either just + set or previously set. - Either way, the active restart setting (Restart=True/False) is - returned so the server knows which more it's in. """ if mode is None: with open(SERVER_RESTART, 'r') as f: @@ -343,7 +340,7 @@ class Evennia(object): # once; we don't need to run the shutdown procedure again. defer.returnValue(None) - mode = self.set_restart_mode(mode) + mode = self.getset_restart_mode(mode) from evennia.objects.models import ObjectDB #from evennia.players.models import PlayerDB @@ -423,6 +420,26 @@ class Evennia(object): if SERVER_STARTSTOP_MODULE: SERVER_STARTSTOP_MODULE.at_server_reload_start() + def at_post_portal_sync(self): + """ + This is called just after the portal has finished syncing back data to the server + after reconnecting. + """ + # one of reload, reset or shutdown + mode = self.getset_restart_mode() + + from evennia.scripts.monitorhandler import MONITOR_HANDLER + MONITOR_HANDLER.restore(mode == 'reload') + + from evennia.scripts.tickerhandler import TICKER_HANDLER + TICKER_HANDLER.restore(mode == 'reload') + + # after sync is complete we force-validate all scripts + # (this also starts any that didn't yet start) + ScriptDB.objects.validate(init_mode=mode) + + # delete the temporary setting + ServerConfig.objects.conf("server_restart_mode", delete=True) def at_server_reload_stop(self): """ diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index 11f032c0a1..9c907c84d6 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -327,14 +327,12 @@ class ServerSessionHandler(SessionHandler): self[sessid] = sess sess.at_sync() - # after sync is complete we force-validate all scripts - # (this also starts them) - init_mode = _ServerConfig.objects.conf("server_restart_mode", default=None) - _ScriptDB.objects.validate(init_mode=init_mode) - _ServerConfig.objects.conf("server_restart_mode", delete=True) + # tell the server hook we synced + self.server.at_post_portal_sync() # announce the reconnection self.announce_all(_(" ... Server restarted.")) + def portal_disconnect(self, session): """ Called from Portal when Portal session closed from the portal diff --git a/evennia/utils/dbserialize.py b/evennia/utils/dbserialize.py index 7e1d6ea4f1..886bf6528b 100644 --- a/evennia/utils/dbserialize.py +++ b/evennia/utils/dbserialize.py @@ -375,19 +375,25 @@ def pack_session(item): can't be safely serialized). Args: - item (packed_session): The fact that item is a packed Session - should be checked before this call. + item (Session)): This item must have all properties of a session + before entering this call. Returns: - unpacked (any): Either the original input or converts the - internal store back to a Session. If the Session no longer - exists, None is returned. + packed (tuple or None): A session-packed tuple on the form + `(__packed_session__, sessid, conn_time)`. If this sessid + does not match a session in the Session handler, None is returned. """ _init_globals() - return item.conn_time and item.sessid and ('__packed_session__', - _GA(item, "sessid"), - _GA(item, "conn_time")) + session = _SESSION_HANDLER.get(item.sessid) + if session and session.conn_time == item.conn_time: + # we require connection times to be identical for the Session + # to be accepted as actually being a session (sessids gets + # reused all the time). + return item.conn_time and item.sessid and ('__packed_session__', + _GA(item, "sessid"), + _GA(item, "conn_time")) + return None def unpack_session(item): """ @@ -454,7 +460,7 @@ def to_pickle(data): return item.__class__([process_item(val) for val in item]) except (AttributeError, TypeError): return [process_item(val) for val in item] - elif hasattr(item, "sessid") and hasattr(item, "conn_time") and item.sessid in _SESSION_HANDLER: + elif hasattr(item, "sessid") and hasattr(item, "conn_time"): return pack_session(item) return pack_dbobj(item) return process_item(data)