Reputation: 37
I want Django development server to exit when an error happens. But whenever the server exits, it restarts. I tried to kill the process using os.kill(os.getpid, 9) which does not work. I tried sys.exit, os._exit, and raised errors and none of them works.
Notes: Since the program doesn't only run on Linux, so ideally I wish not to use anything like bash. Also I don't want to disrupt other processes.
Upvotes: 0
Views: 1540
Reputation: 1840
The development server doesn't restart on error, it just handles the exception and goes on, just as production server does. This is the way it's supposed to be – HTTP is stateless, error in one request shouldn't affect the next one.
So you need to be aware that whatever you're doing, you're probably abusing the runserver
, and there's almost surely a better way to do it.
That said:
There are several places on which the server is prevented from exiting on error.
First, Django wraps each request in the django.core.handlers.exception.convert_exception_to_response
function, which catches every Exception
and turns it into a nice response. So to pass through this, you need to raise an error not derived from Exception
, like a KeyboardInterrupt
or a SystemExit
.
Then there's django.core.servers.basehttp.ServerHandler
, which derives from wsgiref.handers.BaseHandler
, which wraps the request in a try: ... except: ...
to catch all exceptions, and, again, turn them into responses. Since you probably still want to get that response, you'll have to modify the handle_error
method to raise an error again after generating an error response.
Unfortunately, there's a lot of coupling there. The ServerHandler
class is hard-coded into django.core.servers.basehttp.WSGIRequestHandler.handle
, which is hard-coded into django.core.server.basehttp.run
, which is hard-coded into django.core.management.commands.runserver
. So you'd either have to copy all of that code (which is basically what Django does, copying some code from wsgiref
) or you're left with monkey-patching.
And then there's still django.core.servers.basehttp.WSGIServer
, which is a subclass of socketserver.BaseServer
. The exception we just re-raised now goes to WSGIServer.handle_error
, which tries to turn it into a response and go on. So you need to override that.
Then it should work, provided you run the server without threads and auto-reloading, by running a custom command derived from runserver, as in:
import sys
from django.core.management.commands import runserver
from django.core.servers.basehttp import ServerHandler, is_broken_pipe_error, WSGIServer
good_handle_error = ServerHandler.handle_error
def bad_handle_error(self):
if not is_broken_pipe_error():
good_handle_error(self)
sys.exit(1)
ServerHandler.handle_error = bad_handle_error
class BadWSGIServer(WSGIServer):
def handle_error(self, request, client_address):
if is_broken_pipe_error():
super().handle_error(self, request, client_address)
else:
sys.exit(1)
class Command(runserver.Command):
server_cls = BadWSGIServer
def handle(self, *args, **options):
options['use_reloader'] = False
options['use_threading'] = False
super().handle(*args, **options)
Upvotes: 1