Refactored amp.py to more cleanly splitting long AMP messages also during server sync (this could cause errors with a large number of connected players). Also fixed an issue with the lockstring get:all() being set by @create despite it not needing to (overloading changes in the typeclass).

This commit is contained in:
Griatch 2013-11-27 16:57:41 +01:00
parent 1ae17bcbe4
commit e9e2c78eef
9 changed files with 301 additions and 201 deletions

View file

@ -422,7 +422,7 @@ class CmdCreate(ObjManipCommand):
# create object (if not a valid typeclass, the default # create object (if not a valid typeclass, the default
# object typeclass will automatically be used) # object typeclass will automatically be used)
lockstring = "control:id(%s);examine:perm(Builders);delete:id(%s) or perm(Wizards);get:all()" % (caller.id, caller.id) lockstring = "control:id(%s);examine:perm(Builders);delete:id(%s) or perm(Wizards)" % (caller.id, caller.id)
obj = create.create_object(typeclass, name, caller, obj = create.create_object(typeclass, name, caller,
home=caller, aliases=aliases, home=caller, aliases=aliases,
locks=lockstring, report_to=caller) locks=lockstring, report_to=caller)

View file

@ -713,11 +713,12 @@ class CmdServerLoad(MuxCommand):
"%.2f" % (float(tup[2] / totcache[1]) * 100)]) "%.2f" % (float(tup[2] / totcache[1]) * 100)])
# get sizes of other caches # get sizes of other caches
attr_cache_info, field_cache_info, prop_cache_info = get_cache_sizes() attr_cache_info, prop_cache_info = get_cache_sizes()
string += "\n{w Entity idmapper cache usage:{n %5.2f MB (%i items)\n%s" % (totcache[1], totcache[0], memtable) string += "\n{w Entity idmapper cache usage:{n %5.2f MB (%i items)\n%s" % (totcache[1], totcache[0], memtable)
string += "\n{w On-entity Attribute cache usage:{n %5.2f MB (%i attrs)" % (attr_cache_info[1], attr_cache_info[0]) string += "\n{w On-entity Attribute cache usage:{n %5.2f MB (%i attrs)" % (attr_cache_info[1], attr_cache_info[0])
string += "\n{w On-entity Field cache usage:{n %5.2f MB (%i fields)" % (field_cache_info[1], field_cache_info[0])
string += "\n{w On-entity Property cache usage:{n %5.2f MB (%i props)" % (prop_cache_info[1], prop_cache_info[0]) string += "\n{w On-entity Property cache usage:{n %5.2f MB (%i props)" % (prop_cache_info[1], prop_cache_info[0])
base_mem = vmem - totcache[1] - attr_cache_info[1] - prop_cache_info[1]
string += "\n{w Base Server usage (virtmem-idmapper-attrcache-propcache):{n %5.2f MB" % base_mem
caller.msg(string) caller.msg(string)

View file

@ -173,7 +173,7 @@ class Comm(TypeClass):
logger.log_trace("Cannot send msg to connection '%s'" % conn) logger.log_trace("Cannot send msg to connection '%s'" % conn)
def msg(self, msgobj, header=None, senders=None, sender_strings=None, def msg(self, msgobj, header=None, senders=None, sender_strings=None,
persistent=True, online=False, emit=False, external=False): persistent=False, online=False, emit=False, external=False):
""" """
Send the given message to all players connected to channel. Note that Send the given message to all players connected to channel. Note that
no permission-checking is done here; it is assumed to have been no permission-checking is done here; it is assumed to have been
@ -191,9 +191,9 @@ class Comm(TypeClass):
connections where the sender is not a player or object. When connections where the sender is not a player or object. When
this is defined, external will be assumed. this is defined, external will be assumed.
external - Treat this message agnostic of its sender. external - Treat this message agnostic of its sender.
persistent (bool) - ignored if msgobj is a Msg or TempMsg. If True, persistent (default False) - ignored if msgobj is a Msg or TempMsg.
a Msg will be created, using header and senders keywords. If If True, a Msg will be created, using header and senders
False, other keywords will be ignored. keywords. If False, other keywords will be ignored.
online (bool) - If this is set true, only messages people who are online (bool) - If this is set true, only messages people who are
online. Otherwise, messages all players connected. This can online. Otherwise, messages all players connected. This can
make things faster, but may not trigger listeners on players make things faster, but may not trigger listeners on players

View file

@ -19,6 +19,7 @@ Server - (AMP server) Handles all mud operations. The server holds its own list
# imports needed on both server and portal side # imports needed on both server and portal side
import os import os
from collections import defaultdict from collections import defaultdict
from textwrap import wrap
try: try:
import cPickle as pickle import cPickle as pickle
except ImportError: except ImportError:
@ -40,7 +41,7 @@ SSHUTD = chr(7) # server shutdown
SSYNC = chr(8) # server sessigon sync SSYNC = chr(8) # server sessigon sync
MAXLEN = 65535 # max allowed data length in AMP protocol MAXLEN = 65535 # max allowed data length in AMP protocol
_MSGBUFFER = defaultdict(list)
def get_restart_mode(restart_file): def get_restart_mode(restart_file):
""" """
@ -136,10 +137,11 @@ class MsgPortal2Server(amp.Command):
""" """
Message portal -> server Message portal -> server
""" """
key = "MsgPortal2Server"
arguments = [('sessid', amp.Integer()), arguments = [('sessid', amp.Integer()),
('msg', amp.String()),
('ipart', amp.Integer()), ('ipart', amp.Integer()),
('nparts', amp.Integer()), ('nparts', amp.Integer()),
('msg', amp.String()),
('data', amp.String())] ('data', amp.String())]
errors = [(Exception, 'EXCEPTION')] errors = [(Exception, 'EXCEPTION')]
response = [] response = []
@ -149,10 +151,11 @@ class MsgServer2Portal(amp.Command):
""" """
Message server -> portal Message server -> portal
""" """
key = "MsgServer2Portal"
arguments = [('sessid', amp.Integer()), arguments = [('sessid', amp.Integer()),
('msg', amp.String()),
('ipart', amp.Integer()), ('ipart', amp.Integer()),
('nparts', amp.Integer()), ('nparts', amp.Integer()),
('msg', amp.String()),
('data', amp.String())] ('data', amp.String())]
errors = [(Exception, 'EXCEPTION')] errors = [(Exception, 'EXCEPTION')]
response = [] response = []
@ -166,7 +169,10 @@ class ServerAdmin(amp.Command):
operations on the server, such as when a new operations on the server, such as when a new
session connects or resyncs session connects or resyncs
""" """
key = "ServerAdmin"
arguments = [('sessid', amp.Integer()), arguments = [('sessid', amp.Integer()),
('ipart', amp.Integer()),
('nparts', amp.Integer()),
('operation', amp.String()), ('operation', amp.String()),
('data', amp.String())] ('data', amp.String())]
errors = [(Exception, 'EXCEPTION')] errors = [(Exception, 'EXCEPTION')]
@ -180,7 +186,10 @@ class PortalAdmin(amp.Command):
Sent when the server needs to perform admin Sent when the server needs to perform admin
operations on the portal. operations on the portal.
""" """
key = "PortalAdmin"
arguments = [('sessid', amp.Integer()), arguments = [('sessid', amp.Integer()),
('ipart', amp.Integer()),
('nparts', amp.Integer()),
('operation', amp.String()), ('operation', amp.String()),
('data', amp.String())] ('data', amp.String())]
errors = [(Exception, 'EXCEPTION')] errors = [(Exception, 'EXCEPTION')]
@ -194,6 +203,7 @@ class FunctionCall(amp.Command):
Sent when either process needs to call an Sent when either process needs to call an
arbitrary function in the other. arbitrary function in the other.
""" """
key = "FunctionCall"
arguments = [('module', amp.String()), arguments = [('module', amp.String()),
('function', amp.String()), ('function', amp.String()),
('args', amp.String()), ('args', amp.String()),
@ -209,7 +219,6 @@ loads = lambda data: pickle.loads(to_str(data))
# multipart message store # multipart message store
MSGBUFFER = defaultdict(list)
#------------------------------------------------------------ #------------------------------------------------------------
@ -254,44 +263,116 @@ class AMPProtocol(amp.AMP):
print "AMP Error for %(info)s: %(e)s" % {'info': info, print "AMP Error for %(info)s: %(e)s" % {'info': info,
'e': e.getErrorMessage()} 'e': e.getErrorMessage()}
def send_split_msg(self, sessid, msg, data, command): def safe_send(self, command, sessid, **kwargs):
""" """
This helper method splits the sending of a msg into multiple parts This helper method splits the sending of a message into
with a maxlength of MAXLEN. This is to avoid repetition in the two multiple parts with a maxlength of MAXLEN. This is to avoid
msg-sending commands. When calling this, the maximum length has repetition in two sending commands. when calling this the
already been exceeded. maximum length has already been exceeded. The max-length will
Inputs: be checked for all kwargs and these will be used as argument
msg - string to the command. The command type must have keywords ipart and
data - data dictionary nparts to track the parts and put them back together on the
command - one of MsgPortal2Server or MsgServer2Portal commands other side.
Returns a deferred or a list of such
""" """
# split the strings into acceptable chunks to_send = [(key, [string[i:i+MAXLEN] for i in range(0, len(string), MAXLEN)])
datastr = dumps(data) for key, string in kwargs.items()]
nmsg, ndata = len(msg), len(datastr) nparts_max = max(len(part[1]) for part in to_send)
if nmsg > MAXLEN or ndata > MAXLEN: if nparts_max == 1:
msglist = [msg[i:i + MAXLEN] for i in range(0, len(msg), MAXLEN)] # first try to send directly
datalist = [datastr[i:i + MAXLEN] return self.callRemote(command,
for i in range(0, len(datastr), MAXLEN)] sessid=sessid,
nmsglist, ndatalist = len(msglist), len(datalist) ipart=0,
if ndatalist < nmsglist: nparts=1,
datalist.extend("" for i in range(nmsglist - ndatalist)) **kwargs).addErrback(self.errback, command.key)
if nmsglist < ndatalist: else:
msglist.extend("" for i in range(ndatalist - nmsglist)) # one or more parts were too long for MAXLEN.
# we have split the msg/data into right-size chunks. Now we #print "TooLong triggered!"
# send it in sequence deferreds = []
return [self.callRemote(command, for ipart in range(nparts_max):
sessid=sessid, part_kwargs = {}
msg=to_str(msg), for key, str_part in to_send:
ipart=icall, try:
nparts=nmsglist, part_kwargs[key] = str_part[ipart]
data=dumps(data)).addErrback(self.errback, "MsgServer2Portal") except IndexError:
for icall, (msg, data) in enumerate(zip(msglist, datalist))] # means this kwarg needed fewer splits
part_kwargs[key] = ""
# send this part
#print "amp safe sending:", ipart, nparts_max, str_part
deferreds.append(self.callRemote(
command,
sessid=sessid,
ipart=ipart,
nparts=nparts_max,
**part_kwargs).addErrback(self.errback, command.key))
return deferreds
def safe_recv(self, command, sessid, ipart, nparts, **kwargs):
"""
Safely decode potentially split data coming over the wire. No
decoding or parsing is done here, only merging of data split
with safe_send().
If the data stream is not yet complete, this method will return
None, otherwise it will return a dictionary of the (possibly
merged) properties.
"""
global _MSGBUFFER
if nparts == 1:
# the most common case
return kwargs
else:
# part of a multi-part send
hashid = "%s_%s" % (command.key, sessid)
#print "amp safe receive:", ipart, nparts-1, kwargs
if ipart < nparts-1:
# not yet complete
_MSGBUFFER[hashid].append(kwargs)
return
else:
# all parts in place, put them back together
buf = _MSGBUFFER.pop(hashid) + [kwargs]
recv_kwargs = dict((key, "".join(kw[key] for kw in buf)) for key in kwargs)
return recv_kwargs
# def send_split_msg(self, sessid, msg, data, command):
# """
# This helper method splits the sending of a msg into multiple parts
# with a maxlength of MAXLEN. This is to avoid repetition in the two
# msg-sending commands. When calling this, the maximum length has
# already been exceeded.
# Inputs:
# msg - string
# data - data dictionary
# command - one of MsgPortal2Server or MsgServer2Portal commands
# """
# # split the strings into acceptable chunks
# datastr = dumps(data)
# nmsg, ndata = len(msg), len(datastr)
# if nmsg > MAXLEN or ndata > MAXLEN:
# msglist = [msg[i:i + MAXLEN] for i in range(0, len(msg), MAXLEN)]
# datalist = [datastr[i:i + MAXLEN]
# for i in range(0, len(datastr), MAXLEN)]
# nmsglist, ndatalist = len(msglist), len(datalist)
# if ndatalist < nmsglist:
# datalist.extend("" for i in range(nmsglist - ndatalist))
# if nmsglist < ndatalist:
# msglist.extend("" for i in range(ndatalist - nmsglist))
# # we have split the msg/data into right-size chunks. Now we
# # send it in sequence
# return [self.callRemote(command,
# sessid=sessid,
# msg=to_str(msg),
# ipart=icall,
# nparts=nmsglist,
# data=dumps(data)).addErrback(self.errback, "MsgServer2Portal")
# for icall, (msg, data) in enumerate(zip(msglist, datalist))]
# Message definition + helper methods to call/create each message type # Message definition + helper methods to call/create each message type
# Portal -> Server Msg # Portal -> Server Msg
def amp_msg_portal2server(self, sessid, msg, ipart, nparts, data): def amp_msg_portal2server(self, sessid, ipart, nparts, msg, data):
""" """
Relays message to server. This method is executed on the Server. Relays message to server. This method is executed on the Server.
@ -300,20 +381,27 @@ class AMPProtocol(amp.AMP):
and wait for the remaining parts to arrive before continuing. and wait for the remaining parts to arrive before continuing.
""" """
#print "msg portal -> server (server side):", sessid, msg, data #print "msg portal -> server (server side):", sessid, msg, data
global MSGBUFFER ret = self.safe_recv(MsgPortal2Server, sessid, ipart, nparts,
if nparts > 1: text=msg, data=data)
# a multipart message if ret is not None:
if len(MSGBUFFER[sessid]) != nparts: self.factory.server.sessions.data_in(sessid,
# we don't have all parts yet. Wait. text=ret["text"],
return {} **loads(ret["data"]))
else:
# we have all parts. Put it all together in the right order.
msg = "".join(t[1] for t in sorted(MSGBUFFER[sessid], key=lambda o: o[0]))
data = "".join(t[2] for t in sorted(MSGBUFFER[sessid], key=lambda o: o[0]))
del MSGBUFFER[sessid]
# call session hook with the data
self.factory.server.sessions.data_in(sessid, text=msg, **loads(data))
return {} return {}
# global MSGBUFFER
# if nparts > 1:
# # a multipart message
# if len(MSGBUFFER[sessid]) != nparts:
# # we don't have all parts yet. Wait.
# return {}
# else:
# # we have all parts. Put it all together in the right order.
# msg = "".join(t[1] for t in sorted(MSGBUFFER[sessid], key=lambda o: o[0]))
# data = "".join(t[2] for t in sorted(MSGBUFFER[sessid], key=lambda o: o[0]))
# del MSGBUFFER[sessid]
# # call session hook with the data
# self.factory.server.sessions.data_in(sessid, text=msg, **loads(data))
# return {}
MsgPortal2Server.responder(amp_msg_portal2server) MsgPortal2Server.responder(amp_msg_portal2server)
def call_remote_MsgPortal2Server(self, sessid, msg, data=""): def call_remote_MsgPortal2Server(self, sessid, msg, data=""):
@ -321,40 +409,50 @@ class AMPProtocol(amp.AMP):
Access method called by the Portal and executed on the Portal. Access method called by the Portal and executed on the Portal.
""" """
#print "msg portal->server (portal side):", sessid, msg, data #print "msg portal->server (portal side):", sessid, msg, data
try: return self.safe_send(MsgPortal2Server, sessid,
return self.callRemote(MsgPortal2Server, msg=to_str(msg) if msg is not None else "",
sessid=sessid, data=dumps(data))
msg=to_str(msg) if msg is not None else "", # try:
ipart=0, # return self.callRemote(MsgPortal2Server,
nparts=1, # sessid=sessid,
data=dumps(data)).addErrback(self.errback, "MsgPortal2Server") # msg=to_str(msg) if msg is not None else "",
except amp.TooLong: # ipart=0,
# the msg (or data) was too long for AMP to send. # nparts=1,
# We need to send in blocks. # data=dumps(data)).addErrback(self.errback, "MsgPortal2Server")
return self.send_split_msg(sessid, msg, data, MsgPortal2Server) # except amp.TooLong:
# # the msg (or data) was too long for AMP to send.
# # We need to send in blocks.
# return self.send_split_msg(sessid, msg, data, MsgPortal2Server)
# Server -> Portal message # Server -> Portal message
def amp_msg_server2portal(self, sessid, msg, ipart, nparts, data): def amp_msg_server2portal(self, sessid, ipart, nparts, msg, data):
""" """
Relays message to Portal. This method is executed on the Portal. Relays message to Portal. This method is executed on the Portal.
""" """
#print "msg server->portal (portal side):", sessid, msg #print "msg server->portal (portal side):", sessid, msg
global MSGBUFFER ret = self.safe_recv(MsgServer2Portal, sessid,
if nparts > 1: ipart, nparts, text=msg, data=data)
# a multipart message if ret is not None:
MSGBUFFER[sessid].append((ipart, msg, data)) self.factory.portal.sessions.data_out(sessid,
if len(MSGBUFFER[sessid]) != nparts: text=ret["text"],
# we don't have all parts yet. Wait. **loads(ret["data"]))
return {}
else:
# we have all parts. Put it all together in the right order.
msg = "".join(t[1] for t in sorted(MSGBUFFER[sessid], key=lambda o: o[0]))
data = "".join(t[2] for t in sorted(MSGBUFFER[sessid], key=lambda o: o[0]))
del MSGBUFFER[sessid]
# call session hook with the data
self.factory.portal.sessions.data_out(sessid, text=msg, **loads(data))
return {} return {}
# global MSGBUFFER
# if nparts > 1:
# # a multipart message
# MSGBUFFER[sessid].append((ipart, msg, data))
# if len(MSGBUFFER[sessid]) != nparts:
# # we don't have all parts yet. Wait.
# return {}
# else:
# # we have all parts. Put it all together in the right order.
# msg = "".join(t[1] for t in sorted(MSGBUFFER[sessid], key=lambda o: o[0]))
# data = "".join(t[2] for t in sorted(MSGBUFFER[sessid], key=lambda o: o[0]))
# del MSGBUFFER[sessid]
# # call session hook with the data
# self.factory.portal.sessions.data_out(sessid, text=msg, **loads(data))
# return {}
MsgServer2Portal.responder(amp_msg_server2portal) MsgServer2Portal.responder(amp_msg_server2portal)
def call_remote_MsgServer2Portal(self, sessid, msg, data=""): def call_remote_MsgServer2Portal(self, sessid, msg, data=""):
@ -362,47 +460,56 @@ class AMPProtocol(amp.AMP):
Access method called by the Server and executed on the Server. Access method called by the Server and executed on the Server.
""" """
#print "msg server->portal (server side):", sessid, msg, data #print "msg server->portal (server side):", sessid, msg, data
try: return self.safe_send(MsgServer2Portal, sessid,
return self.callRemote(MsgServer2Portal, msg=to_str(msg) if msg is not None else "",
sessid=sessid, data=dumps(data))
msg=to_str(msg) if msg is not None else "",
ipart=0, # try:
nparts=1, # return self.callRemote(MsgServer2Portal,
data=dumps(data)).addErrback(self.errback, "MsgServer2Portal") # sessid=sessid,
except amp.TooLong: # msg=to_str(msg) if msg is not None else "",
# the msg (or data) was too long for AMP to send. # ipart=0,
# We need to send in blocks. # nparts=1,
return self.send_split_msg(sessid, msg, data, MsgServer2Portal) # data=dumps(data)).addErrback(self.errback, "MsgServer2Portal")
# except amp.TooLong:
# # the msg (or data) was too long for AMP to send.
# # We need to send in blocks.
# return self.send_split_msg(sessid, msg, data, MsgServer2Portal)
# Server administration from the Portal side # Server administration from the Portal side
def amp_server_admin(self, sessid, operation, data): def amp_server_admin(self, sessid, ipart, nparts, operation, data):
""" """
This allows the portal to perform admin This allows the portal to perform admin
operations on the server. This is executed on the Server. operations on the server. This is executed on the Server.
""" """
data = loads(data) ret = self.safe_recv(ServerAdmin, sessid, ipart, nparts,
server_sessionhandler = self.factory.server.sessions operation=operation, data=data)
#print "serveradmin (server side):", sessid, ord(operation), data if ret is not None:
data = loads(ret["data"])
operation = ret["operation"]
server_sessionhandler = self.factory.server.sessions
if operation == PCONN: # portal_session_connect #print "serveradmin (server side):", sessid, ord(operation), data
# create a new session and sync it
server_sessionhandler.portal_connect(data)
elif operation == PDISCONN: # portal_session_disconnect if operation == PCONN: # portal_session_connect
# session closed from portal side # create a new session and sync it
self.factory.server.sessions.portal_disconnect(sessid) server_sessionhandler.portal_connect(data)
elif operation == PSYNC: # portal_session_sync elif operation == PDISCONN: # portal_session_disconnect
# force a resync of sessions when portal reconnects to server # session closed from portal side
# (e.g. after a server reboot) the data kwarg contains a dict self.factory.server.sessions.portal_disconnect(sessid)
# {sessid: {arg1:val1,...}} representing the attributes
# to sync for each session.
server_sessionhandler.portal_session_sync(data)
else:
raise Exception("operation %(op)s not recognized." % {'op': operation})
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
# contains a dict {sessid: {arg1:val1,...}}
# representing the attributes to sync for each
# session.
server_sessionhandler.portal_session_sync(data)
else:
raise Exception("operation %(op)s not recognized." % {'op': operation})
return {} return {}
ServerAdmin.responder(amp_server_admin) ServerAdmin.responder(amp_server_admin)
@ -412,47 +519,50 @@ class AMPProtocol(amp.AMP):
""" """
#print "serveradmin (portal side):", sessid, ord(operation), data #print "serveradmin (portal side):", sessid, ord(operation), data
data = dumps(data) data = dumps(data)
return self.safe_send(ServerAdmin, sessid, operation=operation, data=data)
return self.callRemote(ServerAdmin, # return self.callRemote(ServerAdmin,
sessid=sessid, # sessid=sessid,
operation=operation, # operation=operation,
data=data).addErrback(self.errback, "ServerAdmin") # data=data).addErrback(self.errback, "ServerAdmin")
# Portal administraton from the Server side # Portal administraton from the Server side
def amp_portal_admin(self, sessid, operation, data): def amp_portal_admin(self, sessid, ipart, nparts, operation, data):
""" """
This allows the server to perform admin This allows the server to perform admin
operations on the portal. This is executed on the Portal. operations on the portal. This is executed on the Portal.
""" """
data = loads(data)
portal_sessionhandler = self.factory.portal.sessions
#print "portaladmin (portal side):", sessid, ord(operation), data #print "portaladmin (portal side):", sessid, ord(operation), data
if operation == SLOGIN: # server_session_login ret = self.safe_recv(PortalAdmin, sessid, ipart, nparts,
# a session has authenticated; sync it. operation=operation, data=data)
portal_sessionhandler.server_logged_in(sessid, data) if ret is not None:
data = loads(data)
portal_sessionhandler = self.factory.portal.sessions
elif operation == SDISCONN: # server_session_disconnect if operation == SLOGIN: # server_session_login
# the server is ordering to disconnect the session # a session has authenticated; sync it.
portal_sessionhandler.server_disconnect(sessid, reason=data) portal_sessionhandler.server_logged_in(sessid, data)
elif operation == SDISCONNALL: # server_session_disconnect_all elif operation == SDISCONN: # server_session_disconnect
# server orders all sessions to disconnect # the server is ordering to disconnect the session
portal_sessionhandler.server_disconnect_all(reason=data) portal_sessionhandler.server_disconnect(sessid, reason=data)
elif operation == SSHUTD: # server_shutdown elif operation == SDISCONNALL: # server_session_disconnect_all
# the server orders the portal to shut down # server orders all sessions to disconnect
self.factory.portal.shutdown(restart=False) portal_sessionhandler.server_disconnect_all(reason=data)
elif operation == SSYNC: # server_session_sync elif operation == SSHUTD: # server_shutdown
# server wants to save session data to the portal, maybe because # the server orders the portal to shut down
# it's about to shut down. self.factory.portal.shutdown(restart=False)
portal_sessionhandler.server_session_sync(data)
# set a flag in case we are about to shut down soon elif operation == SSYNC: # server_session_sync
self.factory.server_restart_mode = True # server wants to save session data to the portal,
else: # maybe because it's about to shut down.
raise Exception("operation %(op)s not recognized." % {'op': operation}) portal_sessionhandler.server_session_sync(data)
# set a flag in case we are about to shut down soon
self.factory.server_restart_mode = True
else:
raise Exception("operation %(op)s not recognized." % {'op': operation})
return {} return {}
PortalAdmin.responder(amp_portal_admin) PortalAdmin.responder(amp_portal_admin)
@ -460,12 +570,12 @@ class AMPProtocol(amp.AMP):
""" """
Access method called by the server side. Access method called by the server side.
""" """
self.safe_send(PortalAdmin, sessid, operation=operation, data=dumps(data))
#print "portaladmin (server side):", sessid, ord(operation), data #print "portaladmin (server side):", sessid, ord(operation), data
return self.callRemote(PortalAdmin, # return self.callRemote(PortalAdmin,
sessid=sessid, # sessid=sessid,
operation=operation, # operation=operation,
data=dumps(data)).addErrback(self.errback, "PortalAdmin") # data=dumps(data)).addErrback(self.errback, "PortalAdmin")
# Extra functions # Extra functions
def amp_function_call(self, module, function, args, **kwargs): def amp_function_call(self, module, function, args, **kwargs):

View file

@ -79,11 +79,6 @@ def hashid(obj, suffix=""):
# Cache callback handlers # Cache callback handlers
#------------------------------------------------------------ #------------------------------------------------------------
#------------------------------------------------------------
# Field cache - makes sure to cache all database fields when
# they are saved, no matter from where.
#------------------------------------------------------------
# callback to field pre_save signal (connected in src.server.server) # callback to field pre_save signal (connected in src.server.server)
def field_pre_save(sender, instance=None, update_fields=None, raw=False, **kwargs): def field_pre_save(sender, instance=None, update_fields=None, raw=False, **kwargs):
""" """
@ -135,6 +130,21 @@ def field_post_save(sender, instance=None, update_fields=None, raw=False, **kwar
if trackerhandler: if trackerhandler:
trackerhandler.update(fieldname, _GA(instance, fieldname)) trackerhandler.update(fieldname, _GA(instance, fieldname))
#------------------------------------------------------------
# Attribute lookup cache
#------------------------------------------------------------
def get_attr_cache(obj):
"Retrieve lookup cache"
hid = hashid(obj)
return _ATTR_CACHE.get(hid, None)
def set_attr_cache(obj, store):
"Set lookup cache"
global _ATTR_CACHE
hid = hashid(obj)
_ATTR_CACHE[hid] = store
#------------------------------------------------------------ #------------------------------------------------------------
# Property cache - this is a generic cache for properties stored on models. # Property cache - this is a generic cache for properties stored on models.
@ -176,10 +186,8 @@ def get_cache_sizes():
global _ATTR_CACHE, _PROP_CACHE global _ATTR_CACHE, _PROP_CACHE
attr_n = len(_ATTR_CACHE) attr_n = len(_ATTR_CACHE)
attr_mb = sum(getsizeof(obj) for obj in _ATTR_CACHE) / 1024.0 attr_mb = sum(getsizeof(obj) for obj in _ATTR_CACHE) / 1024.0
field_n = 0 # sum(len(dic) for dic in _FIELD_CACHE.values())
field_mb = 0 # sum(sum([getsizeof(obj) for obj in dic.values()]) for dic in _FIELD_CACHE.values()) / 1024.0
prop_n = sum(len(dic) for dic in _PROP_CACHE.values()) prop_n = sum(len(dic) for dic in _PROP_CACHE.values())
prop_mb = sum(sum([getsizeof(obj) for obj in dic.values()]) for dic in _PROP_CACHE.values()) / 1024.0 prop_mb = sum(sum([getsizeof(obj) for obj in dic.values()]) for dic in _PROP_CACHE.values()) / 1024.0
return (attr_n, attr_mb), (field_n, field_mb), (prop_n, prop_mb) return (attr_n, attr_mb), (prop_n, prop_mb)

View file

@ -41,6 +41,7 @@ from django.contrib.contenttypes.models import ContentType
from src.utils.idmapper.models import SharedMemoryModel from src.utils.idmapper.models import SharedMemoryModel
from src.server.caches import get_prop_cache, set_prop_cache from src.server.caches import get_prop_cache, set_prop_cache
from src.server.caches import get_attr_cache, set_attr_cache
#from src.server.caches import call_ndb_hooks #from src.server.caches import call_ndb_hooks
from src.server.models import ServerConfig from src.server.models import ServerConfig
@ -116,8 +117,6 @@ class Attribute(SharedMemoryModel):
"Initializes the parent first -important!" "Initializes the parent first -important!"
SharedMemoryModel.__init__(self, *args, **kwargs) SharedMemoryModel.__init__(self, *args, **kwargs)
self.locks = LockHandler(self) self.locks = LockHandler(self)
self.no_cache = True
self.cached_value = None
class Meta: class Meta:
"Define Django meta options" "Define Django meta options"
@ -137,21 +136,16 @@ class Attribute(SharedMemoryModel):
""" """
Getter. Allows for value = self.value. Getter. Allows for value = self.value.
We cannot cache here since it makes certain cases (such We cannot cache here since it makes certain cases (such
as storing a dbobj which is then deleted elswhere) out-of-sync. as storing a dbobj which is then deleted elsewhere) out-of-sync.
The overhead of unpickling seems hard to avoid. The overhead of unpickling seems hard to avoid.
""" """
return from_pickle(self.db_value, db_obj=self) return from_pickle(self.db_value, db_obj=self)
#if self.no_cache:
# # re-create data from database and cache it
# value = from_pickle(self.db_value, db_obj=self)
# self.cached_value = value
# self.no_cache = False
#return self.cached_value
#@value.setter #@value.setter
def __value_set(self, new_value): def __value_set(self, new_value):
""" """
Setter. Allows for self.value = value. We make sure to cache everything. Setter. Allows for self.value = value. We cannot cache here,
see self.__value_get.
""" """
self.db_value = to_pickle(new_value) self.db_value = to_pickle(new_value)
self.save() self.save()
@ -160,15 +154,6 @@ class Attribute(SharedMemoryModel):
except AttributeError: except AttributeError:
pass pass
return return
#to_store = to_pickle(new_value)
#self.cached_value = from_pickle(to_store, db_obj=self)
#self.no_cache = False
#self.db_value = to_store
#self.save()
#try:
# self._track_db_value_change.update(self.cached_value)
#except AttributeError:
# pass
#@value.deleter #@value.deleter
def __value_del(self): def __value_del(self):
@ -223,8 +208,11 @@ class AttributeHandler(object):
self._cache = None self._cache = None
def _recache(self): def _recache(self):
self._cache = dict(("%s_%s" % (to_str(attr.db_key).lower(), to_str(attr.db_category, force_string=True).lower()), attr) self._cache = dict(("%s_%s" % (to_str(attr.db_key).lower(),
for attr in _GA(self.obj, self._m2m_fieldname).all()) to_str(attr.db_category,
force_string=True).lower()), attr)
for attr in _GA(self.obj, self._m2m_fieldname).all())
set_attr_cache(self.obj, self._cache) # currently only for testing
def has(self, key, category=None): def has(self, key, category=None):
""" """
@ -369,16 +357,6 @@ class AttributeHandler(object):
catkey = "_%s" % to_str(category, force_string=True).lower() catkey = "_%s" % to_str(category, force_string=True).lower()
return [attr for key, attr in self._cache.items() if key.endswith(catkey)] return [attr for key, attr in self._cache.items() if key.endswith(catkey)]
#if category==None:
# all_attrs = _GA(self.obj, self._m2m_fieldname).all()
#else:
# all_attrs = _GA(self.obj, self._m2m_fieldname).filter(db_category=category)
#if accessing_obj:
# return [attr for attr in all_attrs if attr.access(accessing_obj, self._attrread, default=default_access)]
#else:
# return list(all_attrs)
class NickHandler(AttributeHandler): class NickHandler(AttributeHandler):
""" """
Handles the addition and removal of Nicks Handles the addition and removal of Nicks

View file

@ -108,11 +108,11 @@ DEFAULT_NCLIENTS = 1
# line. All launched clients will be called upon to possibly do an # line. All launched clients will be called upon to possibly do an
# action with this frequency. # action with this frequency.
DEFAULT_TIMESTEP = 2 DEFAULT_TIMESTEP = 2
# chance of a client performing an action, per timestep. This helps to
# spread out usage randomly, like it would be in reality.
CHANCE_OF_ACTION = 0.05
# Port to use, if not specified on command line # Port to use, if not specified on command line
DEFAULT_PORT = settings.TELNET_PORTS[0] DEFAULT_PORT = settings.TELNET_PORTS[0]
# chance of an action happening, per timestep. This helps to
# spread out usage randomly, like it would be in reality.
CHANCE_OF_ACTION = 0.1
#------------------------------------------------------------ #------------------------------------------------------------

View file

@ -191,19 +191,19 @@ def c_moves(client):
# #(0.1, c_creates_button), # #(0.1, c_creates_button),
# #(0.4, c_moves)) # #(0.4, c_moves))
## "normal player" definition ## "normal player" definition
ACTIONS = ( c_login, #ACTIONS = ( c_login,
c_logout, # c_logout,
(0.01, c_digs), # (0.01, c_digs),
(0.1, c_socialize), # (0.1, c_socialize),
(0.39, c_looks), # (0.39, c_looks),
(0.1, c_help), # (0.1, c_help),
(0.4, c_moves)) # (0.4, c_moves))
## "socializing heavy builder" definition ## "socializing heavy builder" definition
#ACTIONS = (c_login, ACTIONS = (c_login,
# c_logout, c_logout,
# (0.1, c_socialize), (0.1, c_socialize),
# (0.1, c_looks), (0.1, c_looks),
# (0.1, c_help), (0.1, c_help),
# (0.2, c_creates_obj), (0.2, c_creates_obj),
# (0.2, c_digs), (0.2, c_digs),
# (0.3, c_moves)) (0.3, c_moves))

View file

@ -651,6 +651,9 @@ def check_evennia_dependencies():
sversion = south.__version__ sversion = south.__version__
if sversion < south_min: if sversion < south_min:
errstring += "\n WARNING: South version %s found. Evennia recommends version %s or higher." % (sversion, south_min) errstring += "\n WARNING: South version %s found. Evennia recommends version %s or higher." % (sversion, south_min)
if sversion == "0.8.3":
errstring += "\n ERROR: South version %s found. This has a known bug and will not work. Please upgrade." % sversion
no_error = False
except ImportError: except ImportError:
pass pass
# IRC support # IRC support