Merge ~laney/germinate:urlopen-timeout into germinate:master

Proposed by Iain Lane
Status: Merged
Merged at revision: eebce4eb69bf7b76f9bfd61b2578ce622ac55fce
Proposed branch: ~laney/germinate:urlopen-timeout
Merge into: germinate:master
Diff against target: 134 lines (+79/-2)
2 files modified
germinate/seeds.py (+14/-2)
germinate/tests/test_seeds.py (+65/-0)
Reviewer Review Type Date Requested Status
Colin Watson Approve
Review via email: mp+397151@code.launchpad.net

Description of the change

We're seeing this happening increasingly frequently in livefs builds now. I'm not sure what changed. Here's a first cut at this. Couple of thoughts

  - Are the constants OK?
  - Should I back off?

If the bug comes back then the new test will hang forever. Not sure if I should add a timeout there, or how to do that really.

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) wrote :

We should probably switch the HTTP library here to requests at some point, but something like this will do for now.

review: Needs Fixing
Revision history for this message
Iain Lane (laney) wrote :

Cheers, I've fixed it as requested.

Revision history for this message
Colin Watson (cjwatson) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/germinate/seeds.py b/germinate/seeds.py
index 98ac34f..e3cfdaa 100644
--- a/germinate/seeds.py
+++ b/germinate/seeds.py
@@ -28,6 +28,7 @@ import logging
28import os28import os
29import re29import re
30import shutil30import shutil
31import socket
31import subprocess32import subprocess
32import sys33import sys
33import tempfile34import tempfile
@@ -107,6 +108,8 @@ class SeedVcs(object):
107108
108class Seed(six.Iterator):109class Seed(six.Iterator):
109 """A single seed from a collection."""110 """A single seed from a collection."""
111 _n_retries = 5 # how many times to try downloading a URL
112 _retry_timeout = 10 # timeout between each of the above retries
110113
111 def _open_seed_bzr(self, base, branch, name):114 def _open_seed_bzr(self, base, branch, name):
112 global _vcs_cache_dir115 global _vcs_cache_dir
@@ -177,11 +180,20 @@ class Seed(six.Iterator):
177 fullpath = os.path.join(path, name)180 fullpath = os.path.join(path, name)
178 _logger.info("Using %s", fullpath)181 _logger.info("Using %s", fullpath)
179 return open(fullpath)182 return open(fullpath)
180 _logger.info("Downloading %s", url)
181 req = Request(url)183 req = Request(url)
182 req.add_header('Cache-Control', 'no-cache')184 req.add_header('Cache-Control', 'no-cache')
183 req.add_header('Pragma', 'no-cache')185 req.add_header('Pragma', 'no-cache')
184 return urlopen(req)186 for n in reversed(range(Seed._n_retries)): # try 5 times
187 try:
188 _logger.info("Downloading %s", url)
189 return urlopen(req, timeout=Seed._retry_timeout)
190 except socket.timeout as e:
191 err = e
192 if n > 0:
193 _logger.warning("Timed out opening %s, retrying...", url)
194
195 _logger.error("Timed out opening %s, failing.", url)
196 raise err
185197
186 def _open_seed(self, base, branch, name, vcs=None):198 def _open_seed(self, base, branch, name, vcs=None):
187 if vcs is not None:199 if vcs is not None:
diff --git a/germinate/tests/test_seeds.py b/germinate/tests/test_seeds.py
index f747b0a..b92ec90 100644
--- a/germinate/tests/test_seeds.py
+++ b/germinate/tests/test_seeds.py
@@ -17,13 +17,20 @@
17# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA17# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18# 02110-1301, USA.18# 02110-1301, USA.
1919
20import errno
20import io21import io
21import os22import os
23import random
24import socket
22import textwrap25import textwrap
26import threading
27
28from fixtures import MockPatch
2329
24from germinate.seeds import (30from germinate.seeds import (
25 AtomicFile,31 AtomicFile,
26 Seed,32 Seed,
33 SeedError,
27 SingleSeedStructure,34 SingleSeedStructure,
28 )35 )
29from germinate.tests.helpers import TestCase36from germinate.tests.helpers import TestCase
@@ -109,6 +116,64 @@ class TestSeed(TestCase):
109 self.assertTrue(" * foo\n", lines[0])116 self.assertTrue(" * foo\n", lines[0])
110117
111118
119class TestSeedHTTP(TestCase):
120 def setUp(self):
121 """ Make a server which reads anything you send it but never responds
122 to you, to test timeouts are triggered properly. """
123 super(TestSeedHTTP, self).setUp()
124 self.n_hits = 0
125 self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
126 while True:
127 try:
128 self.port = random.randint(1025, 65535)
129 self.server.bind(("localhost", self.port))
130 break
131 except OSError as e:
132 if e.errno != errno.EADDRINUSE:
133 raise
134 self.server.listen(1)
135
136 def accept():
137 while self.server is not None:
138 try:
139 (client, _) = self.server.accept()
140 self.n_hits += 1
141 except (OSError, socket.error) as e:
142 if e.errno in (errno.EBADF, errno.EINVAL): # closed
143 return
144 raise
145 # We'll read the HTTP request, not reply and the caller should
146 # eventually timeout
147 while True:
148 if not client.recv(1024):
149 break
150 client.close()
151
152 self.thread = threading.Thread(target=accept)
153 self.thread.start()
154
155 def tearDown(self):
156 self.server.shutdown(socket.SHUT_RDWR)
157 self.server.close()
158 self.server = None
159 self.thread.join()
160 super(TestSeedHTTP, self).tearDown()
161
162 def test_init_http_timeout(self):
163 """ When we connect to a server which doesn't respond, we timeout. """
164 fixture = MockPatch("germinate.seeds.Seed._retry_timeout", 0.1)
165 with fixture:
166 try:
167 Seed(
168 ["http://localhost:%s" % self.port],
169 ["collection.dist"],
170 "test")
171 except SeedError:
172 self.assertEqual(self.n_hits, 5)
173 return
174 self.assertFalse(True, "SeedError was not raised")
175
176
112class TestSingleSeedStructure(TestCase):177class TestSingleSeedStructure(TestCase):
113 def test_basic(self):178 def test_basic(self):
114 """A SingleSeedStructure object has the correct basic properties."""179 """A SingleSeedStructure object has the correct basic properties."""

Subscribers

People subscribed via source and target branches

to all changes: