Merge lp:~salgado/storm/fix-wsgi into lp:storm

Proposed by Guilherme Salgado
Status: Merged
Approved by: Sidnei da Silva
Approved revision: 449
Merged at revision: 448
Proposed branch: lp:~salgado/storm/fix-wsgi
Merge into: lp:storm
Diff against target: 122 lines (+40/-37)
2 files modified
storm/wsgi.py (+16/-9)
tests/wsgi.py (+24/-28)
To merge this branch: bzr merge lp:~salgado/storm/fix-wsgi
Reviewer Review Type Date Requested Status
Sidnei da Silva (community) Approve
Thomas Herve (community) Approve
Review via email: mp+108421@code.launchpad.net

Description of the change

Fix wsgi.make_app() to not make assumptions about how to consume data from
whatever is returned by its inner app() call

We cannot use storm.wsgi.make_app() in U1 because it materializes the
IBodyProducer we return, thus defeating its purpose.

To post a comment you must log in.
Revision history for this message
Thomas Herve (therve) wrote :

Maybe I miss something, but returning a IBodyProducer is not WSGI? WSGI expects an iterable, and that's what's done here.

Revision history for this message
Sidnei da Silva (sidnei) wrote :

Correct. We special-case an IBodyProducer return value in U1's twisted wsgi container, so that when returning large files we let the reactor consume that asynchronously instead of running the iterable in a thread. Yes, it's not pure WSGI per the spec.

The change here though should be innocuous as long as you do not leak threads. We discussed this with Robert and he thinks it's fine enough, although perhaps we could use a weakref for extra safety.

Revision history for this message
Thomas Herve (therve) wrote :

Please fix the comment to mention some you're not really using it in a WSGI app. +1!

review: Approve
Revision history for this message
Sidnei da Silva (sidnei) wrote :

+1 with using a weakref for extra safety.

review: Approve
lp:~salgado/storm/fix-wsgi updated
449. By Guilherme Salgado

Fix the comment in wsgi.make_app() and use a wekreference for timeline_map.timeline

Revision history for this message
Guilherme Salgado (salgado) wrote :

I've changed the comment and am now using a weakref for .timeline. It'd be great if you guys could have another quick look

Revision history for this message
Sidnei da Silva (sidnei) wrote :

Awesome. +1!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'storm/wsgi.py'
2--- storm/wsgi.py 2011-09-19 11:55:50 +0000
3+++ storm/wsgi.py 2012-06-04 14:20:48 +0000
4@@ -21,8 +21,8 @@
5
6 """Glue to wire a storm timeline tracer up to a WSGI app."""
7
8-import functools
9 import threading
10+import weakref
11
12 __all__ = ['make_app']
13
14@@ -48,11 +48,18 @@
15 timeline_map = threading.local()
16 def wrapper(environ, start_response):
17 timeline = environ.get('timeline.timeline')
18- timeline_map.timeline = timeline
19- try:
20- gen = app(environ, start_response)
21- for bytes in gen:
22- yield bytes
23- finally:
24- del timeline_map.timeline
25- return wrapper, functools.partial(getattr, timeline_map, 'timeline', None)
26+ timeline_map.timeline = None
27+ if timeline is not None:
28+ timeline_map.timeline = weakref.ref(timeline)
29+ # We could clean up timeline_map.timeline after we're done with the
30+ # request, but for that we'd have to consume all the data from the
31+ # underlying app and it wouldn't play well with some non-standard
32+ # tricks (e.g. let the reactor consume IBodyProducers asynchronously
33+ # when returning large files) that some people may want to do.
34+ return app(environ, start_response)
35+
36+ def get_timeline():
37+ timeline = getattr(timeline_map, 'timeline', None)
38+ if timeline is not None:
39+ return timeline()
40+ return wrapper, get_timeline
41
42=== modified file 'tests/wsgi.py'
43--- tests/wsgi.py 2011-09-19 11:55:50 +0000
44+++ tests/wsgi.py 2012-06-04 14:20:48 +0000
45@@ -37,41 +37,29 @@
46 def test_find_timeline_set_in_environ(self):
47 # If a timeline object is known, find_timeline finds it:
48 app, find_timeline = make_app(self.stub_app)
49- timeline = object()
50+ timeline = FakeTimeline()
51 self.in_request = lambda:self.assertEqual(timeline, find_timeline())
52 list(app({'timeline.timeline': timeline}, self.stub_start_response))
53- # Having left the request, no timeline is known:
54- self.assertEqual(None, find_timeline())
55
56 def test_find_timeline_set_in_environ_during_generator(self):
57 # If a timeline object is known, find_timeline finds it:
58 app, find_timeline = make_app(self.stub_app)
59- timeline = object()
60+ timeline = FakeTimeline()
61 self.in_generator = lambda:self.assertEqual(timeline, find_timeline())
62 list(app({'timeline.timeline': timeline}, self.stub_start_response))
63- # Having left the request, no timeline is known:
64- self.assertEqual(None, find_timeline())
65-
66- def raiser(self):
67- raise ValueError('foo')
68-
69- def test_find_timeline_exception_in_app_still_gets_cleared(self):
70- self.in_request = self.raiser
71- app, find_timeline = make_app(self.stub_app)
72- timeline = object()
73- self.assertRaises(
74- ValueError, lambda: list(app({'timeline.timeline': timeline},
75- self.stub_start_response)))
76- self.assertEqual(None, find_timeline())
77-
78- def test_find_timeline_exception_in_generator_still_gets_cleared(self):
79- self.in_generator = self.raiser
80- app, find_timeline = make_app(self.stub_app)
81- timeline = object()
82- self.assertRaises(
83- ValueError, lambda: list(app({'timeline.timeline': timeline},
84- self.stub_start_response)))
85- self.assertEqual(None, find_timeline())
86+
87+ def test_timeline_is_replaced_in_subsequent_request(self):
88+ app, find_timeline = make_app(self.stub_app)
89+ timeline = FakeTimeline()
90+ self.in_request = lambda:self.assertEqual(timeline, find_timeline())
91+ list(app({'timeline.timeline': timeline}, self.stub_start_response))
92+
93+ # Having left the request, the timeline is left behind...
94+ self.assertEqual(timeline, find_timeline())
95+ # ... but only until the next request comes through.
96+ timeline2 = FakeTimeline()
97+ self.in_request = lambda:self.assertEqual(timeline2, find_timeline())
98+ list(app({'timeline.timeline': timeline2}, self.stub_start_response))
99
100 def test_lookups_are_threaded(self):
101 # with two threads in a request at once, each only sees their own
102@@ -81,7 +69,7 @@
103 sync = threading.Condition()
104 waiting = []
105 def check_timeline():
106- timeline = object()
107+ timeline = FakeTimeline()
108 def start_response(status, headers):
109 # Block on the condition, so all test threads are in
110 # start_response when the test resumes.
111@@ -115,3 +103,11 @@
112 if errors.qsize():
113 found_timeline, timeline = errors.get(False)
114 self.assertEqual(timeline, found_timeline)
115+
116+
117+class FakeTimeline(object):
118+ """A fake Timeline.
119+
120+ We need this because we can't use plain object instances as they can't be
121+ weakreferenced.
122+ """

Subscribers

People subscribed via source and target branches

to status/vote changes: