Merge lp:~vila/bzr/383920-pycurl-hangs into lp:~bzr/bzr/trunk-old

Proposed by Vincent Ladeuil
Status: Merged
Merged at revision: not available
Proposed branch: lp:~vila/bzr/383920-pycurl-hangs
Merge into: lp:~bzr/bzr/trunk-old
Diff against target: 183 lines
To merge this branch: bzr merge lp:~vila/bzr/383920-pycurl-hangs
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
John A Meinel Approve
Review via email: mp+8373@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Vincent Ladeuil (vila) wrote :

The previous approach was not addressing the root cause.

The root cause is still a bit unclear as it involved python gc'ing a socket or not before
leaving pycurl polling it...

Anyway, this fix makes no hazardous assumption about the http protocol ;)

It explicitly shuts down the socket when the thread handling a http[s] connection is about
to finish. In our case, when the connection is closed by the server after sending a 404 error.

That may reduce the number of tests leaking threads, but it doesn't address all cases
(and it's hard to measure precisely anyway until we fix them all).

Revision history for this message
John A Meinel (jameinel) wrote :

You may want to wait for Robert to comment on it, but this seems to conform to everything he requested earlier.

review: Approve
Revision history for this message
Robert Collins (lifeless) wrote :

Thanks for digging deeper.

 review +1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2009-07-06 06:46:30 +0000
3+++ NEWS 2009-07-08 12:35:11 +0000
4@@ -79,6 +79,9 @@
5 transforms.
6 (Craig Hewetson, Martin Pool, #218206)
7
8+* Force socket shutdown in threaded http test servers to avoid client hangs
9+ (pycurl). (Vincent Ladeuil, #383920).
10+
11 * The ``log+`` decorator, useful in debugging or profiling, could cause
12 "AttributeError: 'list' object has no attribute 'next'". This is now
13 fixed. The log decorator no longer shows the elapsed time or transfer
14
15=== modified file 'bzrlib/tests/http_server.py'
16--- bzrlib/tests/http_server.py 2009-03-23 14:59:43 +0000
17+++ bzrlib/tests/http_server.py 2009-07-08 12:35:12 +0000
18@@ -14,6 +14,7 @@
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
22+import BaseHTTPServer
23 import errno
24 import httplib
25 import os
26@@ -382,6 +383,23 @@
27 # lying around.
28 self.daemon_threads = True
29
30+ def process_request_thread(self, request, client_address):
31+ SocketServer.ThreadingTCPServer.process_request_thread(
32+ self, request, client_address)
33+ # Under some circumstances (as in bug #383920), we need to force the
34+ # shutdown as python delays it until gc occur otherwise and the client
35+ # may hang.
36+ try:
37+ # The request process has been completed, the thread is about to
38+ # die, let's shutdown the socket if we can.
39+ request.shutdown(socket.SHUT_RDWR)
40+ except (socket.error, select.error), e:
41+ if e[0] in (errno.EBADF, errno.ENOTCONN):
42+ # Right, the socket is already down
43+ pass
44+ else:
45+ raise
46+
47
48 class HttpServer(transport.Server):
49 """A test server for http transports.
50
51=== modified file 'bzrlib/tests/test_read_bundle.py'
52--- bzrlib/tests/test_read_bundle.py 2009-03-23 14:59:43 +0000
53+++ bzrlib/tests/test_read_bundle.py 2009-07-08 12:35:12 +0000
54@@ -84,70 +84,54 @@
55 class TestReadBundleFromURL(TestTransportImplementation):
56 """Test that read_bundle works properly across multiple transports"""
57
58+ def setUp(self):
59+ super(TestReadBundleFromURL, self).setUp()
60+ self.bundle_name = 'test_bundle'
61+ # read_mergeable_from_url will invoke get_transport which may *not*
62+ # respect self._transport (i.e. returns a transport that is different
63+ # from the one we want to test, so we must inject a correct transport
64+ # into possible_transports first).
65+ self.possible_transports = [self.get_transport(self.bundle_name)]
66+ self._captureVar('BZR_NO_SMART_VFS', None)
67+ wt = self.create_test_bundle()
68+
69+ def read_mergeable_from_url(self, url):
70+ return bzrlib.bundle.read_mergeable_from_url(
71+ url, possible_transports=self.possible_transports)
72+
73 def get_url(self, relpath=''):
74 return bzrlib.urlutils.join(self._server.get_url(), relpath)
75
76 def create_test_bundle(self):
77 out, wt = create_bundle_file(self)
78 if self.get_transport().is_readonly():
79- f = open('test_bundle', 'wb')
80- try:
81- f.write(out.getvalue())
82- finally:
83- f.close()
84+ self.build_tree_contents([(self.bundle_name, out.getvalue())])
85 else:
86- self.get_transport().put_file('test_bundle', out)
87- self.log('Put to: %s', self.get_url('test_bundle'))
88+ self.get_transport().put_file(self.bundle_name, out)
89+ self.log('Put to: %s', self.get_url(self.bundle_name))
90 return wt
91
92 def test_read_mergeable_from_url(self):
93- self._captureVar('BZR_NO_SMART_VFS', None)
94- wt = self.create_test_bundle()
95- if wt is None:
96- return
97- # read_mergeable_from_url will invoke get_transport which may *not*
98- # respect self._transport (i.e. returns a transport that is different
99- # from the one we want to test, so we must inject a correct transport
100- # into possible_transports first.
101- t = self.get_transport('test_bundle')
102- possible_transports = [t]
103- info = bzrlib.bundle.read_mergeable_from_url(
104- unicode(self.get_url('test_bundle')),
105- possible_transports=possible_transports)
106+ info = self.read_mergeable_from_url(
107+ unicode(self.get_url(self.bundle_name)))
108 revision = info.real_revisions[-1]
109 self.assertEqual('commit-1', revision.revision_id)
110
111 def test_read_fail(self):
112 # Trying to read from a directory, or non-bundle file
113 # should fail with NotABundle
114- self._captureVar('BZR_NO_SMART_VFS', None)
115- wt = self.create_test_bundle()
116- if wt is None:
117- return
118-
119- self.assertRaises(errors.NotABundle,
120- bzrlib.bundle.read_mergeable_from_url,
121- self.get_url('tree'))
122- self.assertRaises(errors.NotABundle,
123- bzrlib.bundle.read_mergeable_from_url,
124- self.get_url('tree/a'))
125+ self.assertRaises(errors.NotABundle,
126+ self.read_mergeable_from_url, self.get_url('tree'))
127+ self.assertRaises(errors.NotABundle,
128+ self.read_mergeable_from_url, self.get_url('tree/a'))
129
130 def test_read_mergeable_respects_possible_transports(self):
131- t = self.get_transport('test_bundle')
132- if not isinstance(t, bzrlib.transport.ConnectedTransport):
133+ if not isinstance(self.get_transport(self.bundle_name),
134+ bzrlib.transport.ConnectedTransport):
135 # There is no point testing transport reuse for not connected
136 # transports (the test will fail even).
137- return
138- self._captureVar('BZR_NO_SMART_VFS', None)
139- wt = self.create_test_bundle()
140- if wt is None:
141- return
142- # read_mergeable_from_url will invoke get_transport which may *not*
143- # respect self._transport (i.e. returns a transport that is different
144- # from the one we want to test, so we must inject a correct transport
145- # into possible_transports first.
146- possible_transports = [t]
147- url = unicode(self.get_url('test_bundle'))
148- info = bzrlib.bundle.read_mergeable_from_url(url,
149- possible_transports=possible_transports)
150- self.assertEqual(1, len(possible_transports))
151+ raise tests.TestSkipped(
152+ 'Need a ConnectedTransport to test transport reuse')
153+ url = unicode(self.get_url(self.bundle_name))
154+ info = self.read_mergeable_from_url(url)
155+ self.assertEqual(1, len(self.possible_transports))
156
157=== modified file 'bzrlib/tests/test_transport_implementations.py'
158--- bzrlib/tests/test_transport_implementations.py 2009-04-20 04:19:45 +0000
159+++ bzrlib/tests/test_transport_implementations.py 2009-07-08 12:35:12 +0000
160@@ -203,6 +203,13 @@
161 for content, f in itertools.izip(contents, content_f):
162 self.assertEqual(content, f.read())
163
164+ def test_get_unknown_file(self):
165+ t = self.get_transport()
166+ files = ['a', 'b']
167+ contents = ['contents of a\n',
168+ 'contents of b\n',
169+ ]
170+ self.build_tree(files, transport=t, line_endings='binary')
171 self.assertRaises(NoSuchFile, t.get, 'c')
172 self.assertListRaises(NoSuchFile, t.get_multi, ['a', 'b', 'c'])
173 self.assertListRaises(NoSuchFile, t.get_multi, iter(['a', 'b', 'c']))
174@@ -242,6 +249,9 @@
175 for content, fname in zip(contents, files):
176 self.assertEqual(content, t.get_bytes(fname))
177
178+ def test_get_bytes_unknown_file(self):
179+ t = self.get_transport()
180+
181 self.assertRaises(NoSuchFile, t.get_bytes, 'c')
182
183 def test_get_with_open_write_stream_sees_all_content(self):