Fixes to content_cache handler. Started a simple test of a cmd-limiter.

This commit is contained in:
Griatch 2015-02-28 13:02:51 +01:00
parent b94bb17576
commit e201cda2c3
5 changed files with 75 additions and 30 deletions

View file

@ -265,17 +265,24 @@ class ObjectDB(TypedObject):
self.save(update_fields=["db_location"]) self.save(update_fields=["db_location"])
location = property(__location_get, __location_set, __location_del) location = property(__location_get, __location_set, __location_del)
def _db_location_post_save(self): def at_db_location_postsave(self, new):
""" """
This is called automatically after the location field was saved, This is called automatically after the location field was
no matter how. It checks for a variable _safe_contents_update to saved, no matter how. It checks for a variable
know if the save was triggered via the proper handler or not. _safe_contents_update to know if the save was triggered via
the location handler (which updates the contents cache) or
Since we cannot know at this point was old_location was, we not.
trigger a full-on contents_cache update here.
""" """
if not hasattr(self, "_safe_contents_update"): if not hasattr(self, "_safe_contents_update"):
# changed/set outside of the location handler
if new:
# if new, there is no previous location to worry about
if self.db_location:
self.db_location.contents_cache.add(self)
else:
# Since we cannot know at this point was old_location was, we
# trigger a full-on contents_cache update here.
logger.log_warn("db_location direct save triggered contents_cache.init() for all objects!") logger.log_warn("db_location direct save triggered contents_cache.init() for all objects!")
[o.contents_cache.init() for o in self.__dbclass__.get_all_cached_instances()] [o.contents_cache.init() for o in self.__dbclass__.get_all_cached_instances()]

View file

@ -53,18 +53,22 @@ if not _OOB_ERROR:
class OOBFieldMonitor(object): class OOBFieldMonitor(object):
""" """
This object should be stored on the This object should be stored on the
tracked object as "_oob_at_<fieldname>_update". tracked object as "_oob_at_<fieldname>_postsave".
the update() method w ill be called by the the update() method w ill be called by the
save mechanism, which in turn will call the save mechanism, which in turn will call the
user-customizable func() user-customizable func()
""" """
def __init__(self): def __init__(self, obj):
""" """
This initializes the monitor with the object it sits on. This initializes the monitor with the object it sits on.
Args:
obj (Object): object handler is defined on.
""" """
self.obj = obj
self.subscribers = defaultdict(list) self.subscribers = defaultdict(list)
def __call__(self, obj, fieldname): def __call__(self, fieldname):
""" """
Called by the save() mechanism when the given Called by the save() mechanism when the given
field has updated. field has updated.
@ -74,7 +78,7 @@ class OOBFieldMonitor(object):
# a potential list of oob commands to call when this # a potential list of oob commands to call when this
# field changes. # field changes.
for (oobfuncname, args, kwargs) in oobtuples: for (oobfuncname, args, kwargs) in oobtuples:
OOB_HANDLER.execute_cmd(sessid, oobfuncname, fieldname, obj, *args, **kwargs) OOB_HANDLER.execute_cmd(sessid, oobfuncname, fieldname, self.obj, *args, **kwargs)
def add(self, sessid, oobfuncname, *args, **kwargs): def add(self, sessid, oobfuncname, *args, **kwargs):
""" """
@ -156,7 +160,7 @@ class OOBHandler(TickerHandler):
fieldmonitorname = self._get_fieldmonitor_name(fieldname) fieldmonitorname = self._get_fieldmonitor_name(fieldname)
if not hasattr(obj, fieldmonitorname): if not hasattr(obj, fieldmonitorname):
# assign a new fieldmonitor to the object # assign a new fieldmonitor to the object
_SA(obj, fieldmonitorname, OOBFieldMonitor()) _SA(obj, fieldmonitorname, OOBFieldMonitor(obj))
# register the session with the monitor # register the session with the monitor
_GA(obj, fieldmonitorname).add(sessid, oobfuncname, *args, **kwargs) _GA(obj, fieldmonitorname).add(sessid, oobfuncname, *args, **kwargs)

View file

@ -7,7 +7,7 @@ It is stored on the Server side (as opposed to protocol-specific sessions which
are stored on the Portal side) are stored on the Portal side)
""" """
import time from time import time
from datetime import datetime from datetime import datetime
from django.conf import settings from django.conf import settings
from evennia.comms.models import ChannelDB from evennia.comms.models import ChannelDB
@ -48,6 +48,7 @@ class ServerSession(Session):
self.player = None self.player = None
self.cmdset_storage_string = "" self.cmdset_storage_string = ""
self.cmdset = CmdSetHandler(self, True) self.cmdset = CmdSetHandler(self, True)
self.cmd_per_second = 0.0
def __cmdset_storage_get(self): def __cmdset_storage_get(self):
return [path.strip() for path in self.cmdset_storage_string.split(',')] return [path.strip() for path in self.cmdset_storage_string.split(',')]
@ -98,7 +99,7 @@ class ServerSession(Session):
self.uid = self.player.id self.uid = self.player.id
self.uname = self.player.username self.uname = self.player.username
self.logged_in = True self.logged_in = True
self.conn_time = time.time() self.conn_time = time()
self.puid = None self.puid = None
self.puppet = None self.puppet = None
self.cmdset_storage = settings.CMDSET_SESSION self.cmdset_storage = settings.CMDSET_SESSION
@ -184,12 +185,11 @@ class ServerSession(Session):
and command counters. and command counters.
""" """
# Store the timestamp of the user's last command. # Store the timestamp of the user's last command.
self.cmd_last = time.time()
if not idle: if not idle:
# Increment the user's command counter. # Increment the user's command counter.
self.cmd_total += 1 self.cmd_total += 1
# Player-visible idle time, not used in idle timeout calcs. # Player-visible idle time, not used in idle timeout calcs.
self.cmd_last_visible = time.time() self.cmd_last_visible = time()
def data_in(self, text=None, **kwargs): def data_in(self, text=None, **kwargs):
""" """
@ -200,6 +200,10 @@ class ServerSession(Session):
oobhandler at this point. oobhandler at this point.
""" """
now = time()
self.cmd_per_second = 1.0 / (now - self.cmd_last)
self.cmd_last = now
#explicitly check for None since text can be an empty string, which is #explicitly check for None since text can be an empty string, which is
#also valid #also valid
if text is not None: if text is not None:

View file

@ -12,11 +12,12 @@ There are two similar but separate stores of sessions:
""" """
import time from time import time
from django.conf import settings from django.conf import settings
from evennia.commands.cmdhandler import CMD_LOGINSTART from evennia.commands.cmdhandler import CMD_LOGINSTART
from evennia.utils.utils import variable_from_module, is_iter, \ from evennia.utils.utils import variable_from_module, is_iter, \
to_str, to_unicode, strip_control_sequences, make_iter to_str, to_unicode, strip_control_sequences, make_iter
from evennia.utils import logger
try: try:
import cPickle as pickle import cPickle as pickle
@ -46,11 +47,15 @@ PCONNSYNC = chr(10) # portal post-syncing session
# i18n # i18n
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
SERVERNAME = settings.SERVERNAME _SERVERNAME = settings.SERVERNAME
_MULTISESSION_MODE = settings.MULTISESSION_MODE _MULTISESSION_MODE = settings.MULTISESSION_MODE
IDLE_TIMEOUT = settings.IDLE_TIMEOUT _IDLE_TIMEOUT = settings.IDLE_TIMEOUT
_MAX_SERVER_COMMANDS_PER_SECOND = 100.0
_MAX_SESSION_COMMANDS_PER_SECOND = 5.0
_ERROR_COMMAND_OVERFLOW = "You entered commands too fast. Wait a moment and try again."
def delayed_import(): def delayed_import():
"Helper method for delayed import of all needed entities" "Helper method for delayed import of all needed entities"
global _ServerSession, _PlayerDB, _ServerConfig, _ScriptDB global _ServerSession, _PlayerDB, _ServerConfig, _ScriptDB
@ -130,7 +135,9 @@ class ServerSessionHandler(SessionHandler):
""" """
self.sessions = {} self.sessions = {}
self.server = None self.server = None
self.server_data = {"servername": SERVERNAME} self.server_data = {"servername": _SERVERNAME}
self.cmd_last = time()
self.cmd_per_second = 0.0
def portal_connect(self, portalsession): def portal_connect(self, portalsession):
""" """
@ -359,11 +366,11 @@ class ServerSessionHandler(SessionHandler):
Check all currently connected sessions (logged in and not) Check all currently connected sessions (logged in and not)
and see if any are dead or idle and see if any are dead or idle
""" """
tcurr = time.time() tcurr = time()
reason = _("Idle timeout exceeded, disconnecting.") reason = _("Idle timeout exceeded, disconnecting.")
for session in (session for session in self.sessions.values() for session in (session for session in self.sessions.values()
if session.logged_in and IDLE_TIMEOUT > 0 if session.logged_in and _IDLE_TIMEOUT > 0
and (tcurr - session.cmd_last) > IDLE_TIMEOUT): and (tcurr - session.cmd_last) > _IDLE_TIMEOUT):
self.disconnect(session, reason=reason) self.disconnect(session, reason=reason)
def player_count(self, count=True): def player_count(self, count=True):
@ -493,6 +500,17 @@ class ServerSessionHandler(SessionHandler):
""" """
session = self.sessions.get(sessid, None) session = self.sessions.get(sessid, None)
if session: if session:
now = time()
self.cmd_per_second = 1.0 / (now - self.cmd_last)
self.cmd_last = now
if self.cmd_per_second > _MAX_SERVER_COMMANDS_PER_SECOND:
if session.cmd_per_second > _MAX_SESSION_COMMANDS_PER_SECOND:
session.data.out(text=_ERROR_COMMAND_OVERFLOW)
logger.log_infomsg("overflow kicked in for session %s: %s" % (session.sessid, text))
return
text = text and to_unicode(strip_control_sequences(text), encoding=session.encoding) text = text and to_unicode(strip_control_sequences(text), encoding=session.encoding)
if "oob" in kwargs: if "oob" in kwargs:
# incoming data is always on the form (cmdname, args, kwargs) # incoming data is always on the form (cmdname, args, kwargs)

View file

@ -329,7 +329,17 @@ class SharedMemoryModel(Model):
super(SharedMemoryModel, self).delete(*args, **kwargs) super(SharedMemoryModel, self).delete(*args, **kwargs)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"save method tracking process/thread issues" """
Central database save operation.
Arguments as per django documentation
Calls:
self.at_<fieldname>_postsave(new)
# this is a wrapper set by oobhandler:
self._oob_at_<fieldname>_postsave()
"""
if _IS_SUBPROCESS: if _IS_SUBPROCESS:
# we keep a store of objects modified in subprocesses so # we keep a store of objects modified in subprocesses so
@ -348,24 +358,26 @@ class SharedMemoryModel(Model):
callFromThread(_save_callback, self, *args, **kwargs) callFromThread(_save_callback, self, *args, **kwargs)
# update field-update hooks and eventual OOB watchers # update field-update hooks and eventual OOB watchers
new = False
if "update_fields" in kwargs and kwargs["update_fields"]: if "update_fields" in kwargs and kwargs["update_fields"]:
# get field objects from their names # get field objects from their names
update_fields = (self._meta.get_field_by_name(field)[0] update_fields = (self._meta.get_field_by_name(field)[0]
for field in kwargs.get("update_fields")) for field in kwargs.get("update_fields"))
else: else:
# meta.fields are already field objects; get them all # meta.fields are already field objects; get them all
new =True
update_fields = self._meta.fields update_fields = self._meta.fields
for field in update_fields: for field in update_fields:
fieldname = field.name fieldname = field.name
# if a hook is defined it must be named exactly on this form # if a hook is defined it must be named exactly on this form
hookname = "_at_%s_postsave" % fieldname hookname = "at_%s_postsave" % fieldname
if hasattr(self, hookname) and callable(_GA(self, hookname)): if hasattr(self, hookname) and callable(_GA(self, hookname)):
_GA(self, hookname)() _GA(self, hookname)(new)
# if a trackerhandler is set on this object, update it with the # if a trackerhandler is set on this object, update it with the
# fieldname and the new value # fieldname and the new value
fieldtracker = "_oob_at_%s_postsave" % fieldname fieldtracker = "_oob_at_%s_postsave" % fieldname
if hasattr(self, fieldtracker): if hasattr(self, fieldtracker):
_GA(self, fieldtracker)(self, fieldname) _GA(self, fieldtracker)(fieldname)
class WeakSharedMemoryModelBase(SharedMemoryModelBase): class WeakSharedMemoryModelBase(SharedMemoryModelBase):