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,19 +265,26 @@ class ObjectDB(TypedObject):
self.save(update_fields=["db_location"])
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,
no matter how. It checks for a variable _safe_contents_update to
know if the save was triggered via the proper handler or not.
Since we cannot know at this point was old_location was, we
trigger a full-on contents_cache update here.
This is called automatically after the location field was
saved, no matter how. It checks for a variable
_safe_contents_update to know if the save was triggered via
the location handler (which updates the contents cache) or
not.
"""
if not hasattr(self, "_safe_contents_update"):
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()]
# 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!")
[o.contents_cache.init() for o in self.__dbclass__.get_all_cached_instances()]
class Meta:
"Define Django meta options"

View file

@ -53,18 +53,22 @@ if not _OOB_ERROR:
class OOBFieldMonitor(object):
"""
This object should be stored on the
tracked object as "_oob_at_<fieldname>_update".
the update() method will be called by the
tracked object as "_oob_at_<fieldname>_postsave".
the update() method w ill be called by the
save mechanism, which in turn will call the
user-customizable func()
"""
def __init__(self):
def __init__(self, obj):
"""
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)
def __call__(self, obj, fieldname):
def __call__(self, fieldname):
"""
Called by the save() mechanism when the given
field has updated.
@ -74,7 +78,7 @@ class OOBFieldMonitor(object):
# a potential list of oob commands to call when this
# field changes.
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):
"""
@ -156,7 +160,7 @@ class OOBHandler(TickerHandler):
fieldmonitorname = self._get_fieldmonitor_name(fieldname)
if not hasattr(obj, fieldmonitorname):
# assign a new fieldmonitor to the object
_SA(obj, fieldmonitorname, OOBFieldMonitor())
_SA(obj, fieldmonitorname, OOBFieldMonitor(obj))
# register the session with the monitor
_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)
"""
import time
from time import time
from datetime import datetime
from django.conf import settings
from evennia.comms.models import ChannelDB
@ -48,6 +48,7 @@ class ServerSession(Session):
self.player = None
self.cmdset_storage_string = ""
self.cmdset = CmdSetHandler(self, True)
self.cmd_per_second = 0.0
def __cmdset_storage_get(self):
return [path.strip() for path in self.cmdset_storage_string.split(',')]
@ -98,7 +99,7 @@ class ServerSession(Session):
self.uid = self.player.id
self.uname = self.player.username
self.logged_in = True
self.conn_time = time.time()
self.conn_time = time()
self.puid = None
self.puppet = None
self.cmdset_storage = settings.CMDSET_SESSION
@ -184,12 +185,11 @@ class ServerSession(Session):
and command counters.
"""
# Store the timestamp of the user's last command.
self.cmd_last = time.time()
if not idle:
# Increment the user's command counter.
self.cmd_total += 1
# 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):
"""
@ -200,6 +200,10 @@ class ServerSession(Session):
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
#also valid
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 evennia.commands.cmdhandler import CMD_LOGINSTART
from evennia.utils.utils import variable_from_module, is_iter, \
to_str, to_unicode, strip_control_sequences, make_iter
from evennia.utils import logger
try:
import cPickle as pickle
@ -46,11 +47,15 @@ PCONNSYNC = chr(10) # portal post-syncing session
# i18n
from django.utils.translation import ugettext as _
SERVERNAME = settings.SERVERNAME
_SERVERNAME = settings.SERVERNAME
_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():
"Helper method for delayed import of all needed entities"
global _ServerSession, _PlayerDB, _ServerConfig, _ScriptDB
@ -130,7 +135,9 @@ class ServerSessionHandler(SessionHandler):
"""
self.sessions = {}
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):
"""
@ -359,11 +366,11 @@ class ServerSessionHandler(SessionHandler):
Check all currently connected sessions (logged in and not)
and see if any are dead or idle
"""
tcurr = time.time()
tcurr = time()
reason = _("Idle timeout exceeded, disconnecting.")
for session in (session for session in self.sessions.values()
if session.logged_in and IDLE_TIMEOUT > 0
and (tcurr - session.cmd_last) > IDLE_TIMEOUT):
if session.logged_in and _IDLE_TIMEOUT > 0
and (tcurr - session.cmd_last) > _IDLE_TIMEOUT):
self.disconnect(session, reason=reason)
def player_count(self, count=True):
@ -493,6 +500,17 @@ class ServerSessionHandler(SessionHandler):
"""
session = self.sessions.get(sessid, None)
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)
if "oob" in 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)
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:
# we keep a store of objects modified in subprocesses so
@ -348,24 +358,26 @@ class SharedMemoryModel(Model):
callFromThread(_save_callback, self, *args, **kwargs)
# update field-update hooks and eventual OOB watchers
new = False
if "update_fields" in kwargs and kwargs["update_fields"]:
# get field objects from their names
update_fields = (self._meta.get_field_by_name(field)[0]
for field in kwargs.get("update_fields"))
else:
# meta.fields are already field objects; get them all
new =True
update_fields = self._meta.fields
for field in update_fields:
fieldname = field.name
# 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)):
_GA(self, hookname)()
_GA(self, hookname)(new)
# if a trackerhandler is set on this object, update it with the
# fieldname and the new value
fieldtracker = "_oob_at_%s_postsave" % fieldname
if hasattr(self, fieldtracker):
_GA(self, fieldtracker)(self, fieldname)
_GA(self, fieldtracker)(fieldname)
class WeakSharedMemoryModelBase(SharedMemoryModelBase):