mirror of
https://github.com/evennia/evennia.git
synced 2026-03-26 17:56:32 +01:00
Shifting ProcPool out of src and into a contrib, using the service plugin system.
This commit is contained in:
parent
f677902811
commit
93d95377ce
23 changed files with 363 additions and 390 deletions
|
|
@ -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':
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue