diff --git a/evennia/server/server.py b/evennia/server/server.py index 3da09abed0..206fc54909 100644 --- a/evennia/server/server.py +++ b/evennia/server/server.py @@ -16,7 +16,7 @@ import os from twisted.web import static from twisted.application import internet, service from twisted.internet import reactor, defer -from twisted.internet.task import LoopingCall +from twisted.internet.task import LoopingCall, deferLater import django django.setup() @@ -168,10 +168,18 @@ class Evennia(object): # initialize channelhandler channelhandler.CHANNELHANDLER.update() - # set a callback if the server is killed abruptly, - # by Ctrl-C, reboot etc. - reactor.addSystemEventTrigger('before', 'shutdown', - self.shutdown, _reactor_stopping=True) + # wrap the SIGINT handler to make sure we empty the threadpool + # even when we reload and we have long-running requests in queue. + # this is necessary over using Twisted's signal handler. + # (see https://github.com/evennia/evennia/issues/1128) + def _wrap_sigint_handler(*args): + from twisted.internet.defer import Deferred + d = self.web_root.empty_threadpool() + d.addCallback(lambda _: self.shutdown(_reactor_stopping=True)) + d.addCallback(lambda _: reactor.stop()) + reactor.callLater(1, d.callback, None) + reactor.sigInt = _wrap_sigint_handler + self.game_running = True # track the server time @@ -349,9 +357,6 @@ class Evennia(object): from evennia.server.models import ServerConfig from evennia.utils import gametime as _GAMETIME_MODULE - # lock the threadpool from accepting more requests - self.web_root.pool.lock() - if mode == 'reload': # call restart hooks ServerConfig.objects.conf("server_restart_mode", "reload") @@ -387,24 +392,17 @@ class Evennia(object): # always called, also for a reload self.at_server_stop() - # if _reactor_stopping is true, reactor does not need to - # be stopped again. if os.name == 'nt' and os.path.exists(SERVER_PIDFILE): # for Windows we need to remove pid files manually os.remove(SERVER_PIDFILE) + + if WEBSERVER_ENABLED: + yield self.web_root.empty_threadpool() + if not _reactor_stopping: - # this will also send a reactor.stop signal, so we set a - # flag to avoid loops. + # kill the server self.shutdown_complete = True - if WEBSERVER_ENABLED: - # Make sure to not continue until threadpool queue is empty. - deferred = self.web_root.get_pending_requests() - deferred.addCallback(lambda _: reactor.stop()) - from twisted.internet import task - yield task.deferLater(reactor, 1, deferred.callback, None) - else: - # kill the server - reactor.callLater(1, reactor.stop) + reactor.callLater(1, reactor.stop) # we make sure the proper gametime is saved as late as possible ServerConfig.objects.conf("runtime", _GAMETIME_MODULE.runtime()) diff --git a/evennia/server/webserver.py b/evennia/server/webserver.py index d08f113c1d..754c40d6d8 100644 --- a/evennia/server/webserver.py +++ b/evennia/server/webserver.py @@ -149,12 +149,13 @@ class DjangoWebRoot(resource.Resource): pool (ThreadPool): The twisted threadpool. """ - self._pool = pool + self.pool = pool + self._echo_log = True self._pending_requests = {} resource.Resource.__init__(self) self.wsgi_resource = WSGIResource(reactor, pool, WSGIHandler()) - def get_pending_requests(self): + def empty_threadpool(self): """ Converts our _pending_requests list of deferreds into a DeferredList @@ -162,6 +163,11 @@ class DjangoWebRoot(resource.Resource): deflist (DeferredList): Contains all deferreds of pending requests. """ + self.pool.lock() + if self._pending_requests and self._echo_log: + self._echo_log = False # just to avoid multiple echoes + msg = "Webserver waiting for %i requests ... " + logger.log_info(msg % len(self._pending_requests)) return defer.DeferredList(self._pending_requests, consumeErrors=True) def _decrement_requests(self, *args, **kwargs):