Merge lp:~vila/bzr/leaking-tests-catch-them-all into lp:bzr

Proposed by Vincent Ladeuil
Status: Merged
Approved by: Vincent Ladeuil
Approved revision: no longer in the source branch.
Merged at revision: 5396
Proposed branch: lp:~vila/bzr/leaking-tests-catch-them-all
Merge into: lp:bzr
Prerequisite: lp:~vila/bzr/leaking-tests-sftp-leaks
Diff against target: 363 lines (+109/-15)
14 files modified
bzrlib/smart/server.py (+3/-0)
bzrlib/tests/__init__.py (+9/-0)
bzrlib/tests/per_transport.py (+1/-1)
bzrlib/tests/test_http.py (+27/-1)
bzrlib/tests/test_server.py (+7/-1)
bzrlib/tests/test_smart_transport.py (+15/-11)
bzrlib/transport/__init__.py (+13/-0)
bzrlib/transport/ftp/__init__.py (+5/-0)
bzrlib/transport/gio_transport.py (+6/-0)
bzrlib/transport/http/__init__.py (+5/-0)
bzrlib/transport/http/_pycurl.py (+5/-0)
bzrlib/transport/http/_urllib.py (+5/-0)
bzrlib/transport/remote.py (+3/-1)
bzrlib/transport/sftp.py (+5/-0)
To merge this branch: bzr merge lp:~vila/bzr/leaking-tests-catch-them-all
Reviewer Review Type Date Requested Status
Robert Collins (community) Needs Fixing
Review via email: mp+28336@code.launchpad.net

Commit message

Implement transport.disconnect() and use it to avoid socket leaks.

Description of the change

This patch fixes the *socket* leaks in our tests and is necessary to make the tests pass on windows without triggering a "Can't start new thread" errors.

It implements a disconnect() method on the transport objects and
then arrange to call it for all relevant transports used by the
test suite.

Some more fixes where needed for edge cases where sockets where used but never by proper transport objects (http redirections) but the same principle has been applied: wherever a connection occurred,
a cleanup should be used to disconnect.

There are still some rough edges here (and some in the
pre-requisite threads) but the overall can be landed IMHO and as
such is ready to be reviewed :)

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

=== modified file 'bzrlib/tests/__init__.py'
16 --- bzrlib/tests/__init__.py 2010-06-23 18:37:20 +0000
17 +++ bzrlib/tests/__init__.py 2010-06-23 18:37:22 +0000
18 @@ -2431,6 +2431,15 @@
19
20 def setUp(self):
21 super(TestCaseWithMemoryTransport, self).setUp()
22 + # Ensure that ConnectedTransport doesn't leak sockets
23 + def get_transport_with_cleanup(*args, **kwargs):
24 + t = self._orig_get_transport(*args, **kwargs)
25 + if isinstance(t, _mod_transport.ConnectedTransport):
26 + self.addCleanup(t.disconnect)
27 + return t
28 +
29 + self._orig_get_transport = self.overrideAttr(
30 + _mod_transport, 'get_transport', get_transport_with_cleanup)
31 self._make_test_root()
32 self.addCleanup(os.chdir, os.getcwdu())
33 self.makeAndChdirToTestDir()

This is pretty ugly; it appears to be on the class that defines get_transport, so why not just change self.get_transport directly?

That is:
a) define a self.get_abs_transport which calls self._orig_mod_transport
b) tests for self.get_abs_transport
c) overrideAttr in setUp

Secondly, all the disconnect implementations are the same; why isn't it in a base class? I know this has been a lot of work for you so it can be easy to miss details; I will be looking for such duplicate code blocks in the other threads now - perhaps you should have a re-review first ? :)

I'd like to see this after those things are fixed.

review: Needs Fixing
Revision history for this message
Vincent Ladeuil (vila) wrote :

>>>>> Robert Collins <email address hidden> writes:

    > Review: Needs Fixing
    > === modified file 'bzrlib/tests/__init__.py'
    > 16 --- bzrlib/tests/__init__.py 2010-06-23 18:37:20 +0000
    > 17 +++ bzrlib/tests/__init__.py 2010-06-23 18:37:22 +0000
    > 18 @@ -2431,6 +2431,15 @@
    > 19
    > 20 def setUp(self):
    > 21 super(TestCaseWithMemoryTransport, self).setUp()
    > 22 + # Ensure that ConnectedTransport doesn't leak sockets
    > 23 + def get_transport_with_cleanup(*args, **kwargs):
    > 24 + t = self._orig_get_transport(*args, **kwargs)
    > 25 + if isinstance(t, _mod_transport.ConnectedTransport):
    > 26 + self.addCleanup(t.disconnect)
    > 27 + return t
    > 28 +
    > 29 + self._orig_get_transport = self.overrideAttr(
    > 30 + _mod_transport, 'get_transport', get_transport_with_cleanup)
    > 31 self._make_test_root()
    > 32 self.addCleanup(os.chdir, os.getcwdu())
    > 33 self.makeAndChdirToTestDir()

    > This is pretty ugly; it appears to be on the class that defines
    > get_transport, so why not just change self.get_transport directly?

Ugly but working :)

It helped me track all the tests that wasn't calling
transport.get_transport (for no good reasons !).

Doing that I discovered many small bugs here and there, mostly about
using the right kind of transport (nothing serious as it uncovered no
bugs in the code, only in test coverage which was important to fix
anyway).

A hook called when a transport *really* connect was a suggestion
discussed but not implemented (and would also have addressed the need
but would have missed the opportunity to catch the coverage bugs).

Again, I chose to not overload the submission, can fix it, etc.

    > That is:

    > a) define a self.get_abs_transport which calls self._orig_mod_transport

What means 'abs' here ? abstract ? absolute ? Do you intend to rename
get_transport ?

    > b) tests for self.get_abs_transport

Right, I had a huge test coverage to find any problem here but not at
the right level, bad vila, no sugar ;)

    > c) overrideAttr in setUp

Or a hook.

    > Secondly, all the disconnect implementations are the same; why
    > isn't it in a base class?

No good reason :) Good review :)

    > I know this has been a lot of work for you so it can be easy to
    > miss details; I will be looking for such duplicate code blocks in
    > the other threads now - perhaps you should have a re-review first
    > ? :)

Hehe, right, sorry for that, I've been working too long on that and get
doscouraged several times so I really needed to get the whole lot
submitted as my eyes were bleeding which doesn't help :)

So I *did* re{>n}-review but not enough...

    > I'd like to see this after those things are fixed.

Cool, thanks a lot.

Revision history for this message
Vincent Ladeuil (vila) wrote :

For the record, running this on windows:
  http://babune.ladeuil.net:24842/job/selftest-windows/139

Ran 24267 tests in 4489.002s FAILED (failures=1, errors=6) Recording test results
Finished: FAILURE

So, the full test is running to completion (pfew) and only an handful of failures are left.

Since it's a bit tedious to keep this loom in sync, I'd like to land it and where possible address
missing bits in further submissions (like the hook mentioned in this thread).

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

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 8/25/2010 3:06 AM, Vincent Ladeuil wrote:
> For the record, running this on windows:
> http://babune.ladeuil.net:24842/job/selftest-windows/139
>
> Ran 24267 tests in 4489.002s FAILED (failures=1, errors=6) Recording test results
> Finished: FAILURE
>
> So, the full test is running to completion (pfew) and only an handful of failures are left.
>
> Since it's a bit tedious to keep this loom in sync, I'd like to land it and where possible address
> missing bits in further submissions (like the hook mentioned in this thread).

I'm happy to have cleanups land, but you can't actually land it if there
are failures...

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkx1Ka8ACgkQJdeBCYSNAAMJrwCbBn9b4tbK0ZBdSG4cDBi7ExWS
M68AnjOfxFT2XaLY6RGkiR02XQzKOBtx
=zi2X
-----END PGP SIGNATURE-----

Revision history for this message
Vincent Ladeuil (vila) wrote :

>>>>> John Arbash Meinel <email address hidden> writes:

    > On 8/25/2010 3:06 AM, Vincent Ladeuil wrote:
    >> For the record, running this on windows:
    >> http://babune.ladeuil.net:24842/job/selftest-windows/139
    >>
    >> Ran 24267 tests in 4489.002s FAILED (failures=1, errors=6) Recording test results
    >> Finished: FAILURE
    >>
    >> So, the full test is running to completion (pfew) and only an handful of failures are left.
    >>
    >> Since it's a bit tedious to keep this loom in sync, I'd like to land it and where possible address
    >> missing bits in further submissions (like the hook mentioned in this thread).

    > I'm happy to have cleanups land, but you can't actually land it if there
    > are failures...

Failures on *windows* *only* as opposed to *not even completing the run* :)

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

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 8/25/2010 10:38 AM, Vincent Ladeuil wrote:
>>>>>> John Arbash Meinel <email address hidden> writes:
>
> > On 8/25/2010 3:06 AM, Vincent Ladeuil wrote:
> >> For the record, running this on windows:
> >> http://babune.ladeuil.net:24842/job/selftest-windows/139
> >>
> >> Ran 24267 tests in 4489.002s FAILED (failures=1, errors=6) Recording test results
> >> Finished: FAILURE
> >>
> >> So, the full test is running to completion (pfew) and only an handful of failures are left.
> >>
> >> Since it's a bit tedious to keep this loom in sync, I'd like to land it and where possible address
> >> missing bits in further submissions (like the hook mentioned in this thread).
>
> > I'm happy to have cleanups land, but you can't actually land it if there
> > are failures...
>
> Failures on *windows* *only* as opposed to *not even completing the run* :)
>

Sounds good then.

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkx1OWUACgkQJdeBCYSNAAMR2ACgu3NH1S8IPFdMWvdGjbWhnj+0
XfMAoLlsF1LbLHfFxJBFfCEREeD492Fp
=zfjf
-----END PGP SIGNATURE-----

Revision history for this message
Martin Packman (gz) wrote :

I'm getting over a hundred errors with this branch in my test setup in the form:

    Traceback (most recent call last):
      ...
      File ".\bzrlib\bundle\__init__.py", line 42, in read_mergeable_from_url
        possible_transports=possible_transports)
      File ".\bzrlib\tests\__init__.py", line 2441, in get_transport_with_cleanup
        t = self._orig_get_transport(*args, **kwargs)
    AttributeError: 'TestBoundBranches' object has no attribute '_orig_get_transport'

This is because I'm clearing the TestCase __dict__ in the result's stopTest method, so is this method being run after that? Or on the wrong test instance? Something seems up, at any rate.

On the plus side, the run does now complete without hanging which earlier versions of this branch did not. However, it seems to have taken 83 minutes, rather than ~50 which is what I'm getting currently on trunk.

Revision history for this message
Vincent Ladeuil (vila) wrote :

Somehow I missed this reply...

> I'm getting over a hundred errors with this branch in my test setup in the
> form:
>
> Traceback (most recent call last):
> ...
> File ".\bzrlib\bundle\__init__.py", line 42, in read_mergeable_from_url
> possible_transports=possible_transports)
> File ".\bzrlib\tests\__init__.py", line 2441, in
> get_transport_with_cleanup
> t = self._orig_get_transport(*args, **kwargs)
> AttributeError: 'TestBoundBranches' object has no attribute
> '_orig_get_transport'
>
> This is because I'm clearing the TestCase __dict__ in the result's stopTest
> method, so is this method being run after that? Or on the wrong test instance?
> Something seems up, at any rate.

Given that _orig_get_transport is set just after get_transport_with_cleanup is installed, I fail
to see how this can occur... or is it related to a closure being deleted by your clearing ?

This attribute must be available during test cleanup, may be stopTest is too early to clear the dict ?

> On the plus side, the run does now complete without hanging which earlier
> versions of this branch did not. However, it seems to have taken 83 minutes,
> rather than ~50 which is what I'm getting currently on trunk.

Err, are we comparing apples with oranges here ? I don't have any timing for a complete run myself so
it's hard to measure whether this patch makes it slower. For platforms where I have such timings I
certainly don't see a 66% increase...

Revision history for this message
Vincent Ladeuil (vila) wrote :

> -----BEGIN PGP SIGNED MESSAGE-----
> > > I'm happy to have cleanups land, but you can't actually land it if there
> > > are failures...
> >
> > Failures on *windows* *only* as opposed to *not even completing the run* :)
> >
>
> Sounds good then.

Weird, I never got the corresponding email....

If nobody objects, I'll land then.

Revision history for this message
Martin Packman (gz) wrote :

> Given that _orig_get_transport is set just after get_transport_with_cleanup is
> installed, I fail
> to see how this can occur... or is it related to a closure being deleted by
> your clearing ?

Not obvious what the problem is to me either, I'll try poking around with pdp and see if I can work out what the issue is.

> This attribute must be available during test cleanup, may be stopTest is too
> early to clear the dict ?

Should be fine (bar the problems we already know about with cycles and gc) as it is after all the cleanups run.

> Err, are we comparing apples with oranges here ? I don't have any timing for a
> complete run myself so
> it's hard to measure whether this patch makes it slower. For platforms where I
> have such timings I
> certainly don't see a 66% increase...

I believe it to be a fair comparison, as I don't think dev is that far removed from this branch, and unlike you I do get a clean full run (with the test case __dict__ clearing added). I don't have a direct cause for you though, will run a diff between the results and see if any timing changes are obvious.

Revision history for this message
Martin Packman (gz) wrote :

> Not obvious what the problem is to me either, I'll try poking around with pdp
> and see if I can work out what the issue is.

Okay, I don't completely understand the why, but this is easy to fix. Don't store the real get_transport method on the test instance and close over self, just close over the real method.

=== modified file 'bzrlib/tests/__init__.py'
--- bzrlib/tests/__init__.py 2010-08-24 13:03:18 +0000
+++ bzrlib/tests/__init__.py 2010-08-29 16:32:25 +0000
@@ -2437,14 +2437,15 @@
     def setUp(self):
         super(TestCaseWithMemoryTransport, self).setUp()
         # Ensure that ConnectedTransport doesn't leak sockets
+ _orig_get_transport = _mod_transport.get_transport
         def get_transport_with_cleanup(*args, **kwargs):
- t = self._orig_get_transport(*args, **kwargs)
+ t = _orig_get_transport(*args, **kwargs)
             if isinstance(t, _mod_transport.ConnectedTransport):
                 self.addCleanup(t.disconnect)
             return t

- self._orig_get_transport = self.overrideAttr(
- _mod_transport, 'get_transport', get_transport_with_cleanup)
+ self.overrideAttr(_mod_transport, 'get_transport',
+ get_transport_with_cleanup)
         self._make_test_root()
         self.addCleanup(os.chdir, os.getcwdu())
         self.makeAndChdirToTestDir()

> > Err, are we comparing apples with oranges here ? I don't have any timing for
> a
> > complete run myself so
> > it's hard to measure whether this patch makes it slower. For platforms where
> I
> > have such timings I
> > certainly don't see a 66% increase...
>
> I believe it to be a fair comparison, as I don't think dev is that far removed
> from this branch, and unlike you I do get a clean full run (with the test case
> __dict__ clearing added). I don't have a direct cause for you though, will run
> a diff between the results and see if any timing changes are obvious.

Looking at that, there are a number of differences along the lines of:

bt.test_remote.TestStacking.test_unstacked_get_parent_map
  Was: ok 882ms
    Test leaked threads, current number of live threads: 1662
  Now: ok 40898ms
    thread ('127.0.0.1', 2122) -> ('127.0.0.1', 2113) hung
    thread ('127.0.0.1', 2121) -> ('127.0.0.1', 2113) hung
    thread ('127.0.0.1', 2120) -> ('127.0.0.1', 2113) hung
    thread ('127.0.0.1', 2119) -> ('127.0.0.1', 2113) hung
    thread ('127.0.0.1', 2118) -> ('127.0.0.1', 2113) hung
    thread ('127.0.0.1', 2117) -> ('127.0.0.1', 2113) hung
    thread ('127.0.0.1', 2116) -> ('127.0.0.1', 2113) hung
    thread ('127.0.0.1', 2115) -> ('127.0.0.1', 2113) hung

    Test leaked threads, current number of live threads: 256

So, you've done a great job reducing the number of leaking threads, but a subset of them have turned into hang timeouts at 5 seconds apiece. There are also tests that used to leak a thread and now don't and are 10x faster, so fixing these other ones should result in an overall speed up.

Revision history for this message
Vincent Ladeuil (vila) wrote :

> Okay, I don't completely understand the why, but this is easy to fix. Don't
> store the real get_transport method on the test instance and close over self,
> just close over the real method.

Ok, I'll fix that, but (mentioning it here instead of IRC to have a track record ;),
avoiding cycles shouldn't require us to avoid closures. Breaking the cycle during
cleanup should be enough.

> Looking at that, there are a number of differences along the lines of:
>
> bt.test_remote.TestStacking.test_unstacked_get_parent_map
> Was: ok 882ms
> Test leaked threads, current number of live threads: 1662
> Now: ok 40898ms
> thread ('127.0.0.1', 2122) -> ('127.0.0.1', 2113) hung
> thread ('127.0.0.1', 2121) -> ('127.0.0.1', 2113) hung
> thread ('127.0.0.1', 2120) -> ('127.0.0.1', 2113) hung
> thread ('127.0.0.1', 2119) -> ('127.0.0.1', 2113) hung
> thread ('127.0.0.1', 2118) -> ('127.0.0.1', 2113) hung
> thread ('127.0.0.1', 2117) -> ('127.0.0.1', 2113) hung
> thread ('127.0.0.1', 2116) -> ('127.0.0.1', 2113) hung
> thread ('127.0.0.1', 2115) -> ('127.0.0.1', 2113) hung
>
> Test leaked threads, current number of live threads: 256
>
> So, you've done a great job reducing the number of leaking threads, but a
> subset of them have turned into hang timeouts at 5 seconds apiece. There are
> also tests that used to leak a thread and now don't and are 10x faster, so
> fixing these other ones should result in an overall speed up.

Right, so, this only occurs on windows. As mentioned in the comment above the line
that 'print.*hung' the real fix is to kill the thread and I didn't want to venture into that
on top of all the other fixes.
I don't know why this happens on windows only, but the symptom is that both the client
and the server are waiting for some change on the underlying socket... and this doesn't happen
on any other platform.

Now, the 5s timeout may be too high but I think I kept it that way to allow some minimal
time to poke under pdb... note that I don't see the same hung tests than you (from memory
I see far less <100, compared to >x1000 leaking tests).

And yes, the overall test suite is faster because there is less sockets to care about mainly,
the speed difference vary greatly from platform to platform though...

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

Vincent, why not join that thread? killing it seems... suboptimal.

Revision history for this message
Vincent Ladeuil (vila) wrote :

@Robert: We time out trying to join it :)
Both the client and the server are hanging for an unknown reason on windows *only*.

Revision history for this message
Vincent Ladeuil (vila) wrote :

> @Robert: We time out trying to join it :)
> Both the client and the server are hanging for an unknown reason on windows
> *only*.
And when I say 'unknown' I should really say "despite having spent a significant
amount of time trying all the tricks I could think of to reveal why they were
or trying to *avoid* the problem".

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

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 8/30/2010 2:28 AM, Robert Collins wrote:
> Vincent, why not join that thread? killing it seems... suboptimal.

I believe the point is that the thread is hung while we are trying to
join to it.

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkx74eAACgkQJdeBCYSNAAMboQCeK5zx1lUzMLyR+6/opuHucc43
FiAAnAmhwjETDnjOVjHl4V7N8UoV2Tod
=Sh1w
-----END PGP SIGNATURE-----

Revision history for this message
Martin Packman (gz) wrote :

> Ok, I'll fix that, but (mentioning it here instead of IRC to have a track record ;),
> avoiding cycles shouldn't require us to avoid closures. Breaking the cycle during
> cleanup should be enough.

Thanks vila, that worked. But... testing on r5396 seems to show it's turned into a different flavour of orange:

bt.per_repository_reference.test_default_stacking.TestDefaultStackingPolicy.test_sprout_to_smart_server_stacking_policy_handling(RepositoryFormatKnitPack6RichRoot)
    Traceback (most recent call last):
      File ".\bzrlib\tests\per_repository_reference\test_default_stacking.py", line 30, in test_sprout_to_smart_server_stacking_policy_handling
        target = source.bzrdir.sprout(url).open_branch()
      File ".\bzrlib\controldir.py", line 367, in sprout
        target_transport = get_transport(url, possible_transports)
      File ".\bzrlib\tests\__init__.py", line 2443, in get_transport_with_cleanup
        self.addCleanup(t.disconnect)
      File ".\bzrlib\tests\__init__.py", line 1494, in addCleanup
        self._cleanups.append((callable, args, kwargs))
    AttributeError: 'TestAncestry' object has no attribute '_cleanups'

Almost all of the errors are on parameterised tests so I'm wondering if something like bug 625574 is involved here.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bzrlib/smart/server.py'
--- bzrlib/smart/server.py 2010-08-30 07:26:44 +0000
+++ bzrlib/smart/server.py 2010-08-30 07:26:46 +0000
@@ -190,6 +190,9 @@
190 thread_name = 'smart-server-child' + thread_name_suffix190 thread_name = 'smart-server-child' + thread_name_suffix
191 connection_thread = threading.Thread(191 connection_thread = threading.Thread(
192 None, handler.serve, name=thread_name)192 None, handler.serve, name=thread_name)
193 # FIXME: This thread is never joined, it should at least be collected
194 # somewhere so that tests that want to check for leaked threads can get
195 # rid of them -- vila 20100531
193 connection_thread.setDaemon(True)196 connection_thread.setDaemon(True)
194 connection_thread.start()197 connection_thread.start()
195 return connection_thread198 return connection_thread
196199
=== modified file 'bzrlib/tests/__init__.py'
--- bzrlib/tests/__init__.py 2010-08-30 07:26:44 +0000
+++ bzrlib/tests/__init__.py 2010-08-30 07:26:46 +0000
@@ -2436,6 +2436,15 @@
24362436
2437 def setUp(self):2437 def setUp(self):
2438 super(TestCaseWithMemoryTransport, self).setUp()2438 super(TestCaseWithMemoryTransport, self).setUp()
2439 # Ensure that ConnectedTransport doesn't leak sockets
2440 def get_transport_with_cleanup(*args, **kwargs):
2441 t = orig_get_transport(*args, **kwargs)
2442 if isinstance(t, _mod_transport.ConnectedTransport):
2443 self.addCleanup(t.disconnect)
2444 return t
2445
2446 orig_get_transport = self.overrideAttr(_mod_transport, 'get_transport',
2447 get_transport_with_cleanup)
2439 self._make_test_root()2448 self._make_test_root()
2440 self.addCleanup(os.chdir, os.getcwdu())2449 self.addCleanup(os.chdir, os.getcwdu())
2441 self.makeAndChdirToTestDir()2450 self.makeAndChdirToTestDir()
24422451
=== modified file 'bzrlib/tests/per_transport.py'
--- bzrlib/tests/per_transport.py 2010-08-30 07:26:44 +0000
+++ bzrlib/tests/per_transport.py 2010-08-30 07:26:46 +0000
@@ -1265,7 +1265,7 @@
1265 self.assertIs(t._get_connection(), c._get_connection())1265 self.assertIs(t._get_connection(), c._get_connection())
12661266
1267 # Temporary failure, we need to create a new dummy connection1267 # Temporary failure, we need to create a new dummy connection
1268 new_connection = object()1268 new_connection = None
1269 t._set_connection(new_connection)1269 t._set_connection(new_connection)
1270 # Check that both transports use the same connection1270 # Check that both transports use the same connection
1271 self.assertIs(new_connection, t._get_connection())1271 self.assertIs(new_connection, t._get_connection())
12721272
=== modified file 'bzrlib/tests/test_http.py'
--- bzrlib/tests/test_http.py 2010-08-30 07:26:44 +0000
+++ bzrlib/tests/test_http.py 2010-08-30 07:26:46 +0000
@@ -1314,6 +1314,27 @@
1314 test.overrideAttr(_urllib2_wrappers, 'Request', RedirectedRequest)1314 test.overrideAttr(_urllib2_wrappers, 'Request', RedirectedRequest)
13151315
13161316
1317def cleanup_http_redirection_connections(test):
1318 # Some sockets are opened but never seen by _urllib, so we trap them at
1319 # the _urllib2_wrappers level to be able to clean them up.
1320 def socket_disconnect(sock):
1321 try:
1322 sock.shutdown(socket.SHUT_RDWR)
1323 sock.close()
1324 except socket.error:
1325 pass
1326 def connect(connection):
1327 test.http_connect_orig(connection)
1328 test.addCleanup(socket_disconnect, connection.sock)
1329 test.http_connect_orig = test.overrideAttr(
1330 _urllib2_wrappers.HTTPConnection, 'connect', connect)
1331 def connect(connection):
1332 test.https_connect_orig(connection)
1333 test.addCleanup(socket_disconnect, connection.sock)
1334 test.https_connect_orig = test.overrideAttr(
1335 _urllib2_wrappers.HTTPSConnection, 'connect', connect)
1336
1337
1317class TestHTTPSilentRedirections(http_utils.TestCaseWithRedirectedWebserver):1338class TestHTTPSilentRedirections(http_utils.TestCaseWithRedirectedWebserver):
1318 """Test redirections.1339 """Test redirections.
13191340
@@ -1335,6 +1356,7 @@
1335 "pycurl doesn't redirect silently anymore")1356 "pycurl doesn't redirect silently anymore")
1336 super(TestHTTPSilentRedirections, self).setUp()1357 super(TestHTTPSilentRedirections, self).setUp()
1337 install_redirected_request(self)1358 install_redirected_request(self)
1359 cleanup_http_redirection_connections(self)
1338 self.build_tree_contents([('a','a'),1360 self.build_tree_contents([('a','a'),
1339 ('1/',),1361 ('1/',),
1340 ('1/a', 'redirected once'),1362 ('1/a', 'redirected once'),
@@ -1380,6 +1402,7 @@
1380 def setUp(self):1402 def setUp(self):
1381 super(TestDoCatchRedirections, self).setUp()1403 super(TestDoCatchRedirections, self).setUp()
1382 self.build_tree_contents([('a', '0123456789'),],)1404 self.build_tree_contents([('a', '0123456789'),],)
1405 cleanup_http_redirection_connections(self)
13831406
1384 self.old_transport = self.get_old_transport()1407 self.old_transport = self.get_old_transport()
13851408
@@ -1710,6 +1733,7 @@
1710 branch = self.make_branch('relpath')1733 branch = self.make_branch('relpath')
1711 url = self.http_server.get_url() + 'relpath'1734 url = self.http_server.get_url() + 'relpath'
1712 bd = bzrdir.BzrDir.open(url)1735 bd = bzrdir.BzrDir.open(url)
1736 self.addCleanup(bd.transport.disconnect)
1713 self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)1737 self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
17141738
1715 def test_bulk_data(self):1739 def test_bulk_data(self):
@@ -2081,6 +2105,7 @@
2081 ('(.*)', r'%s/1\1' % (new_prefix), 301),]2105 ('(.*)', r'%s/1\1' % (new_prefix), 301),]
2082 self.old_transport = self.get_old_transport()2106 self.old_transport = self.get_old_transport()
2083 self.new_server.add_user('joe', 'foo')2107 self.new_server.add_user('joe', 'foo')
2108 cleanup_http_redirection_connections(self)
20842109
2085 def create_transport_readonly_server(self):2110 def create_transport_readonly_server(self):
2086 server = self._auth_server(protocol_version=self._protocol_version)2111 server = self._auth_server(protocol_version=self._protocol_version)
@@ -2096,6 +2121,7 @@
2096 def redirected(t, exception, redirection_notice):2121 def redirected(t, exception, redirection_notice):
2097 self.redirections += 12122 self.redirections += 1
2098 redirected_t = t._redirected_to(exception.source, exception.target)2123 redirected_t = t._redirected_to(exception.source, exception.target)
2124 self.addCleanup(redirected_t.disconnect)
2099 return redirected_t2125 return redirected_t
21002126
2101 stdout = tests.StringIOWrapper()2127 stdout = tests.StringIOWrapper()
@@ -2123,7 +2149,7 @@
2123 self.new_server.port)2149 self.new_server.port)
2124 self.old_server.redirections = [2150 self.old_server.redirections = [
2125 ('(.*)', r'%s/1\1' % (new_prefix), 301),]2151 ('(.*)', r'%s/1\1' % (new_prefix), 301),]
2126 self.assertEqual('redirected once',t._perform(req).read())2152 self.assertEqual('redirected once', t._perform(req).read())
2127 # stdin should be empty2153 # stdin should be empty
2128 self.assertEqual('', ui.ui_factory.stdin.readline())2154 self.assertEqual('', ui.ui_factory.stdin.readline())
2129 # stdout should be empty, stderr will contains the prompts2155 # stdout should be empty, stderr will contains the prompts
21302156
=== modified file 'bzrlib/tests/test_server.py'
--- bzrlib/tests/test_server.py 2010-08-30 07:26:44 +0000
+++ bzrlib/tests/test_server.py 2010-08-30 07:26:46 +0000
@@ -335,7 +335,13 @@
335 # The timeout expired without joining the thread, the thread is335 # The timeout expired without joining the thread, the thread is
336 # therefore stucked and that's a failure as far as the test is336 # therefore stucked and that's a failure as far as the test is
337 # concerned. We used to hang here.337 # concerned. We used to hang here.
338 raise AssertionError('thread %s hung' % (self.name,))338
339 # FIXME: we need to kill the thread, but as far as the test is
340 # concerned, raising an assertion is too strong. On most of the
341 # platforms, this doesn't occur, so just mentioning the problem is
342 # enough for now -- vila 2010824
343 sys.stderr.write('thread %s hung\n' % (self.name,))
344 #raise AssertionError('thread %s hung' % (self.name,))
339345
340 def pending_exception(self):346 def pending_exception(self):
341 """Raise the caught exception.347 """Raise the caught exception.
342348
=== modified file 'bzrlib/tests/test_smart_transport.py'
--- bzrlib/tests/test_smart_transport.py 2010-08-30 07:26:44 +0000
+++ bzrlib/tests/test_smart_transport.py 2010-08-30 07:26:46 +0000
@@ -1026,37 +1026,41 @@
1026 self.server.start_server('127.0.0.1', 0)1026 self.server.start_server('127.0.0.1', 0)
1027 self.server.start_background_thread('-' + self.id())1027 self.server.start_background_thread('-' + self.id())
1028 self.transport = remote.RemoteTCPTransport(self.server.get_url())1028 self.transport = remote.RemoteTCPTransport(self.server.get_url())
1029 self.addCleanup(self.tearDownServer)1029 self.addCleanup(self.stop_server)
1030 self.permit_url(self.server.get_url())1030 self.permit_url(self.server.get_url())
10311031
1032 def tearDownServer(self):1032 def stop_server(self):
1033 """Disconnect the client and stop the server.
1034
1035 This must be re-entrant as some tests will call it explicitly in
1036 addition to the normal cleanup.
1037 """
1033 if getattr(self, 'transport', None):1038 if getattr(self, 'transport', None):
1034 self.transport.disconnect()1039 self.transport.disconnect()
1035 del self.transport1040 del self.transport
1036 if getattr(self, 'server', None):1041 if getattr(self, 'server', None):
1037 self.server.stop_background_thread()1042 self.server.stop_background_thread()
1038 # XXX: why not .stop_server() -- mbp 20100106
1039 del self.server1043 del self.server
10401044
10411045
1042class TestServerSocketUsage(SmartTCPTests):1046class TestServerSocketUsage(SmartTCPTests):
10431047
1044 def test_server_setup_teardown(self):1048 def test_server_start_stop(self):
1045 """It should be safe to teardown the server with no requests."""1049 """It should be safe to stop the server with no requests."""
1046 self.start_server()1050 self.start_server()
1047 t = remote.RemoteTCPTransport(self.server.get_url())1051 t = remote.RemoteTCPTransport(self.server.get_url())
1048 self.tearDownServer()1052 self.stop_server()
1049 self.assertRaises(errors.ConnectionError, t.has, '.')1053 self.assertRaises(errors.ConnectionError, t.has, '.')
10501054
1051 def test_server_closes_listening_sock_on_shutdown_after_request(self):1055 def test_server_closes_listening_sock_on_shutdown_after_request(self):
1052 """The server should close its listening socket when it's stopped."""1056 """The server should close its listening socket when it's stopped."""
1053 self.start_server()1057 self.start_server()
1054 server = self.server1058 server_url = self.server.get_url()
1055 self.transport.has('.')1059 self.transport.has('.')
1056 self.tearDownServer()1060 self.stop_server()
1057 # if the listening socket has closed, we should get a BADFD error1061 # if the listening socket has closed, we should get a BADFD error
1058 # when connecting, rather than a hang.1062 # when connecting, rather than a hang.
1059 t = remote.RemoteTCPTransport(server.get_url())1063 t = remote.RemoteTCPTransport(server_url)
1060 self.assertRaises(errors.ConnectionError, t.has, '.')1064 self.assertRaises(errors.ConnectionError, t.has, '.')
10611065
10621066
@@ -1198,7 +1202,7 @@
1198 self.transport.has('.')1202 self.transport.has('.')
1199 self.assertEqual([], self.hook_calls)1203 self.assertEqual([], self.hook_calls)
1200 # clean up the server1204 # clean up the server
1201 self.tearDownServer()1205 self.stop_server()
1202 # now it should have fired.1206 # now it should have fired.
1203 self.assertEqual(result, self.hook_calls)1207 self.assertEqual(result, self.hook_calls)
12041208
@@ -1217,7 +1221,7 @@
1217 self.transport.has('.')1221 self.transport.has('.')
1218 self.assertEqual([], self.hook_calls)1222 self.assertEqual([], self.hook_calls)
1219 # clean up the server1223 # clean up the server
1220 self.tearDownServer()1224 self.stop_server()
1221 # now it should have fired.1225 # now it should have fired.
1222 self.assertEqual(result, self.hook_calls)1226 self.assertEqual(result, self.hook_calls)
12231227
12241228
=== modified file 'bzrlib/transport/__init__.py'
--- bzrlib/transport/__init__.py 2010-06-02 01:45:31 +0000
+++ bzrlib/transport/__init__.py 2010-08-30 07:26:46 +0000
@@ -1286,6 +1286,12 @@
1286 # should be asked to ConnectedTransport only.1286 # should be asked to ConnectedTransport only.
1287 return None1287 return None
12881288
1289 def disconnect(self):
1290 # This is really needed for ConnectedTransport only, but it's easier to
1291 # have Transport do nothing than testing that the disconnect should be
1292 # asked to ConnectedTransport only.
1293 pass
1294
1289 def _redirected_to(self, source, target):1295 def _redirected_to(self, source, target):
1290 """Returns a transport suitable to re-issue a redirected request.1296 """Returns a transport suitable to re-issue a redirected request.
12911297
@@ -1550,6 +1556,13 @@
1550 transport = self.__class__(other_base, _from_transport=self)1556 transport = self.__class__(other_base, _from_transport=self)
1551 return transport1557 return transport
15521558
1559 def disconnect(self):
1560 """Disconnect the transport.
1561
1562 If and when required the transport willl reconnect automatically.
1563 """
1564 raise NotImplementedError(self.disconnect)
1565
15531566
1554def get_transport(base, possible_transports=None):1567def get_transport(base, possible_transports=None):
1555 """Open a transport to access a URL or directory.1568 """Open a transport to access a URL or directory.
15561569
=== modified file 'bzrlib/transport/ftp/__init__.py'
--- bzrlib/transport/ftp/__init__.py 2010-08-25 10:21:17 +0000
+++ bzrlib/transport/ftp/__init__.py 2010-08-30 07:26:46 +0000
@@ -170,6 +170,11 @@
170 connection, credentials = self._create_connection(credentials)170 connection, credentials = self._create_connection(credentials)
171 self._set_connection(connection, credentials)171 self._set_connection(connection, credentials)
172172
173 def disconnect(self):
174 connection = self._get_connection()
175 if connection is not None:
176 connection.close()
177
173 def _translate_ftp_error(self, err, path, extra=None,178 def _translate_ftp_error(self, err, path, extra=None,
174 unknown_exc=FtpPathError):179 unknown_exc=FtpPathError):
175 """Try to translate an ftplib exception to a bzrlib exception.180 """Try to translate an ftplib exception to a bzrlib exception.
176181
=== modified file 'bzrlib/transport/gio_transport.py'
--- bzrlib/transport/gio_transport.py 2010-08-25 10:21:17 +0000
+++ bzrlib/transport/gio_transport.py 2010-08-30 07:26:46 +0000
@@ -241,7 +241,13 @@
241 " %s" % str(e), orig_error=e)241 " %s" % str(e), orig_error=e)
242 return connection, (user, password)242 return connection, (user, password)
243243
244 def disconnect(self):
245 # FIXME: Nothing seems to be necessary here, which sounds a bit strange
246 # -- vila 20100601
247 pass
248
244 def _reconnect(self):249 def _reconnect(self):
250 # FIXME: This doesn't seem to be used -- vila 20100601
245 """Create a new connection with the previously used credentials"""251 """Create a new connection with the previously used credentials"""
246 credentials = self._get_credentials()252 credentials = self._get_credentials()
247 connection, credentials = self._create_connection(credentials)253 connection, credentials = self._create_connection(credentials)
248254
=== modified file 'bzrlib/transport/http/__init__.py'
--- bzrlib/transport/http/__init__.py 2010-02-17 17:11:16 +0000
+++ bzrlib/transport/http/__init__.py 2010-08-30 07:26:46 +0000
@@ -629,6 +629,11 @@
629 """629 """
630 pass630 pass
631631
632 def disconnect(self):
633 """See SmartClientMedium.disconnect()."""
634 t = self._http_transport_ref()
635 t.disconnect()
636
632637
633# TODO: May be better located in smart/medium.py with the other638# TODO: May be better located in smart/medium.py with the other
634# SmartMediumRequest classes639# SmartMediumRequest classes
635640
=== modified file 'bzrlib/transport/http/_pycurl.py'
--- bzrlib/transport/http/_pycurl.py 2010-08-30 07:26:44 +0000
+++ bzrlib/transport/http/_pycurl.py 2010-08-30 07:26:46 +0000
@@ -128,6 +128,11 @@
128 self._set_connection(connection, auth)128 self._set_connection(connection, auth)
129 return connection129 return connection
130130
131 def disconnect(self):
132 connection = self._get_connection()
133 if connection is not None:
134 connection.close()
135
131 def has(self, relpath):136 def has(self, relpath):
132 """See Transport.has()"""137 """See Transport.has()"""
133 # We set NO BODY=0 in _get_full, so it should be safe138 # We set NO BODY=0 in _get_full, so it should be safe
134139
=== modified file 'bzrlib/transport/http/_urllib.py'
--- bzrlib/transport/http/_urllib.py 2010-02-17 17:11:16 +0000
+++ bzrlib/transport/http/_urllib.py 2010-08-30 07:26:46 +0000
@@ -99,6 +99,11 @@
9999
100 return response100 return response
101101
102 def disconnect(self):
103 connection = self._get_connection()
104 if connection is not None:
105 connection.close()
106
102 def _get(self, relpath, offsets, tail_amount=0):107 def _get(self, relpath, offsets, tail_amount=0):
103 """See HttpTransport._get"""108 """See HttpTransport._get"""
104 abspath = self._remote_path(relpath)109 abspath = self._remote_path(relpath)
105110
=== modified file 'bzrlib/transport/remote.py'
--- bzrlib/transport/remote.py 2010-06-17 09:23:19 +0000
+++ bzrlib/transport/remote.py 2010-08-30 07:26:46 +0000
@@ -435,7 +435,9 @@
435 remote._translate_error(err, path=relpath)435 remote._translate_error(err, path=relpath)
436436
437 def disconnect(self):437 def disconnect(self):
438 self.get_smart_medium().disconnect()438 m = self.get_smart_medium()
439 if m is not None:
440 m.disconnect()
439441
440 def stat(self, relpath):442 def stat(self, relpath):
441 resp = self._call2('stat', self._remote_path(relpath))443 resp = self._call2('stat', self._remote_path(relpath))
442444
=== modified file 'bzrlib/transport/sftp.py'
--- bzrlib/transport/sftp.py 2010-07-19 10:55:15 +0000
+++ bzrlib/transport/sftp.py 2010-08-30 07:26:46 +0000
@@ -389,6 +389,11 @@
389 self._host, self._port)389 self._host, self._port)
390 return connection, (user, password)390 return connection, (user, password)
391391
392 def disconnect(self):
393 connection = self._get_connection()
394 if connection is not None:
395 connection.close()
396
392 def _get_sftp(self):397 def _get_sftp(self):
393 """Ensures that a connection is established"""398 """Ensures that a connection is established"""
394 connection = self._get_connection()399 connection = self._get_connection()