Updated the reload and shutdown mecanism to avoid a loop when shutting down from inside the game. Made sure to have server sync correctly with portal at @reload (some session info were lost before). Some other cleanups.

This commit is contained in:
Griatch 2012-05-01 14:19:54 +02:00
parent e82515f8cb
commit 94477b8340
8 changed files with 171 additions and 118 deletions

View file

@ -17,8 +17,8 @@ matter the value of this file.
import os import os
import sys import sys
from optparse import OptionParser from optparse import OptionParser
from subprocess import Popen, call from subprocess import Popen
import Queue, thread, subprocess import Queue, thread
# #
# System Configuration # System Configuration
@ -276,7 +276,6 @@ def main():
if options.iportal: if options.iportal:
# make portal interactive # make portal interactive
portal_argv[1] = '--nodaemon' portal_argv[1] = '--nodaemon'
PORTAL_INTERACTIVE = True
set_restart_mode(PORTAL_RESTART, True) set_restart_mode(PORTAL_RESTART, True)
print "\nStarting Evennia Portal in non-Daemon mode (output to stdout)." print "\nStarting Evennia Portal in non-Daemon mode (output to stdout)."
else: else:

View file

@ -6,6 +6,7 @@ System commands
import traceback import traceback
import os, datetime, time import os, datetime, time
from sys import getsizeof
import django, twisted import django, twisted
from django.conf import settings from django.conf import settings
@ -16,9 +17,10 @@ from src.players.models import PlayerDB
from src.utils import logger, utils, gametime, create from src.utils import logger, utils, gametime, create
from src.commands.default.muxcommand import MuxCommand from src.commands.default.muxcommand import MuxCommand
# delayed imports
_resource = None _resource = None
_idmapper = None _idmapper = None
_attribute_cache = None
# limit symbol import for API # limit symbol import for API
__all__ = ("CmdReload", "CmdReset", "CmdShutdown", "CmdPy", __all__ = ("CmdReload", "CmdReset", "CmdShutdown", "CmdPy",
@ -537,12 +539,31 @@ class CmdTime(MuxCommand):
class CmdServerLoad(MuxCommand): class CmdServerLoad(MuxCommand):
""" """
server load statistics server load and memory statistics
Usage: Usage:
@serverload @serverload
Show server load statistics in a table. This command shows server load statistics and dynamic memory
usage.
Some Important statistics in the table:
{wServer load{n is an average of processor usage. It's usually
between 0 (no usage) and 1 (100% usage), but may also be
temporarily higher if your computer has multiple CPU cores.
The {wResident/Virtual memory{n displays the total memory used by
the server process.
Evennia {wcaches{n all retrieved database entities when they are
loaded by use of the idmapper functionality. This allows Evennia
to maintain the same instances of an entity and allowing
non-persistent storage schemes. The total amount of cached objects
are displayed plus a breakdown of database object types. Finally,
{wAttributes{n are cached on-demand for speed. The total amount of
memory used for this type of cache is also displayed.
""" """
key = "@server" key = "@server"
aliases = ["@serverload", "@serverprocess"] aliases = ["@serverload", "@serverprocess"]
@ -559,11 +580,13 @@ class CmdServerLoad(MuxCommand):
if not utils.host_os_is('posix'): if not utils.host_os_is('posix'):
string = "Process listings are only available under Linux/Unix." string = "Process listings are only available under Linux/Unix."
else: else:
global _resource, _idmapper global _resource, _idmapper, _attribute_cache
if not _resource: if not _resource:
import resource as _resource import resource as _resource
if not _idmapper: if not _idmapper:
from src.utils.idmapper import base as _idmapper from src.utils.idmapper import base as _idmapper
if not _attribute_cache:
from src.typeclasses.models import _ATTRIBUTE_CACHE as _attribute_cache
import resource import resource
loadavg = os.getloadavg() loadavg = os.getloadavg()
@ -622,10 +645,10 @@ class CmdServerLoad(MuxCommand):
for row in ftable: for row in ftable:
string += "\n " + "{w%s{n" % row[0] + "".join(row[1:]) string += "\n " + "{w%s{n" % row[0] + "".join(row[1:])
# cache size # object cache size
cachedict = _idmapper.cache_size() cachedict = _idmapper.cache_size()
totcache = cachedict["_total"] totcache = cachedict["_total"]
string += "\n{w Object cache usage: %5.2f MB (%i items){n" % (totcache[1], totcache[0]) string += "\n{w Database entity (idmapper) cache usage:{n %5.2f MB (%i items)" % (totcache[1], totcache[0])
sorted_cache = sorted([(key, tup[0], tup[1]) for key, tup in cachedict.items() if key !="_total" and tup[0] > 0], sorted_cache = sorted([(key, tup[0], tup[1]) for key, tup in cachedict.items() if key !="_total" and tup[0] > 0],
key=lambda tup: tup[2], reverse=True) key=lambda tup: tup[2], reverse=True)
table = [[tup[0] for tup in sorted_cache], table = [[tup[0] for tup in sorted_cache],
@ -634,6 +657,10 @@ class CmdServerLoad(MuxCommand):
ftable = utils.format_table(table, 5) ftable = utils.format_table(table, 5)
for row in ftable: for row in ftable:
string += "\n " + row[0] + row[1] + row[2] string += "\n " + row[0] + row[1] + row[2]
# attribute cache
size = sum([sum([getsizeof(obj) for obj in dic.values()]) for dic in _attribute_cache.values()])/1024.0
count = sum([len(dic) for dic in _attribute_cache.values()])
string += "\n{w On-entity Attribute cache usage:{n %5.2f MB (%i items)" % (size, count)
caller.msg(string) caller.msg(string)
# class CmdPs(MuxCommand): # class CmdPs(MuxCommand):

View file

@ -1,6 +1,7 @@
""" """
Contains the protocols, commands, and client factory needed for the server Contains the protocols, commands, and client factory needed for the Server and Portal
to service the MUD portal proxy. to communicate with each other, letting Portal work as a proxy. Both sides use this
same protocol.
The separation works like this: The separation works like this:
@ -13,24 +14,25 @@ Server - (AMP server) Handles all mud operations. The server holds its own list
and when a session connects/disconnects and when a session connects/disconnects
""" """
import os
# imports needed on both server and portal side
try: try:
import cPickle as pickle import cPickle as pickle
except ImportError: except ImportError:
import pickle import pickle
from twisted.protocols import amp from twisted.protocols import amp
from twisted.internet import protocol from twisted.internet import protocol
from django.conf import settings
from src.utils.utils import to_str from src.utils.utils import to_str
from src.server.models import ServerConfig # these are only needed on the server side, so we delay loading of them
from src.scripts.models import ScriptDB # so as to not have to load them on the portal too. Note: It's doubtful
from src.players.models import PlayerDB # if this really matters, considering many of the
from src.server.serversession import ServerSession # protocols require import of django components (at least settings).
_ServerConfig = None
PORTAL_RESTART = os.path.join(settings.GAME_DIR, "portal.restart") _ScriptDB = None
SERVER_RESTART = os.path.join(settings.GAME_DIR, "server.restart") _PlayerDB = None
_ServerSession = None
_ = None #i18n hook
# communication bits # communication bits
@ -43,10 +45,6 @@ SDISCONNALL = chr(6) # server session disconnect all
SSHUTD = chr(7) # server shutdown SSHUTD = chr(7) # server shutdown
SSYNC = chr(8) # server session sync SSYNC = chr(8) # server session sync
# i18n
from django.utils.translation import ugettext as _
def get_restart_mode(restart_file): def get_restart_mode(restart_file):
""" """
Parse the server/portal restart status Parse the server/portal restart status
@ -87,7 +85,7 @@ class AmpClientFactory(protocol.ReconnectingClientFactory):
""" """
# Initial reconnect delay in seconds. # Initial reconnect delay in seconds.
initialDelay = 1 initialDelay = 1
#factor = 1.5 factor = 1.5
maxDelay = 1 maxDelay = 1
def __init__(self, portal): def __init__(self, portal):
@ -115,14 +113,19 @@ class AmpClientFactory(protocol.ReconnectingClientFactory):
""" """
Called when the AMP connection to the MUD server is lost. Called when the AMP connection to the MUD server is lost.
""" """
if not get_restart_mode(SERVER_RESTART): if not hasattr(self, "server_restart_mode"):
self.portal.sessions.announce_all(_(" Portal lost connection to Server.")) # Don't translate this; avoiding loading django on portal side.
self.portal.sessions.announce_all(" Portal lost connection to Server.")
protocol.ReconnectingClientFactory.clientConnectionLost(self, connector, reason) protocol.ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
def clientConnectionFailed(self, connector, reason): def clientConnectionFailed(self, connector, reason):
""" """
Called when an AMP connection attempt to the MUD server fails. Called when an AMP connection attempt to the MUD server fails.
""" """
if hasattr(self, "server_restart_mode"):
self.maxDelay = 1
else:
self.maxDelay = 10
self.portal.sessions.announce_all(" ...") self.portal.sessions.announce_all(" ...")
protocol.ReconnectingClientFactory.clientConnectionFailed(self, connector, reason) protocol.ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
@ -214,27 +217,27 @@ class AMPProtocol(amp.AMP):
def connectionMade(self): def connectionMade(self):
""" """
This is called when a connection is established This is called when a connection is established
between server and portal. It is called on both sides, between server and portal. AMP calls it on both sides,
so we need to make sure to only trigger resync from the so we need to make sure to only trigger resync from the
server side. portal side.
""" """
if hasattr(self.factory, "portal"): if hasattr(self.factory, "portal"):
# only the portal has the 'portal' property, so we know we are
# on the portal side and can initialize the connection.
sessdata = self.factory.portal.sessions.get_all_sync_data() sessdata = self.factory.portal.sessions.get_all_sync_data()
#print sessdata
self.call_remote_ServerAdmin(0, self.call_remote_ServerAdmin(0,
PSYNC, PSYNC,
data=sessdata) data=sessdata)
if get_restart_mode(SERVER_RESTART):
msg = _(" ... Server restarted.")
self.factory.portal.sessions.announce_all(msg)
self.factory.portal.sessions.at_server_connection() self.factory.portal.sessions.at_server_connection()
if hasattr(self.factory, "server_restart_mode"):
del self.factory.server_restart_mode
# Error handling # Error handling
def errback(self, e, info): def errback(self, e, info):
"error handler, to avoid dropping connections on server tracebacks." "error handler, to avoid dropping connections on server tracebacks."
e.trap(Exception) e.trap(Exception)
print _("AMP Error for %(info)s: %(e)s") % {'info': info, 'e': e.getErrorMessage()} print "AMP Error for %(info)s: %(e)s" % {'info': info, 'e': e.getErrorMessage()}
# Message definition + helper methods to call/create each message type # Message definition + helper methods to call/create each message type
@ -255,7 +258,7 @@ 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 #print "msg portal->server (portal side):", sessid, msg
self.callRemote(MsgPortal2Server, return self.callRemote(MsgPortal2Server,
sessid=sessid, sessid=sessid,
msg=msg, msg=msg,
data=dumps(data)).addErrback(self.errback, "MsgPortal2Server") data=dumps(data)).addErrback(self.errback, "MsgPortal2Server")
@ -276,7 +279,7 @@ 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
self.callRemote(MsgServer2Portal, return self.callRemote(MsgServer2Portal,
sessid=sessid, sessid=sessid,
msg=to_str(msg), msg=to_str(msg),
data=dumps(data)).addErrback(self.errback, "OOBServer2Portal") data=dumps(data)).addErrback(self.errback, "OOBServer2Portal")
@ -319,7 +322,7 @@ class AMPProtocol(amp.AMP):
Access method called by the Server and executed on the Portal. Access method called by the Server and executed on the Portal.
""" """
#print "oob server->portal (server side):", sessid, data #print "oob server->portal (server side):", sessid, data
self.callRemote(OOBServer2Portal, return self.callRemote(OOBServer2Portal,
sessid=sessid, sessid=sessid,
data=dumps(data)).addErrback(self.errback, "OOBServer2Portal") data=dumps(data)).addErrback(self.errback, "OOBServer2Portal")
@ -335,14 +338,28 @@ class AMPProtocol(amp.AMP):
#print "serveradmin (server side):", sessid, operation, data #print "serveradmin (server side):", sessid, operation, data
# late import of django-related stuff. This avoids having to
# load these also for the portal side.
global _ServerConfig, _ScriptDB, _PlayerDB, _ServerSession, _
if not _ServerConfig:
from src.server.models import ServerConfig as _ServerConfig
if not _ScriptDB:
from src.scripts.models import ScriptDB as _ScriptDB
if not _PlayerDB:
from src.players.models import PlayerDB as _PlayerDB
if not _ServerSession:
from src.server.serversession import ServerSession as _ServerSession
if not _:
from django.utils.translation import ugettext as _
if operation == PCONN: #portal_session_connect if operation == PCONN: #portal_session_connect
# create a new session and sync it # create a new session and sync it
sess = ServerSession() sess = _ServerSession()
sess.sessionhandler = self.factory.server.sessions sess.sessionhandler = self.factory.server.sessions
sess.load_sync_data(data) sess.load_sync_data(data)
if sess.logged_in and sess.uid: if sess.logged_in and sess.uid:
# this can happen in the case of auto-authenticating protocols like SSH # this can happen in the case of auto-authenticating protocols like SSH
sess.player = PlayerDB.objects.get_player_from_uid(sess.uid) sess.player = _PlayerDB.objects.get_player_from_uid(sess.uid)
sess.at_sync() # this runs initialization without acr sess.at_sync() # this runs initialization without acr
self.factory.server.sessions.portal_connect(sessid, sess) self.factory.server.sessions.portal_connect(sessid, sess)
@ -358,21 +375,22 @@ class AMPProtocol(amp.AMP):
sesslist = [] sesslist = []
server_sessionhandler = self.factory.server.sessions server_sessionhandler = self.factory.server.sessions
for sessid, sessdict in data.items(): for sessid, sessdict in data.items():
sess = ServerSession() sess = _ServerSession()
sess.sessionhandler = server_sessionhandler sess.sessionhandler = server_sessionhandler
sess.load_sync_data(sessdict) sess.load_sync_data(sessdict)
if sess.uid: if sess.uid:
sess.player = PlayerDB.objects.get_player_from_uid(sess.uid) sess.player = _PlayerDB.objects.get_player_from_uid(sess.uid)
sesslist.append(sess) sesslist.append(sess)
# replace sessions on server # replace sessions on server
server_sessionhandler.portal_session_sync(sesslist) server_sessionhandler.portal_session_sync(sesslist)
# after sync is complete we force-validate all scripts (this starts everything) # after sync is complete we force-validate all scripts (this starts everything)
init_mode = ServerConfig.objects.conf("server_restart_mode", default=None) init_mode = _ServerConfig.objects.conf("server_restart_mode", default=None)
ScriptDB.objects.validate(init_mode=init_mode) _ScriptDB.objects.validate(init_mode=init_mode)
ServerConfig.objects.conf("server_restart_mode", delete=True) _ServerConfig.objects.conf("server_restart_mode", delete=True)
# let the server announce the reconnection
server_sessionhandler.announce_all(_(" ... Server restarted."))
else: else:
raise Exception(_("operation %(op)s not recognized.") % {'op': operation}) raise Exception("operation %(op)s not recognized." % {'op': operation})
return {} return {}
ServerAdmin.responder(amp_server_admin) ServerAdmin.responder(amp_server_admin)
@ -384,7 +402,7 @@ class AMPProtocol(amp.AMP):
#print "serveradmin (portal side):", sessid, operation, data #print "serveradmin (portal side):", sessid, operation, data
data = dumps(data) data = dumps(data)
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")
@ -398,7 +416,7 @@ class AMPProtocol(amp.AMP):
""" """
data = loads(data) data = loads(data)
#print "portaladmin (portal side):", sessid, operation, data #print "portaladmin (portal side):", sessid, ord(operation), data
if operation == SLOGIN: # 'server_session_login' if operation == SLOGIN: # 'server_session_login'
# a session has authenticated; sync it. # a session has authenticated; sync it.
sess = self.factory.portal.sessions.get_session(sessid) sess = self.factory.portal.sessions.get_session(sessid)
@ -421,20 +439,19 @@ class AMPProtocol(amp.AMP):
# it's about to shut down. We don't overwrite any sessions, # it's about to shut down. We don't overwrite any sessions,
# just update data on them and remove eventual ones that are # just update data on them and remove eventual ones that are
# out of sync (shouldn't happen normally). # out of sync (shouldn't happen normally).
portal_sessionhandler = self.factory.portal.sessions
portal_sessionhandler = self.factory.portal.sessions.sessions
to_save = [sessid for sessid in data if sessid in portal_sessionhandler.sessions] to_save = [sessid for sessid in data if sessid in portal_sessionhandler.sessions]
to_delete = [sessid for sessid in data if sessid not in to_save] to_delete = [sessid for sessid in data if sessid not in to_save]
# save protocols # save protocols
for sessid in to_save: for sessid in to_save:
portal_sessionhandler.sessions[sessid].load_sync_data(data[sessid]) portal_sessionhandler.sessions[sessid].load_sync_data(data[sessid])
# disconnect missing protocols # disconnect missing protocols
for sessid in to_delete: for sessid in to_delete:
portal_sessionhandler.server_disconnect(sessid) portal_sessionhandler.server_disconnect(sessid)
# save a flag in case connection is soon lost.
self.factory.server_restart_mode = True
else: else:
raise Exception(_("operation %(op)s not recognized.") % {'op': operation}) raise Exception("operation %(op)s not recognized." % {'op': operation})
return {} return {}
PortalAdmin.responder(amp_portal_admin) PortalAdmin.responder(amp_portal_admin)
@ -442,10 +459,10 @@ class AMPProtocol(amp.AMP):
""" """
Access method called by the server side. Access method called by the server side.
""" """
#print "portaladmin (server side):", sessid, operation, data #print "portaladmin (server side):", sessid, ord(operation), data
data = dumps(data) data = dumps(data)
self.callRemote(PortalAdmin, return self.callRemote(PortalAdmin,
sessid=sessid, sessid=sessid,
operation=operation, operation=operation,
data=data).addErrback(self.errback, "PortalAdmin") data=data).addErrback(self.errback, "PortalAdmin")

View file

@ -95,7 +95,7 @@ class Portal(object):
# set a callback if the server is killed abruptly, # set a callback if the server is killed abruptly,
# by Ctrl-C, reboot etc. # by Ctrl-C, reboot etc.
reactor.addSystemEventTrigger('before', 'shutdown', self.shutdown, _abrupt=True) reactor.addSystemEventTrigger('before', 'shutdown', self.shutdown, _reactor_stopping=True)
self.game_running = False self.game_running = False
@ -139,26 +139,33 @@ class Portal(object):
f.write(str(mode)) f.write(str(mode))
f.close() f.close()
def shutdown(self, restart=None, _abrupt=False): def shutdown(self, restart=None, _reactor_stopping=False):
""" """
Shuts down the server from inside it. Shuts down the server from inside it.
restart - True/False sets the flags so the server will be restart - True/False sets the flags so the server will be
restarted or not. If None, the current flag setting restarted or not. If None, the current flag setting
(set at initialization or previous runs) is used. (set at initialization or previous runs) is used.
_abrupt - this is set if server is stopped by a kill command, _reactor_stopping - this is set if server is already in the process of
in which case the reactor is dead anyway. shutting down; in this case we don't need to stop it again.
Note that restarting (regardless of the setting) will not work Note that restarting (regardless of the setting) will not work
if the Portal is currently running in daemon mode. In that if the Portal is currently running in daemon mode. In that
case it always needs to be restarted manually. case it always needs to be restarted manually.
""" """
if _reactor_stopping and hasattr(self, "shutdown_complete"):
# we get here due to us calling reactor.stop below. No need
# to do the shutdown procedure again.
return
self.set_restart_mode(restart) self.set_restart_mode(restart)
if not _abrupt:
reactor.callLater(0, reactor.stop)
if os.name == 'nt' and os.path.exists(PORTAL_PIDFILE): if os.name == 'nt' and os.path.exists(PORTAL_PIDFILE):
# for Windows we need to remove pid files manually # for Windows we need to remove pid files manually
os.remove(PORTAL_PIDFILE) os.remove(PORTAL_PIDFILE)
if not _reactor_stopping:
# shutting down the reactor will trigger another signal. We set
# a flag to avoid loops.
self.shutdown_complete = True
reactor.callLater(0, reactor.stop)
#------------------------------------------------------------ #------------------------------------------------------------
# #

View file

@ -17,8 +17,7 @@ if os.name == 'nt':
os.path.dirname(os.path.abspath(__file__))))) os.path.dirname(os.path.abspath(__file__)))))
from twisted.application import internet, service from twisted.application import internet, service
from twisted.internet import protocol, reactor, defer from twisted.internet import reactor, defer
from twisted.web import server, static
import django import django
from django.db import connection from django.db import connection
from django.conf import settings from django.conf import settings
@ -106,7 +105,7 @@ class Evennia(object):
# set a callback if the server is killed abruptly, # set a callback if the server is killed abruptly,
# by Ctrl-C, reboot etc. # by Ctrl-C, reboot etc.
reactor.addSystemEventTrigger('before', 'shutdown', self.shutdown, _abrupt=True) reactor.addSystemEventTrigger('before', 'shutdown', self.shutdown, _reactor_stopping=True)
self.game_running = True self.game_running = True
@ -133,6 +132,7 @@ class Evennia(object):
""" """
This attempts to run the initial_setup script of the server. This attempts to run the initial_setup script of the server.
It returns if this is not the first time the server starts. It returns if this is not the first time the server starts.
Once finished the last_initial_setup_step is set to -1.
""" """
last_initial_setup_step = ServerConfig.objects.conf('last_initial_setup_step') last_initial_setup_step = ServerConfig.objects.conf('last_initial_setup_step')
if not last_initial_setup_step: if not last_initial_setup_step:
@ -184,10 +184,12 @@ class Evennia(object):
returned so the server knows which more it's in. returned so the server knows which more it's in.
""" """
if mode == None: if mode == None:
if os.path.exists(SERVER_RESTART) and 'True' == open(SERVER_RESTART, 'r').read(): f = open(SERVER_RESTART, 'r')
if os.path.exists(SERVER_RESTART) and 'True' == f.read():
mode = 'reload' mode = 'reload'
else: else:
mode = 'shutdown' mode = 'shutdown'
f.close()
else: else:
restart = mode in ('reload', 'reset') restart = mode in ('reload', 'reset')
f = open(SERVER_RESTART, 'w') f = open(SERVER_RESTART, 'w')
@ -195,7 +197,8 @@ class Evennia(object):
f.close() f.close()
return mode return mode
def shutdown(self, mode=None, _abrupt=False): @defer.inlineCallbacks
def shutdown(self, mode=None, _reactor_stopping=False):
""" """
Shuts down the server from inside it. Shuts down the server from inside it.
@ -204,11 +207,15 @@ class Evennia(object):
'reset' - server restarts, non-persistent scripts stopped, at_shutdown hooks called. 'reset' - server restarts, non-persistent scripts stopped, at_shutdown hooks called.
'shutdown' - like reset, but server will not auto-restart. 'shutdown' - like reset, but server will not auto-restart.
None - keep currently set flag from flag file. None - keep currently set flag from flag file.
_abrupt - this is set if server is stopped by a kill command, _reactor_stopping - this is set if server is stopped by a kill command OR this method was already called
in which case the reactor is dead anyway. once - in both cases the reactor is dead/stopping already.
""" """
mode = self.set_restart_mode(mode) if _reactor_stopping and hasattr(self, "shutdown_complete"):
# this means we have already passed through this method once; we don't need
# to run the shutdown procedure again.
defer.returnValue(None)
mode = self.set_restart_mode(mode)
# call shutdown hooks on all cached objects # call shutdown hooks on all cached objects
from src.objects.models import ObjectDB from src.objects.models import ObjectDB
@ -217,31 +224,34 @@ class Evennia(object):
if mode == 'reload': if mode == 'reload':
# call restart hooks # call restart hooks
[(o.typeclass, o.at_server_reload()) for o in ObjectDB.get_all_cached_instances()] yield [(o.typeclass, o.at_server_reload()) for o in ObjectDB.get_all_cached_instances()]
[(p.typeclass, p.at_server_reload()) for p in PlayerDB.get_all_cached_instances()] yield [(p.typeclass, p.at_server_reload()) for p in PlayerDB.get_all_cached_instances()]
[(s.typeclass, s.pause(), s.at_server_reload()) for s in ScriptDB.get_all_cached_instances()] yield [(s.typeclass, s.pause(), s.at_server_reload()) for s in ScriptDB.get_all_cached_instances()]
yield self.sessions.all_sessions_portal_sync()
ServerConfig.objects.conf("server_restart_mode", "reload") ServerConfig.objects.conf("server_restart_mode", "reload")
else: else:
if mode == 'reset': if mode == 'reset':
# don't call disconnect hooks on reset # don't call disconnect hooks on reset
[(o.typeclass, o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()] yield [(o.typeclass, o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
yield self.all_sessions_portal_sync()
else: # shutdown else: # shutdown
[(o.typeclass, o.at_disconnect(), o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()] yield [(o.typeclass, o.at_disconnect(), o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
[(p.typeclass, p.at_server_shutdown()) for p in PlayerDB.get_all_cached_instances()] yield [(p.typeclass, p.at_server_shutdown()) for p in PlayerDB.get_all_cached_instances()]
[(s.typeclass, s.at_server_shutdown()) for s in ScriptDB.get_all_cached_instances()] yield [(s.typeclass, s.at_server_shutdown()) for s in ScriptDB.get_all_cached_instances()]
ServerConfig.objects.conf("server_restart_mode", "reset") ServerConfig.objects.conf("server_restart_mode", "reset")
if not _abrupt:
if SERVER_HOOK_MODULE: if SERVER_HOOK_MODULE:
SERVER_HOOK_MODULE.at_server_stop() SERVER_HOOK_MODULE.at_server_stop()
reactor.callLater(0, reactor.stop) # if _reactor_stopping is true, reactor does not need to be stopped again.
if os.name == 'nt' and os.path.exists(SERVER_PIDFILE): if os.name == 'nt' and os.path.exists(SERVER_PIDFILE):
# for Windows we need to remove pid files manually # for Windows we need to remove pid files manually
os.remove(SERVER_PIDFILE) os.remove(SERVER_PIDFILE)
if not _reactor_stopping:
# this will also send a reactor.stop signal, so we set a flag to avoid loops.
self.shutdown_complete = True
reactor.callLater(0, reactor.stop)
#------------------------------------------------------------ #------------------------------------------------------------
# #

View file

@ -33,10 +33,10 @@ class Session(object):
""" """
# names of attributes that should be affected by syncing. # names of attributes that should be affected by syncing.
_attrs_to_sync = ['protocol_key', 'address', 'suid', 'sessid', 'uid', 'uname', _attrs_to_sync = ('protocol_key', 'address', 'suid', 'sessid', 'uid', 'uname',
'logged_in', 'cid', 'encoding', 'logged_in', 'cid', 'encoding',
'conn_time', 'cmd_last', 'cmd_last_visible', 'cmd_total', 'conn_time', 'cmd_last', 'cmd_last_visible', 'cmd_total',
'server_data'] 'server_data')
def init_session(self, protocol_key, address, sessionhandler): def init_session(self, protocol_key, address, sessionhandler):
""" """
@ -84,18 +84,15 @@ class Session(object):
""" """
Return all data relevant to sync the session Return all data relevant to sync the session
""" """
sessdata = {} return dict((key, value) for key, value in self.__dict__.items() if key in self._attrs_to_sync)
for attrname in self._attrs_to_sync:
sessdata[attrname] = self.__dict__.get(attrname, None)
return sessdata
def load_sync_data(self, sessdata): def load_sync_data(self, sessdata):
""" """
Takes a session dictionary, as created by get_sync_data, Takes a session dictionary, as created by get_sync_data,
and loads it into the correct attributes of the session. and loads it into the correct properties of the session.
""" """
for attrname, value in sessdata.items(): for propname, value in sessdata.items():
self.__dict__[attrname] = value self.__dict__[propname] = value
def at_sync(self): def at_sync(self):
""" """

View file

@ -69,11 +69,7 @@ class SessionHandler(object):
Create a dictionary of sessdata dicts representing all Create a dictionary of sessdata dicts representing all
sessions in store. sessions in store.
""" """
sessdict = {} return dict((sessid, sess.get_sync_data()) for sessid, sess in self.sessions.items())
for sess in self.sessions.values():
# copy all relevant data from all sessions
sessdict[sess.sessid] = sess.get_sync_data()
return sessdict
#------------------------------------------------------------ #------------------------------------------------------------
# Server-SessionHandler class # Server-SessionHandler class
@ -179,14 +175,14 @@ class ServerSessionHandler(SessionHandler):
operation=SLOGIN, operation=SLOGIN,
data=sessdata) data=sessdata)
def session_sync(self): def all_sessions_portal_sync(self):
""" """
This is called by the server when it reboots. It syncs all session data This is called by the server when it reboots. It syncs all session data
to the portal. to the portal. Returns a deferred!
""" """
sessdata = self.get_all_sync_data() sessdata = self.get_all_sync_data()
self.server.amp_protocol.call_remote_PortalAdmin(0, return self.server.amp_protocol.call_remote_PortalAdmin(0,
SSYNC, operation=SSYNC,
data=sessdata) data=sessdata)

View file

@ -144,15 +144,15 @@ def c_moves(client):
# #
# heavy builder definition # heavy builder definition
ACTIONS = ( c_login, #ACTIONS = ( c_login,
c_logout, # c_logout,
(0.2, c_looks), # (0.2, c_looks),
(0.1, c_examines), # (0.1, c_examines),
(0.2, c_help), # (0.2, c_help),
(0.1, c_digs), # (0.1, c_digs),
(0.1, c_creates_obj), # (0.1, c_creates_obj),
#(0.1, c_creates_button), # #(0.1, c_creates_button),
(0.2, c_moves)) # (0.2, c_moves))
# "normal builder" definition # "normal builder" definition
#ACTIONS = ( c_login, #ACTIONS = ( c_login,
# c_logout, # c_logout,
@ -164,13 +164,13 @@ ACTIONS = ( c_login,
# #(0.1, c_creates_button), # #(0.1, c_creates_button),
# (0.3, c_moves)) # (0.3, c_moves))
# "normal player" definition # "normal player" definition
#ACTIONS = ( c_login, ACTIONS = ( c_login,
# c_logout, c_logout,
# (0.4, c_looks), (0.7, c_looks),
# #(0.1, c_examines), #(0.1, c_examines),
# (0.2, c_help), (0.3, c_help))
# #(0.1, c_digs), #(0.1, c_digs),
# #(0.1, c_creates_obj), #(0.1, c_creates_obj),
# #(0.1, c_creates_button), #(0.1, c_creates_button),
#(0.4, c_moves)) #(0.4, c_moves))