[twisted] add request error handler to avoid finishing it twice
authorAdrien Di Mascio <Adrien.DiMascio@logilab.fr>
Tue, 15 Dec 2015 08:35:13 +0100
changeset 11877 32a3860c799d
parent 11876 b35e21fc1f9b
child 11878 e42cb31e9301
[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) --- <exception caught here> --- [...] 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.
cubicweb/etwist/http.py
cubicweb/etwist/server.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)
--- 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()