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
=== modified file 'NEWS'
--- NEWS 2009-07-06 06:46:30 +0000
+++ NEWS 2009-07-08 12:35:11 +0000
@@ -79,6 +79,9 @@
79 transforms.79 transforms.
80 (Craig Hewetson, Martin Pool, #218206)80 (Craig Hewetson, Martin Pool, #218206)
8181
82* Force socket shutdown in threaded http test servers to avoid client hangs
83 (pycurl). (Vincent Ladeuil, #383920).
84
82* The ``log+`` decorator, useful in debugging or profiling, could cause85* The ``log+`` decorator, useful in debugging or profiling, could cause
83 "AttributeError: 'list' object has no attribute 'next'". This is now86 "AttributeError: 'list' object has no attribute 'next'". This is now
84 fixed. The log decorator no longer shows the elapsed time or transfer87 fixed. The log decorator no longer shows the elapsed time or transfer
8588
=== modified file 'bzrlib/tests/http_server.py'
--- bzrlib/tests/http_server.py 2009-03-23 14:59:43 +0000
+++ bzrlib/tests/http_server.py 2009-07-08 12:35:12 +0000
@@ -14,6 +14,7 @@
14# along with this program; if not, write to the Free Software14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1616
17import BaseHTTPServer
17import errno18import errno
18import httplib19import httplib
19import os20import os
@@ -382,6 +383,23 @@
382 # lying around.383 # lying around.
383 self.daemon_threads = True384 self.daemon_threads = True
384385
386 def process_request_thread(self, request, client_address):
387 SocketServer.ThreadingTCPServer.process_request_thread(
388 self, request, client_address)
389 # Under some circumstances (as in bug #383920), we need to force the
390 # shutdown as python delays it until gc occur otherwise and the client
391 # may hang.
392 try:
393 # The request process has been completed, the thread is about to
394 # die, let's shutdown the socket if we can.
395 request.shutdown(socket.SHUT_RDWR)
396 except (socket.error, select.error), e:
397 if e[0] in (errno.EBADF, errno.ENOTCONN):
398 # Right, the socket is already down
399 pass
400 else:
401 raise
402
385403
386class HttpServer(transport.Server):404class HttpServer(transport.Server):
387 """A test server for http transports.405 """A test server for http transports.
388406
=== modified file 'bzrlib/tests/test_read_bundle.py'
--- bzrlib/tests/test_read_bundle.py 2009-03-23 14:59:43 +0000
+++ bzrlib/tests/test_read_bundle.py 2009-07-08 12:35:12 +0000
@@ -84,70 +84,54 @@
84class TestReadBundleFromURL(TestTransportImplementation):84class TestReadBundleFromURL(TestTransportImplementation):
85 """Test that read_bundle works properly across multiple transports"""85 """Test that read_bundle works properly across multiple transports"""
8686
87 def setUp(self):
88 super(TestReadBundleFromURL, self).setUp()
89 self.bundle_name = 'test_bundle'
90 # read_mergeable_from_url will invoke get_transport which may *not*
91 # respect self._transport (i.e. returns a transport that is different
92 # from the one we want to test, so we must inject a correct transport
93 # into possible_transports first).
94 self.possible_transports = [self.get_transport(self.bundle_name)]
95 self._captureVar('BZR_NO_SMART_VFS', None)
96 wt = self.create_test_bundle()
97
98 def read_mergeable_from_url(self, url):
99 return bzrlib.bundle.read_mergeable_from_url(
100 url, possible_transports=self.possible_transports)
101
87 def get_url(self, relpath=''):102 def get_url(self, relpath=''):
88 return bzrlib.urlutils.join(self._server.get_url(), relpath)103 return bzrlib.urlutils.join(self._server.get_url(), relpath)
89104
90 def create_test_bundle(self):105 def create_test_bundle(self):
91 out, wt = create_bundle_file(self)106 out, wt = create_bundle_file(self)
92 if self.get_transport().is_readonly():107 if self.get_transport().is_readonly():
93 f = open('test_bundle', 'wb')108 self.build_tree_contents([(self.bundle_name, out.getvalue())])
94 try:
95 f.write(out.getvalue())
96 finally:
97 f.close()
98 else:109 else:
99 self.get_transport().put_file('test_bundle', out)110 self.get_transport().put_file(self.bundle_name, out)
100 self.log('Put to: %s', self.get_url('test_bundle'))111 self.log('Put to: %s', self.get_url(self.bundle_name))
101 return wt112 return wt
102113
103 def test_read_mergeable_from_url(self):114 def test_read_mergeable_from_url(self):
104 self._captureVar('BZR_NO_SMART_VFS', None)115 info = self.read_mergeable_from_url(
105 wt = self.create_test_bundle()116 unicode(self.get_url(self.bundle_name)))
106 if wt is None:
107 return
108 # read_mergeable_from_url will invoke get_transport which may *not*
109 # respect self._transport (i.e. returns a transport that is different
110 # from the one we want to test, so we must inject a correct transport
111 # into possible_transports first.
112 t = self.get_transport('test_bundle')
113 possible_transports = [t]
114 info = bzrlib.bundle.read_mergeable_from_url(
115 unicode(self.get_url('test_bundle')),
116 possible_transports=possible_transports)
117 revision = info.real_revisions[-1]117 revision = info.real_revisions[-1]
118 self.assertEqual('commit-1', revision.revision_id)118 self.assertEqual('commit-1', revision.revision_id)
119119
120 def test_read_fail(self):120 def test_read_fail(self):
121 # Trying to read from a directory, or non-bundle file121 # Trying to read from a directory, or non-bundle file
122 # should fail with NotABundle122 # should fail with NotABundle
123 self._captureVar('BZR_NO_SMART_VFS', None)123 self.assertRaises(errors.NotABundle,
124 wt = self.create_test_bundle()124 self.read_mergeable_from_url, self.get_url('tree'))
125 if wt is None:125 self.assertRaises(errors.NotABundle,
126 return126 self.read_mergeable_from_url, self.get_url('tree/a'))
127
128 self.assertRaises(errors.NotABundle,
129 bzrlib.bundle.read_mergeable_from_url,
130 self.get_url('tree'))
131 self.assertRaises(errors.NotABundle,
132 bzrlib.bundle.read_mergeable_from_url,
133 self.get_url('tree/a'))
134127
135 def test_read_mergeable_respects_possible_transports(self):128 def test_read_mergeable_respects_possible_transports(self):
136 t = self.get_transport('test_bundle')129 if not isinstance(self.get_transport(self.bundle_name),
137 if not isinstance(t, bzrlib.transport.ConnectedTransport):130 bzrlib.transport.ConnectedTransport):
138 # There is no point testing transport reuse for not connected131 # There is no point testing transport reuse for not connected
139 # transports (the test will fail even).132 # transports (the test will fail even).
140 return133 raise tests.TestSkipped(
141 self._captureVar('BZR_NO_SMART_VFS', None)134 'Need a ConnectedTransport to test transport reuse')
142 wt = self.create_test_bundle()135 url = unicode(self.get_url(self.bundle_name))
143 if wt is None:136 info = self.read_mergeable_from_url(url)
144 return137 self.assertEqual(1, len(self.possible_transports))
145 # read_mergeable_from_url will invoke get_transport which may *not*
146 # respect self._transport (i.e. returns a transport that is different
147 # from the one we want to test, so we must inject a correct transport
148 # into possible_transports first.
149 possible_transports = [t]
150 url = unicode(self.get_url('test_bundle'))
151 info = bzrlib.bundle.read_mergeable_from_url(url,
152 possible_transports=possible_transports)
153 self.assertEqual(1, len(possible_transports))
154138
=== modified file 'bzrlib/tests/test_transport_implementations.py'
--- bzrlib/tests/test_transport_implementations.py 2009-04-20 04:19:45 +0000
+++ bzrlib/tests/test_transport_implementations.py 2009-07-08 12:35:12 +0000
@@ -203,6 +203,13 @@
203 for content, f in itertools.izip(contents, content_f):203 for content, f in itertools.izip(contents, content_f):
204 self.assertEqual(content, f.read())204 self.assertEqual(content, f.read())
205205
206 def test_get_unknown_file(self):
207 t = self.get_transport()
208 files = ['a', 'b']
209 contents = ['contents of a\n',
210 'contents of b\n',
211 ]
212 self.build_tree(files, transport=t, line_endings='binary')
206 self.assertRaises(NoSuchFile, t.get, 'c')213 self.assertRaises(NoSuchFile, t.get, 'c')
207 self.assertListRaises(NoSuchFile, t.get_multi, ['a', 'b', 'c'])214 self.assertListRaises(NoSuchFile, t.get_multi, ['a', 'b', 'c'])
208 self.assertListRaises(NoSuchFile, t.get_multi, iter(['a', 'b', 'c']))215 self.assertListRaises(NoSuchFile, t.get_multi, iter(['a', 'b', 'c']))
@@ -242,6 +249,9 @@
242 for content, fname in zip(contents, files):249 for content, fname in zip(contents, files):
243 self.assertEqual(content, t.get_bytes(fname))250 self.assertEqual(content, t.get_bytes(fname))
244251
252 def test_get_bytes_unknown_file(self):
253 t = self.get_transport()
254
245 self.assertRaises(NoSuchFile, t.get_bytes, 'c')255 self.assertRaises(NoSuchFile, t.get_bytes, 'c')
246256
247 def test_get_with_open_write_stream_sees_all_content(self):257 def test_get_with_open_write_stream_sees_all_content(self):