Shifting ProcPool out of src and into a contrib, using the service plugin system.

This commit is contained in:
Griatch 2012-09-22 20:40:30 +02:00
parent f677902811
commit 93d95377ce
23 changed files with 363 additions and 390 deletions

View file

@ -7,7 +7,6 @@ sets up all the networking features. (this is done automatically
by game/evennia.py).
"""
import time
import sys
import os
if os.name == 'nt':
@ -19,10 +18,10 @@ from twisted.application import internet, service
from twisted.internet import protocol, reactor
from twisted.web import server, static
from django.conf import settings
from src.utils.utils import get_evennia_version, mod_import
from src.utils.utils import get_evennia_version, mod_import, make_iter
from src.server.sessionhandler import PORTAL_SESSIONS
PORTAL_SERVICES_PLUGIN_MODULE = mod_import(settings.PORTAL_SERVICES_PLUGIN_MODULE)
PORTAL_SERVICES_PLUGIN_MODULES = [mod_import(module) for module in make_iter(settings.PORTAL_SERVICES_PLUGIN_MODULES)]
if os.name == 'nt':
# For Windows we need to handle pid files manually.
@ -287,9 +286,9 @@ if WEBSERVER_ENABLED:
webserver.setName('EvenniaWebServer%s' % pstring)
PORTAL.services.addService(webserver)
if PORTAL_SERVICES_PLUGIN_MODULE:
for plugin_module in PORTAL_SERVICES_PLUGIN_MODULES:
# external plugin services to start
PORTAL_SERVICES_PLUGIN_MODULE.start_plugin_services(PORTAL)
plugin_module.start_plugin_services(PORTAL)
if os.name == 'nt':

View file

@ -1,134 +0,0 @@
"""
ProcPool
This module implements and handles processes running under the AMPoule
pool. The ProcPool can accept data from processes and runs them in a
dynamically changing pool of processes, talking to them over AMP. This
offers full asynchronous operation (Python threading does not work as
well for this).
The ExecuteCode command found here is used by src.utils.utils.run_async()
to launch snippets of code on the process pool. The pool itself is a
service named "Process Pool" and is controlled from src/server/server.py.
It can be customized via settings.PROCPOOL_*
"""
from twisted.protocols import amp
from src.utils.ampoule.child import AMPChild
from src.utils.utils import to_pickle, from_pickle
from src import PROC_MODIFIED_OBJS
# handle global setups
_LOGGER = None
# Evennia multiprocess command
class ExecuteCode(amp.Command):
"""
Executes python code in the python process,
returning result when ready.
source - a compileable Python source code string
environment - a pickled dictionary of Python
data. Each key will become the name
of a variable available to the source
code. Database objects are stored on
the form ((app, modelname), id) allowing
the receiver to easily rebuild them on
this side.
errors - an all-encompassing error handler
response - a string or a pickled string
"""
arguments = [('source', amp.String()),
('environment', amp.String())]
errors = [(Exception, 'EXCEPTION')]
response = [('response', amp.String()),
('recached', amp.String())]
# Evennia multiprocess child process template
class ProcPoolChild(AMPChild):
"""
This is describing what happens on the subprocess side.
This already supports Echo, Shutdown and Ping.
Methods:
executecode - a remote code execution environment
"""
def executecode(self, source, environment):
"""
Remote code execution
source - Python code snippet
environment - pickled dictionary of environment
variables. They are stored in
two keys "normal" and "objs" where
normal holds a dictionary of
normally pickled python objects
wheras objs points to a dictionary
of database represenations ((app,key),id).
The environment's entries will be made available as
local variables during the execution. Normal eval
results will be returned as-is. For more complex
code snippets (run by exec), the _return function
is available: All data sent to _return(retval) will
be returned from this system whenever the system
finishes. Multiple calls to _return will result in
a list being return. The return value is pickled
and thus allows for returning any pickleable data.
"""
class Ret(object):
"Helper class for holding returns from exec"
def __init__(self):
self.returns = []
def __call__(self, *args, **kwargs):
self.returns.extend(list(args))
def get_returns(self):
lr = len(self.returns)
if lr == 0:
return ""
elif lr == 1:
return to_pickle(self.returns[0], emptypickle=False) or ""
else:
return to_pickle(self.returns, emptypickle=False) or ""
_return = Ret()
available_vars = {'_return':_return}
if environment:
# load environment
try:
environment = from_pickle(environment)
except Exception:
global _LOGGER
if not _LOGGER:
from src.utils.logger import logger as _LOGGER
_LOGGER.log_trace("Could not find remote object")
available_vars.update(environment)
# try to execute with eval first
try:
ret = eval(source, {}, available_vars)
ret = _return.get_returns() or to_pickle(ret, emptypickle=False) or ""
except Exception:
# use exec instead
exec source in available_vars
ret = _return.get_returns()
# get the list of affected objects to recache
objs = list(set(PROC_MODIFIED_OBJS))
# we need to include the locations too, to update their content caches
objs = objs + list(set([o.location for o in objs if hasattr(o, "location") and o.location]))
#print "objs:", objs
#print "to_pickle", to_pickle(objs, emptypickle=False, do_pickle=False)
to_recache = to_pickle(objs, emptypickle=False) or ""
# empty the list without loosing memory reference
PROC_MODIFIED_OBJS[:] = []
return {'response': ret,
'recached': to_recache}
ExecuteCode.responder(executecode)

View file

@ -26,7 +26,7 @@ from src.scripts.models import ScriptDB
from src.server.models import ServerConfig
from src.server import initial_setup
from src.utils.utils import get_evennia_version, mod_import
from src.utils.utils import get_evennia_version, mod_import, make_iter
from src.comms import channelhandler
from src.server.sessionhandler import SESSIONS
@ -43,8 +43,7 @@ SERVER_RESTART = os.path.join(settings.GAME_DIR, 'server.restart')
SERVER_STARTSTOP_MODULE = mod_import(settings.AT_SERVER_STARTSTOP_MODULE)
# module containing plugin services
SERVER_SERVICES_PLUGIN_MODULE = mod_import(settings.SERVER_SERVICES_PLUGIN_MODULE)
SERVER_SERVICES_PLUGIN_MODULES = [mod_import(module) for module in make_iter(settings.SERVER_SERVICES_PLUGIN_MODULES)]
#------------------------------------------------------------
# Evennia Server settings
@ -57,19 +56,6 @@ AMP_ENABLED = True
AMP_HOST = settings.AMP_HOST
AMP_PORT = settings.AMP_PORT
PROCPOOL_ENABLED = settings.PROCPOOL_ENABLED
PROCPOOL_DEBUG = settings.PROCPOOL_DEBUG
PROCPOOL_MIN_NPROC = settings.PROCPOOL_MIN_NPROC
PROCPOOL_MAX_NPROC = settings.PROCPOOL_MAX_NPROC
PROCPOOL_TIMEOUT = settings.PROCPOOL_TIMEOUT
PROCPOOL_IDLETIME = settings.PROCPOOL_IDLETIME
PROCPOOL_HOST = settings.PROCPOOL_HOST
PROCPOOL_PORT = settings.PROCPOOL_PORT
PROCPOOL_INTERFACE = settings.PROCPOOL_INTERFACE
PROCPOOL_UID = settings.PROCPOOL_UID
PROCPOOL_GID = settings.PROCPOOL_GID
PROCPOOL_DIRECTORY = settings.PROCPOOL_DIRECTORY
# server-channel mappings
IMC2_ENABLED = settings.IMC2_ENABLED
IRC_ENABLED = settings.IRC_ENABLED
@ -227,9 +213,6 @@ class Evennia(object):
"""
print ' %(servername)s Server (%(version)s) started.' % {'servername': SERVERNAME, 'version': VERSION}
print ' amp (to Portal): %s' % AMP_PORT
if PROCPOOL_ENABLED:
print ' amp (Process Pool): %s' % PROCPOOL_PORT
def set_restart_mode(self, mode=None):
"""
@ -341,50 +324,6 @@ if AMP_ENABLED:
amp_service.setName("EvenniaPortal")
EVENNIA.services.addService(amp_service)
# The ampoule twisted extension manages asynchronous process pools
# via an AMP port. It can be used to offload expensive operations
# to another process asynchronously.
if PROCPOOL_ENABLED:
from src.utils.ampoule import main as ampoule_main
from src.utils.ampoule import service as ampoule_service
from src.utils.ampoule import pool as ampoule_pool
from src.utils.ampoule.main import BOOTSTRAP as _BOOTSTRAP
from src.server.procpool import ProcPoolChild
# for some reason absolute paths don't work here, only relative ones.
apackages = ("twisted",
os.path.join(os.pardir, "src", "utils", "ampoule"),
os.path.join(os.pardir, "ev"),
os.path.join(os.pardir))
aenv = {"DJANGO_SETTINGS_MODULE":"settings",
"DATABASE_NAME":settings.DATABASES.get("default", {}).get("NAME") or settings.DATABASE_NAME}
if PROCPOOL_DEBUG:
_BOOTSTRAP = _BOOTSTRAP % "log.startLogging(sys.stderr)"
else:
_BOOTSTRAP = _BOOTSTRAP % ""
procpool_starter = ampoule_main.ProcessStarter(packages=apackages,
env=aenv,
path=PROCPOOL_DIRECTORY,
uid=PROCPOOL_UID,
gid=PROCPOOL_GID,
bootstrap=_BOOTSTRAP,
childReactor=os.name == 'nt' and "select" or "epoll")
procpool = ampoule_pool.ProcessPool(name="ProcPool",
min=PROCPOOL_MIN_NPROC,
max=PROCPOOL_MAX_NPROC,
recycleAfter=500,
ampChild=ProcPoolChild,
starter=procpool_starter)
procpool_service = ampoule_service.AMPouleService(procpool,
ProcPoolChild,
PROCPOOL_PORT,
PROCPOOL_INTERFACE)
procpool_service.setName("ProcPool")
EVENNIA.services.addService(procpool_service)
if IRC_ENABLED:
# IRC channel connections
@ -405,9 +344,9 @@ if RSS_ENABLED:
from src.comms import rss
rss.connect_all()
if SERVER_SERVICES_PLUGIN_MODULE:
for plugin_module in SERVER_SERVICES_PLUGIN_MODULES:
# external plugin protocols
SERVER_SERVICES_PLUGIN_MODULE.start_plugin_services(EVENNIA)
plugin_module.start_plugin_services(EVENNIA)
# clear server startup mode
ServerConfig.objects.conf("server_starting_mode", delete=True)