summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Merickel <michael@merickel.org>2019-03-28 20:21:37 -0500
committerMichael Merickel <michael@merickel.org>2019-03-28 21:00:39 -0500
commit8eb6507a59621f48462c05fbc5e855748992764b (patch)
tree2faf44e71878239eb9a756bc7fa110e93361e417
parent47942f829edff2b1d608cd2d1ec46b2fbc37955f (diff)
downloadwaitress-error-task-disconnect.tar.gz
handle client disconnections while rendering error responseerror-task-disconnect
-rw-r--r--CHANGES.txt1
-rw-r--r--waitress/channel.py5
-rw-r--r--waitress/tests/test_channel.py27
3 files changed, 32 insertions, 1 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 1291f41..ef08b18 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -8,6 +8,7 @@ Bugfixes
socket due to a client disconnect. This should notify a long-lived streaming
response when a client hangs up.
See https://github.com/Pylons/waitress/pull/238
+ and https://github.com/Pylons/waitress/pull/240
- When a client closes a socket unexpectedly there was potential for memory
leaks in which data was written to the buffers after they were closed,
diff --git a/waitress/channel.py b/waitress/channel.py
index 6b80919..a377a54 100644
--- a/waitress/channel.py
+++ b/waitress/channel.py
@@ -374,7 +374,10 @@ class HTTPChannel(wasyncore.dispatcher, object):
except KeyError:
pass
task = self.error_task_class(self, request)
- task.service() # must not fail
+ try:
+ task.service() # must not fail
+ except ClientDisconnected:
+ task.close_on_finish = True
else:
task.close_on_finish = True
# we cannot allow self.requests to drop to empty til
diff --git a/waitress/tests/test_channel.py b/waitress/tests/test_channel.py
index d550f87..7efd3b3 100644
--- a/waitress/tests/test_channel.py
+++ b/waitress/tests/test_channel.py
@@ -585,6 +585,33 @@ class TestHTTPChannel(unittest.TestCase):
self.assertEqual(inst.error_task_class.serviced, False)
self.assertTrue(request.closed)
+ def test_service_with_request_error_raises_disconnect(self):
+ from waitress.channel import ClientDisconnected
+
+ inst, sock, map = self._makeOneWithMap()
+ inst.adj.expose_tracebacks = False
+ inst.server = DummyServer()
+ request = DummyRequest()
+ err_request = DummyRequest()
+ inst.requests = [request]
+ inst.parser_class = lambda x: err_request
+ inst.task_class = DummyTaskClass(RuntimeError)
+ inst.task_class.wrote_header = False
+ inst.error_task_class = DummyTaskClass(ClientDisconnected)
+ inst.logger = DummyLogger()
+ inst.service()
+ self.assertTrue(request.serviced)
+ self.assertTrue(err_request.serviced)
+ self.assertEqual(inst.requests, [])
+ self.assertEqual(len(inst.logger.exceptions), 1)
+ self.assertEqual(len(inst.logger.warnings), 0)
+ self.assertTrue(inst.force_flush)
+ self.assertTrue(inst.last_activity)
+ self.assertFalse(inst.will_close)
+ self.assertEqual(inst.task_class.serviced, True)
+ self.assertEqual(inst.error_task_class.serviced, True)
+ self.assertTrue(request.closed)
+
def test_cancel_no_requests(self):
inst, sock, map = self._makeOneWithMap()
inst.requests = ()