# HG changeset patch # User Adrien Di Mascio # Date 1450164913 -3600 # Node ID 32a3860c799d87183e88aac5e6e8fbd5dc33428b # Parent b35e21fc1f9bb48a8c42d124ac29cc4f2612193a [twisted] add request error handler to avoid finishing it twice And avoid stack traces like:: Traceback (most recent call last): File "/usr/lib/python2.7/threading.py", line 783, in __bootstrap self.__bootstrap_inner() File "/usr/lib/python2.7/threading.py", line 810, in __bootstrap_inner self.run() File "/usr/lib/python2.7/threading.py", line 763, in run self.__target(*self.__args, **self.__kwargs) --- --- [...] File "/usr/lib/python2.7/dist-packages/twisted/python/threadpool.py", line 196, in _worker result = context.call(ctx, function, *args, **kwargs) File "/usr/lib/python2.7/dist-packages/twisted/python/context.py", line 118, in callWithContext return self.currentContext().callWithContext(ctx, func, *args, **kw) File "/usr/lib/python2.7/dist-packages/twisted/python/context.py", line 81, in callWithContext return func(*args,**kw) File "/home/me/envs/grshell-cw/cubicweb/statsd_logger.py", line 121, in __call__ return self.callable(*args, **kw) File "/home/me/envs/grshell-cw/cubicweb/etwist/server.py", line 131, in render_request code=500, twisted_request=request) File "/home/me/envs/grshell-cw/cubicweb/etwist/http.py", line 22, in __init__ self._finalize() File "/home/me/envs/grshell-cw/cubicweb/etwist/http.py", line 46, in _finalize self._twreq.finish() File "/usr/lib/python2.7/dist-packages/twisted/web/server.py", line 228, in finish return http.Request.finish(self) File "/usr/lib/python2.7/dist-packages/twisted/web/http.py", line 931, in finish "Request.finish called on a request after its connection was lost; " exceptions.RuntimeError: Request.finish called on a request after its connection was lost; use Request.notifyFinish to keep track of this. diff -r b35e21fc1f9b -r 32a3860c799d cubicweb/etwist/http.py --- a/cubicweb/etwist/http.py Fri Sep 04 18:05:51 2015 +0200 +++ b/cubicweb/etwist/http.py Tue Dec 15 08:35:13 2015 +0100 @@ -8,6 +8,7 @@ + class HTTPResponse(object): """An object representing an HTTP Response to be sent to the client. """ @@ -29,9 +30,14 @@ # add content-length if not present if (self._headers_out.getHeader('content-length') is None and self._stream is not None): - self._twreq.setHeader('content-length', len(self._stream)) + self._twreq.setHeader('content-length', len(self._stream)) def _finalize(self): + # cw_failed is set on errors such as "connection aborted by client". In + # such cases, req.finish() was already called and calling it a twice + # would crash + if getattr(self._twreq, 'cw_failed', False): + return # we must set code before writing anything, else it's too late if self._code is not None: self._twreq.setResponseCode(self._code) diff -r b35e21fc1f9b -r 32a3860c799d cubicweb/etwist/server.py --- a/cubicweb/etwist/server.py Fri Sep 04 18:05:51 2015 +0200 +++ b/cubicweb/etwist/server.py Tue Dec 15 08:35:13 2015 +0100 @@ -21,6 +21,7 @@ import traceback import threading from cgi import FieldStorage, parse_header +from functools import partial from cubicweb.statsd_logger import statsd_timeit @@ -89,8 +90,17 @@ """Indicate which resource to use to process down the URL's path""" return self + def on_request_finished_ko(self, request, reason): + # annotate the twisted request so that we're able later to check for + # failure without having to dig into request's internal attributes such + # as _disconnected + request.cw_failed = True + self.warning('request finished abnormally: %s', reason) + def render(self, request): """Render a page from the root resource""" + finish_deferred = request.notifyFinish() + finish_deferred.addErrback(partial(self.on_request_finished_ko, request)) # reload modified files in debug mode if self.config.debugmode: self.config.uiprops.reload_if_needed()