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:
parent
e82515f8cb
commit
94477b8340
8 changed files with 171 additions and 118 deletions
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue