258
def _retry_get(self, relpath, ranges, exc_info):
259
"""A GET request have failed, let's retry with a simpler request."""
262
# The server does not gives us enough data or
263
# a bogus-looking result, let's try again with
264
# a simpler request if possible.
258
def _degrade_range_hint(self, relpath, ranges, exc_info):
265
259
if self._range_hint == 'multi':
266
260
self._range_hint = 'single'
267
mutter('Retry %s with single range request' % relpath)
261
mutter('Retry "%s" with single range request' % relpath)
269
262
elif self._range_hint == 'single':
270
263
self._range_hint = None
271
mutter('Retry %s without ranges' % relpath)
274
# Note that since the offsets and the ranges may not
275
# be in the same order, we don't try to calculate a
276
# restricted single range encompassing unprocessed
278
code, f = self._get(relpath, ranges)
279
return try_again, code, f
264
mutter('Retry "%s" without ranges' % relpath)
281
# We tried all the tricks, but nothing worked. We
282
# re-raise original exception; the 'mutter' calls
283
# above will indicate that further tries were
266
# We tried all the tricks, but nothing worked. We re-raise original
267
# exception; the 'mutter' calls above will indicate that further
268
# tries were unsuccessful
285
269
raise exc_info[0], exc_info[1], exc_info[2]
287
# Having to round trip to the server means waiting for a response, so it is
288
# better to download extra bytes. We have two constraints to satisfy when
289
# choosing the right value: a request with too much ranges will make some
290
# servers issue a '400: Bad request' error (when we have too much ranges,
291
# we exceed the apache header max size (8190 by default) for example) ;
292
# coalescing too much ranges will increasing the data transferred and
293
# degrade performances.
294
_bytes_to_read_before_seek = 512
295
# No limit on the offset number combined into one, we are trying to avoid
296
# downloading the whole file.
271
def _get_ranges_hinted(self, relpath, ranges):
272
"""Issue a ranged GET request taking server capabilities into account.
274
Depending of the errors returned by the server, we try several GET
275
requests, trying to minimize the data transferred.
277
:param relpath: Path relative to transport base URL
278
:param ranges: None to get the whole file;
279
or a list of _CoalescedOffset to fetch parts of a file.
280
:returns: A file handle containing at least the requested ranges.
287
code, f = self._get(relpath, ranges)
288
except errors.InvalidRange, e:
290
exc_info = sys.exc_info()
291
self._degrade_range_hint(relpath, ranges, exc_info)
295
# _coalesce_offsets is a helper for readv, it try to combine ranges without
296
# degrading readv performances. _bytes_to_read_before_seek is the value
297
# used for the limit parameter and has been tuned for other transports. For
298
# HTTP, the name is inappropriate but the parameter is still useful and
299
# helps reduce the number of chunks in the response. The overhead for a
300
# chunk (headers, length, footer around the data itself is variable but
301
# around 50 bytes. We use 128 to reduce the range specifiers that appear in
302
# the header, some servers (notably Apache) enforce a maximum length for a
303
# header and issue a '400: Bad request' error when too much ranges are
305
_bytes_to_read_before_seek = 128
306
# No limit on the offset number that get combined into one, we are trying
307
# to avoid downloading the whole file.
297
308
_max_readv_combined = 0
299
310
def readv(self, relpath, offsets):
311
322
mutter('http readv of %s offsets => %s collapsed %s',
312
323
relpath, len(offsets), len(coalesced))
318
code, f = self._get(relpath, coalesced)
319
except (errors.InvalidRange, errors.ShortReadvError), e:
320
try_again, code, f = self._retry_get(relpath, coalesced,
325
f = self._get_ranges_hinted(relpath, coalesced)
323
326
for start, size in offsets:
331
334
if len(data) != size:
332
335
raise errors.ShortReadvError(relpath, start, size,
333
336
actual=len(data))
334
except (errors.InvalidRange, errors.ShortReadvError), e:
335
# Note that we replace 'f' here and that it
336
# may need cleaning one day before being
338
try_again, code, f = self._retry_get(relpath, coalesced,
337
except errors.ShortReadvError, e:
338
self._degrade_range_hint(relpath, coalesced, sys.exc_info())
340
# Since the offsets and the ranges may not be in the same
341
# order, we don't try to calculate a restricted single
342
# range encompassing unprocessed offsets.
344
# Note: we replace 'f' here, it may need cleaning one day
345
# before being thrown that way.
346
f = self._get_ranges_hinted(relpath, coalesced)
340
349
# After one or more tries, we get the data.
341
350
yield start, data