Fix error in MonitorHandler recovering a saved Session across a reload. This probably affected the TickerHandler as well. Add a new hook to the server object that gets called once the portal has synced, and run the monitorhandler/tickerhandler restores there. Also some changes to the serialization of Sessions. Resolves #1164.
This commit is contained in:
parent
b46bc9b2aa
commit
052e1845a2
6 changed files with 70 additions and 34 deletions
|
|
@ -133,7 +133,7 @@ class ScriptDBManager(TypedObjectManager):
|
||||||
return nr_deleted
|
return nr_deleted
|
||||||
|
|
||||||
def validate(self, scripts=None, obj=None, key=None, dbref=None,
|
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
|
This will step through the script database and make sure
|
||||||
all objects run scripts that are still valid in the context
|
all objects run scripts that are still valid in the context
|
||||||
|
|
@ -153,7 +153,7 @@ class ScriptDBManager(TypedObjectManager):
|
||||||
particular id.
|
particular id.
|
||||||
init_mode (str, optional): This is used during server
|
init_mode (str, optional): This is used during server
|
||||||
upstart and can have three values:
|
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
|
- `"reset"` - server reboot. Kill non-persistent scripts
|
||||||
- `"reload"` - server reload. Keep non-persistent scripts.
|
- `"reload"` - server reload. Keep non-persistent scripts.
|
||||||
Returns:
|
Returns:
|
||||||
|
|
|
||||||
|
|
@ -71,11 +71,16 @@ class MonitorHandler(object):
|
||||||
restored_monitors = dbunserialize(restored_monitors)
|
restored_monitors = dbunserialize(restored_monitors)
|
||||||
for (obj, fieldname, idstring, path, persistent, kwargs) in restored_monitors:
|
for (obj, fieldname, idstring, path, persistent, kwargs) in restored_monitors:
|
||||||
try:
|
try:
|
||||||
if not persistent and not server_reload:
|
if not server_reload and not persistent:
|
||||||
# this monitor will not be restarted
|
# this monitor will not be restarted
|
||||||
continue
|
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)
|
modname, varname = path.rsplit(".", 1)
|
||||||
callback = variable_from_module(modname, varname)
|
callback = variable_from_module(modname, varname)
|
||||||
|
|
||||||
if obj and hasattr(obj, fieldname):
|
if obj and hasattr(obj, fieldname):
|
||||||
self.monitors[obj][fieldname][idstring] = (callback, persistent, kwargs)
|
self.monitors[obj][fieldname][idstring] = (callback, persistent, kwargs)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
@ -116,6 +121,13 @@ class MonitorHandler(object):
|
||||||
persistent (bool, optional): If False, the monitor will survive
|
persistent (bool, optional): If False, the monitor will survive
|
||||||
a server reload but not a cold restart. This is default.
|
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):
|
if not fieldname.startswith("db_") or not hasattr(obj, fieldname):
|
||||||
# an Attribute - we track its db_value field
|
# an Attribute - we track its db_value field
|
||||||
|
|
|
||||||
|
|
@ -355,6 +355,9 @@ def _on_monitor_change(**kwargs):
|
||||||
obj = kwargs["obj"]
|
obj = kwargs["obj"]
|
||||||
name = kwargs["name"]
|
name = kwargs["name"]
|
||||||
session = kwargs["session"]
|
session = kwargs["session"]
|
||||||
|
# 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)})
|
session.msg(monitor={"name": name, "value": _GA(obj, fieldname)})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -278,17 +278,10 @@ class Evennia(object):
|
||||||
[o.at_init() for o in ObjectDB.get_all_cached_instances()]
|
[o.at_init() for o in ObjectDB.get_all_cached_instances()]
|
||||||
[p.at_init() for p in PlayerDB.get_all_cached_instances()]
|
[p.at_init() for p in PlayerDB.get_all_cached_instances()]
|
||||||
|
|
||||||
with open(SERVER_RESTART, 'r') as f:
|
mode = self.getset_restart_mode()
|
||||||
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'))
|
|
||||||
|
|
||||||
# call correct server hook based on start file value
|
# 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
|
# True was the old reload flag, kept for compatibilty
|
||||||
self.at_server_reload_start()
|
self.at_server_reload_start()
|
||||||
elif mode == 'reset':
|
elif mode == 'reset':
|
||||||
|
|
@ -301,15 +294,19 @@ class Evennia(object):
|
||||||
# always call this regardless of start type
|
# always call this regardless of start type
|
||||||
self.at_server_start()
|
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
|
This manages the flag file that tells the runner if the server is
|
||||||
reloading, resetting or shutting down. Valid modes are
|
reloading, resetting or shutting down.
|
||||||
'reload', 'reset', 'shutdown' and None.
|
|
||||||
If mode is None, no change will be done to the flag file.
|
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:
|
if mode is None:
|
||||||
with open(SERVER_RESTART, 'r') as f:
|
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.
|
# once; we don't need to run the shutdown procedure again.
|
||||||
defer.returnValue(None)
|
defer.returnValue(None)
|
||||||
|
|
||||||
mode = self.set_restart_mode(mode)
|
mode = self.getset_restart_mode(mode)
|
||||||
|
|
||||||
from evennia.objects.models import ObjectDB
|
from evennia.objects.models import ObjectDB
|
||||||
#from evennia.players.models import PlayerDB
|
#from evennia.players.models import PlayerDB
|
||||||
|
|
@ -423,6 +420,26 @@ class Evennia(object):
|
||||||
if SERVER_STARTSTOP_MODULE:
|
if SERVER_STARTSTOP_MODULE:
|
||||||
SERVER_STARTSTOP_MODULE.at_server_reload_start()
|
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):
|
def at_server_reload_stop(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -327,14 +327,12 @@ class ServerSessionHandler(SessionHandler):
|
||||||
self[sessid] = sess
|
self[sessid] = sess
|
||||||
sess.at_sync()
|
sess.at_sync()
|
||||||
|
|
||||||
# after sync is complete we force-validate all scripts
|
# tell the server hook we synced
|
||||||
# (this also starts them)
|
self.server.at_post_portal_sync()
|
||||||
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)
|
|
||||||
# announce the reconnection
|
# announce the reconnection
|
||||||
self.announce_all(_(" ... Server restarted."))
|
self.announce_all(_(" ... Server restarted."))
|
||||||
|
|
||||||
|
|
||||||
def portal_disconnect(self, session):
|
def portal_disconnect(self, session):
|
||||||
"""
|
"""
|
||||||
Called from Portal when Portal session closed from the portal
|
Called from Portal when Portal session closed from the portal
|
||||||
|
|
|
||||||
|
|
@ -375,19 +375,25 @@ def pack_session(item):
|
||||||
can't be safely serialized).
|
can't be safely serialized).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
item (packed_session): The fact that item is a packed Session
|
item (Session)): This item must have all properties of a session
|
||||||
should be checked before this call.
|
before entering this call.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
unpacked (any): Either the original input or converts the
|
packed (tuple or None): A session-packed tuple on the form
|
||||||
internal store back to a Session. If the Session no longer
|
`(__packed_session__, sessid, conn_time)`. If this sessid
|
||||||
exists, None is returned.
|
does not match a session in the Session handler, None is returned.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
_init_globals()
|
_init_globals()
|
||||||
|
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__',
|
return item.conn_time and item.sessid and ('__packed_session__',
|
||||||
_GA(item, "sessid"),
|
_GA(item, "sessid"),
|
||||||
_GA(item, "conn_time"))
|
_GA(item, "conn_time"))
|
||||||
|
return None
|
||||||
|
|
||||||
def unpack_session(item):
|
def unpack_session(item):
|
||||||
"""
|
"""
|
||||||
|
|
@ -454,7 +460,7 @@ def to_pickle(data):
|
||||||
return item.__class__([process_item(val) for val in item])
|
return item.__class__([process_item(val) for val in item])
|
||||||
except (AttributeError, TypeError):
|
except (AttributeError, TypeError):
|
||||||
return [process_item(val) for val in item]
|
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_session(item)
|
||||||
return pack_dbobj(item)
|
return pack_dbobj(item)
|
||||||
return process_item(data)
|
return process_item(data)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue