[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.
--- 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()