Merge lp:~tcole/wsgi-oops/result-iteration-fixes into lp:wsgi-oops

Proposed by Tim Cole
Status: Merged
Approved by: Roman Yepishev
Approved revision: 56
Merged at revision: 52
Proposed branch: lp:~tcole/wsgi-oops/result-iteration-fixes
Merge into: lp:wsgi-oops
Diff against target: 144 lines (+83/-3)
4 files modified
canonical/oops/serializer.py (+8/-1)
canonical/oops/tests/test_wsgi.py (+63/-0)
canonical/oops/tests/testcase.py (+1/-1)
canonical/oops/wsgi.py (+11/-1)
To merge this branch: bzr merge lp:~tcole/wsgi-oops/result-iteration-fixes
Reviewer Review Type Date Requested Status
Roman Yepishev (community) Approve
John O'Brien (community) Approve
Philip Fibiger (community) Approve
Review via email: mp+29307@code.launchpad.net

Commit message

Properly handle closing of the result generator

Description of the change

This gracefully handles the WSGI container closing the result generator. Ordinarily this should only happen when the client drops the connection before we've finished writing all the data to it. This fix prevents oopses in such cases.

To post a comment you must log in.
56. By Tim Cole

drop unused helper function

Revision history for this message
Philip Fibiger (pfibiger) wrote :

looks good.

review: Approve
Revision history for this message
John O'Brien (jdobrien) wrote :

Looks good. Great testing.

review: Approve
Revision history for this message
Roman Yepishev (rye) wrote :

Ran tests, tested that web interface continues working so approving.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'canonical/oops/serializer.py'
2--- canonical/oops/serializer.py 2010-05-06 19:07:16 +0000
3+++ canonical/oops/serializer.py 2010-07-06 15:47:25 +0000
4@@ -56,7 +56,14 @@
5 import zipfile
6
7 from pprint import pformat
8-from canonical.versioninfo import version_info
9+try:
10+ from canonical.versioninfo import version_info
11+except ImportError:
12+ version_info = {
13+ 'branch_nick': 'unknown',
14+ 'revno': '0',
15+ 'date': '1970-01-01'
16+ }
17
18 UTC = pytz.utc
19 EPOCH = datetime(2006, 01, 01)
20
21=== modified file 'canonical/oops/tests/test_wsgi.py'
22--- canonical/oops/tests/test_wsgi.py 2010-01-05 16:23:19 +0000
23+++ canonical/oops/tests/test_wsgi.py 2010-07-06 15:47:25 +0000
24@@ -41,6 +41,68 @@
25 self.assertFalse(self.oops.wanted)
26
27
28+class TestResultIteration(testcase.WSGIOopsTestCase):
29+ """Test iteration on results."""
30+
31+ def test_immediate_start(self):
32+ """Test that we start without waiting for body consumption."""
33+
34+ def dummy_app(environ, start_response):
35+ """Produce a response with infinite garbage."""
36+ start_response("200 OK", {}, exc_info=None)
37+ return ["foobar"]
38+
39+ started = [False]
40+
41+ def dummy_start_response(status, headers, exc_info=None):
42+ """Sets flag when response is started"""
43+ started[0] = True
44+
45+ app = wsgi.OopsWare(dummy_app, self.tmp_dir)
46+ app({}, dummy_start_response)
47+ self.assertTrue(started[0])
48+
49+ def _generate_garbage(self):
50+ """Generate an infinite sequence of garbage until closed."""
51+ while True:
52+ yield "xyzzy"
53+
54+ def test_close_no_oops(self):
55+ """Test that closing the generator doesn't oops"""
56+
57+ def dummy_app(environ, start_response):
58+ """Produce a response with infinite garbage."""
59+ start_response("200 OK", {}, exc_info=None)
60+ return self._generate_garbage()
61+
62+ def dummy_start_response(status, headers, exc_info=None):
63+ """Do nothing."""
64+
65+ interceptor = testcase.OopsWareInterceptor(dummy_app)
66+ app = wsgi.OopsWare(interceptor, self.tmp_dir)
67+ gen = app({}, dummy_start_response)
68+ gen.close()
69+ self.assertFalse(interceptor.oops.wanted)
70+
71+ def test_close_closes_inner_generator(self):
72+ """Test that closing the outer generator closes the inner one."""
73+ garbage = self._generate_garbage()
74+
75+ def dummy_app(environ, start_response):
76+ """Produce a response with the specific garbage generator."""
77+ start_response("200 OK", {}, exc_info=None)
78+ return garbage
79+
80+ def dummy_start_response(status, headers, exc_info=None):
81+ """Do nothing."""
82+
83+ interceptor = testcase.OopsWareInterceptor(dummy_app)
84+ app = wsgi.OopsWare(interceptor, self.tmp_dir)
85+ gen = app({}, dummy_start_response)
86+ gen.close()
87+ self.assertRaises(StopIteration, garbage.next)
88+
89+
90 class TestEnvironWSGIOops(testcase.WSGIOopsTestCase):
91 """Create the oops enviroment for testing"""
92 def create_test_app(self):
93@@ -61,6 +123,7 @@
94 """ test that OOPSID is in the environ """
95 res = self.testapp.get('?ok=all_ok', status=200, expect_errors=False)
96
97+
98 class TestWSGIOopsKey(testcase.WSGIOopsTestCase):
99 """ Check that the OOPSSerializer key can be configured when instantiating
100 an OopsWare.
101
102=== modified file 'canonical/oops/tests/testcase.py'
103--- canonical/oops/tests/testcase.py 2010-03-23 15:49:56 +0000
104+++ canonical/oops/tests/testcase.py 2010-07-06 15:47:25 +0000
105@@ -102,6 +102,6 @@
106 return ["An error was requested"]
107 else:
108 start_response("200 OK", [('Content-Type', 'text/html'), ])
109- return ["Hooary, all OK!"]
110+ return ["Hooray, all OK!"]
111
112
113
114=== modified file 'canonical/oops/wsgi.py'
115--- canonical/oops/wsgi.py 2010-03-23 15:49:56 +0000
116+++ canonical/oops/wsgi.py 2010-07-06 15:47:25 +0000
117@@ -68,6 +68,12 @@
118 key, oops_dir, fh=fh)
119
120 def __call__(self, environ, start_response):
121+ """Call do_request and pump once to start request processing."""
122+ gen = self.do_request(environ, start_response)
123+ gen.next() # first value yielded is meant to be discarded
124+ return gen
125+
126+ def do_request(self, environ, start_response):
127 """
128 Capture exceptions during WSGI application execution and dump
129 oopses as needed.
130@@ -119,9 +125,13 @@
131 metadata["request-start"] = time.time()
132 try:
133 body = self.app(environ, trap_response)
134- # trap exceptions during lazy content generation also
135+ yield "" # thrown away by __call__
136 for chunk in body:
137 yield chunk
138+ except GeneratorExit:
139+ close = getattr(body, 'close', None)
140+ if close:
141+ close()
142 except:
143 oops.keep()
144 exc_info = sys.exc_info()

Subscribers

People subscribed via source and target branches