Merge lp:~allenap/maas/no-requests-for-simplestreams into lp:~maas-committers/maas/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 3997
Proposed branch: lp:~allenap/maas/no-requests-for-simplestreams
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 47 lines (+23/-0)
1 file modified
src/provisioningserver/plugin.py (+23/-0)
To merge this branch: bzr merge lp:~allenap/maas/no-requests-for-simplestreams
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+261235@code.launchpad.net

Commit message

Experimentally force simplestreams to use `urllib2` instead of `requests`.

The latter appears to be allowing simplestreams to leak file-descriptors; streaming requests that are not explicitly closed remain referenced by a connection pool and thus not garbage-collected.

To post a comment you must log in.
Revision history for this message
Raphaël Badin (rvb) wrote :

Looks good, but let's get proof that this solves the problem we're seeing before landing this…

Revision history for this message
Ricardo Bánffy (rbanffy) wrote :

Seems OK.

Revision history for this message
Gavin Panella (allenap) wrote :

This is meant first as a cowboy-patch for OIL to see if it helps. If it does help I'll probably pursue a proper fix in simplestreams.

Thanks for the reviews :)

Revision history for this message
Blake Rouse (blake-rouse) wrote :

So I was able to test this branch. I setup the cluster to sync from the region every 10 seconds. With it syncing every 10 seconds with this change there where no CLOSE_WAIT sockets.

sudo lsof | grep maas | grep CLOSE_WAIT | wc -l
0

With this change disabled, so simplestreams using python-requests I got many CLOSE_WAITS

sudo lsof | grep maas | grep CLOSE_WAIT | wc -l
88
sudo lsof | grep maas | grep CLOSE_WAIT | wc -l
104

And it keeps growing. This fixes the problem lets land this now. So 1.8 is stable, without the cluster will need to keep being restarted. At some point people will reach a limit no matter the ulimit.

review: Approve
Revision history for this message
Scott Moser (smoser) wrote :

python-requests uses urllib3 which does connection pooling.
it'd seem easy enough to reproduce this outside of maas.
just look at sstream-query . you could run that in a loop, and we should see similar leakage.

Revision history for this message
Ricardo Bánffy (rbanffy) wrote :

I was left wondering if we should adopt a convention for naming "cowboy patches" (if that's the term we'll use) so that we don't confuse them with long-term solutions.

I've been using "bug-NNNNN-something-meaningful" as my personal sanity-keeping memory aid, but the "bug-" part could be morphed into something more interesting.

Revision history for this message
Gavin Panella (allenap) wrote :

> python-requests uses urllib3 which does connection pooling. it'd seem
> easy enough to reproduce this outside of maas. just look at
> sstream-query . you could run that in a loop, and we should see
> similar leakage.

Thanks Scott. I've tried this:

  $ python -i /usr/bin/sstream-query \
  > --keyring=/usr/share/keyrings/ubuntu-cloudimage-keyring.gpg \
  > http://maas.ubuntu.com/images/ephemeral-v2/daily/
  ...
  >>> import time
  >>> while True:
  ... main()
  ... time.sleep(0.1)

but I wasn't able to accumulate connections in CLOSE_WAIT. Could there
be a code path taken (or not) that might mean we're missing something?
I've found five code paths through contentsource.py that /might/ leak (I
have put together a patch to fix them, but it's not tested:
http://paste.ubuntu.com/11648573/).

Revision history for this message
Gavin Panella (allenap) wrote :

> I was left wondering if we should adopt a convention for naming
> "cowboy patches" (if that's the term we'll use) so that we don't
> confuse them with long-term solutions.
>
> I've been using "bug-NNNNN-something-meaningful" as my personal
> sanity-keeping memory aid, but the "bug-" part could be morphed into
> something more interesting.

Raphaël moved this into a module called provisioningserver.monkey today.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/provisioningserver/plugin.py'
--- src/provisioningserver/plugin.py 2015-05-18 20:17:28 +0000
+++ src/provisioningserver/plugin.py 2015-06-05 15:01:24 +0000
@@ -20,6 +20,7 @@
20import os20import os
21import socket21import socket
22from socket import error as socket_error22from socket import error as socket_error
23import sys
2324
24from provisioningserver.utils.debug import (25from provisioningserver.utils.debug import (
25 register_sigusr2_thread_dump_handler,26 register_sigusr2_thread_dump_handler,
@@ -45,6 +46,27 @@
45from zope.interface import implementer46from zope.interface import implementer
4647
4748
49def force_simplestreams_to_use_urllib2():
50 """Monkey-patch `simplestreams` to use `urllib2`.
51
52 This prevents the use of `requests` which /may/ be helping simplestreams
53 to lose file-descriptors.
54 """
55 import simplestreams.contentsource
56
57 if sys.version_info > (3, 0):
58 import urllib.request as urllib_request
59 import urllib.error as urllib_error
60 else:
61 import urllib2 as urllib_request
62 urllib_error = urllib_request
63
64 vars(simplestreams.contentsource).update(
65 URL_READER=simplestreams.contentsource.Urllib2UrlReader,
66 URL_READER_CLASSNAME="Urllib2UrlReader", urllib_error=urllib_error,
67 urllib_request=urllib_request)
68
69
48@implementer(ICredentialsChecker)70@implementer(ICredentialsChecker)
49class SingleUsernamePasswordChecker:71class SingleUsernamePasswordChecker:
50 """An `ICredentialsChecker` for a single username and password."""72 """An `ICredentialsChecker` for a single username and password."""
@@ -231,6 +253,7 @@
231 def makeService(self, options):253 def makeService(self, options):
232 """Construct the MAAS Cluster service."""254 """Construct the MAAS Cluster service."""
233 register_sigusr2_thread_dump_handler()255 register_sigusr2_thread_dump_handler()
256 force_simplestreams_to_use_urllib2()
234257
235 from provisioningserver import services258 from provisioningserver import services
236 from provisioningserver.config import Config259 from provisioningserver.config import Config