git-svn-id: https://s3tools.svn.sourceforge.net/svnroot/s3tools/s3cmd/trunk@265 830e0280-6d2a-0410-9c65-932aecc39d9d
Michal Ludvig authored on 2008/11/25 10:15:39... | ... |
@@ -1,5 +1,9 @@ |
1 | 1 |
2008-11-24 Michal Ludvig <michal@logix.cz> |
2 | 2 |
|
3 |
+ * s3/s3.py: improved retrying in send_request() and send_file() |
|
4 |
+ |
|
5 |
+2008-11-24 Michal Ludvig <michal@logix.cz> |
|
6 |
+ |
|
3 | 7 |
* s3cmd, S3/S3.py, NEWS: "s3cmd mv" for moving objects |
4 | 8 |
|
5 | 9 |
2008-11-24 Michal Ludvig <michal@logix.cz> |
... | ... |
@@ -6,6 +6,7 @@ |
6 | 6 |
import sys |
7 | 7 |
import os, os.path |
8 | 8 |
import base64 |
9 |
+import time |
|
9 | 10 |
import md5 |
10 | 11 |
import sha |
11 | 12 |
import hmac |
... | ... |
@@ -58,6 +59,9 @@ class S3(object): |
58 | 58 |
## S3 sometimes sends HTTP-307 response |
59 | 59 |
redir_map = {} |
60 | 60 |
|
61 |
+ ## Maximum attempts of re-issuing failed requests |
|
62 |
+ _max_retries = 5 |
|
63 |
+ |
|
61 | 64 |
def __init__(self, config): |
62 | 65 |
self.config = config |
63 | 66 |
|
... | ... |
@@ -328,7 +332,11 @@ class S3(object): |
328 | 328 |
debug("CreateRequest: resource[uri]=" + resource['uri']) |
329 | 329 |
return (method_string, resource, headers) |
330 | 330 |
|
331 |
- def send_request(self, request, body = None, retries = 5): |
|
331 |
+ def _fail_wait(self, retries): |
|
332 |
+ # Wait a few seconds. The more it fails the more we wait. |
|
333 |
+ return (self._max_retries - retries + 1) * 3 |
|
334 |
+ |
|
335 |
+ def send_request(self, request, body = None, retries = _max_retries): |
|
332 | 336 |
method_string, resource, headers = request |
333 | 337 |
debug("Processing request, please wait...") |
334 | 338 |
try: |
... | ... |
@@ -345,6 +353,8 @@ class S3(object): |
345 | 345 |
except Exception, e: |
346 | 346 |
if retries: |
347 | 347 |
warning("Retrying failed request: %s (%s)" % (resource['uri'], e)) |
348 |
+ warning("Waiting %d sec..." % self._fail_wait(retries)) |
|
349 |
+ time.sleep(self._fail_wait(retries)) |
|
348 | 350 |
return self.send_request(request, body, retries - 1) |
349 | 351 |
else: |
350 | 352 |
raise S3RequestError("Request failed for: %s" % resource['uri']) |
... | ... |
@@ -362,6 +372,8 @@ class S3(object): |
362 | 362 |
if retries: |
363 | 363 |
warning(u"Retrying failed request: %s" % resource['uri']) |
364 | 364 |
warning(unicode(e)) |
365 |
+ warning("Waiting %d sec..." % self._fail_wait(retries)) |
|
366 |
+ time.sleep(self._fail_wait(retries)) |
|
365 | 367 |
return self.send_request(request, body, retries - 1) |
366 | 368 |
else: |
367 | 369 |
raise e |
... | ... |
@@ -371,7 +383,7 @@ class S3(object): |
371 | 371 |
|
372 | 372 |
return response |
373 | 373 |
|
374 |
- def send_file(self, request, file, throttle = 0, retries = 3): |
|
374 |
+ def send_file(self, request, file, throttle = 0, retries = _max_retries): |
|
375 | 375 |
method_string, resource, headers = request |
376 | 376 |
size_left = size_total = headers.get("content-length") |
377 | 377 |
if self.config.progress_meter: |
... | ... |
@@ -379,55 +391,66 @@ class S3(object): |
379 | 379 |
else: |
380 | 380 |
info("Sending file '%s', please wait..." % file.name) |
381 | 381 |
timestamp_start = time.time() |
382 |
- conn = self.get_connection(resource['bucket']) |
|
383 |
- conn.connect() |
|
384 |
- conn.putrequest(method_string, self.format_uri(resource)) |
|
385 |
- for header in headers.keys(): |
|
386 |
- conn.putheader(header, str(headers[header])) |
|
387 |
- conn.endheaders() |
|
382 |
+ try: |
|
383 |
+ conn = self.get_connection(resource['bucket']) |
|
384 |
+ conn.connect() |
|
385 |
+ conn.putrequest(method_string, self.format_uri(resource)) |
|
386 |
+ for header in headers.keys(): |
|
387 |
+ conn.putheader(header, str(headers[header])) |
|
388 |
+ conn.endheaders() |
|
389 |
+ except Exception, e: |
|
390 |
+ if retries: |
|
391 |
+ warning("Retrying failed request: %s (%s)" % (resource['uri'], e)) |
|
392 |
+ warning("Waiting %d sec..." % self._fail_wait(retries)) |
|
393 |
+ time.sleep(self._fail_wait(retries)) |
|
394 |
+ # Connection error -> same throttle value |
|
395 |
+ return self.send_file(request, file, throttle, retries - 1) |
|
396 |
+ else: |
|
397 |
+ raise S3UploadError("Request failed for: %s" % resource['uri']) |
|
388 | 398 |
file.seek(0) |
389 | 399 |
md5_hash = md5.new() |
390 |
- while (size_left > 0): |
|
391 |
- debug("SendFile: Reading up to %d bytes from '%s'" % (self.config.send_chunk, file.name)) |
|
392 |
- data = file.read(self.config.send_chunk) |
|
393 |
- md5_hash.update(data) |
|
394 |
- if self.config.progress_meter: |
|
395 |
- progress.update(delta_position = len(data)) |
|
396 |
- else: |
|
397 |
- debug("SendFile: Sending %d bytes to the server" % len(data)) |
|
398 |
- try: |
|
399 |
- conn.send(data) |
|
400 |
- except Exception, e: |
|
400 |
+ try: |
|
401 |
+ while (size_left > 0): |
|
402 |
+ debug("SendFile: Reading up to %d bytes from '%s'" % (self.config.send_chunk, file.name)) |
|
403 |
+ data = file.read(self.config.send_chunk) |
|
404 |
+ md5_hash.update(data) |
|
401 | 405 |
if self.config.progress_meter: |
402 |
- progress.done("failed") |
|
403 |
- ## When an exception occurs insert a |
|
404 |
- if retries: |
|
405 |
- conn.close() |
|
406 |
- warning("Upload of '%s' failed %s " % (file.name, e)) |
|
407 |
- throttle = throttle and throttle * 5 or 0.01 |
|
408 |
- warning("Retrying on lower speed (throttle=%0.2f)" % throttle) |
|
409 |
- return self.send_file(request, file, throttle, retries - 1) |
|
406 |
+ progress.update(delta_position = len(data)) |
|
410 | 407 |
else: |
411 |
- debug("Giving up on '%s' %s" % (file.name, e)) |
|
412 |
- raise S3UploadError |
|
413 |
- |
|
414 |
- size_left -= len(data) |
|
415 |
- if throttle: |
|
416 |
- time.sleep(throttle) |
|
417 |
- ## Call progress meter from here |
|
418 |
- debug("Sent %d bytes (%d %% of %d)" % ( |
|
419 |
- (size_total - size_left), |
|
420 |
- (size_total - size_left) * 100 / size_total, |
|
421 |
- size_total)) |
|
422 |
- md5_computed = md5_hash.hexdigest() |
|
423 |
- response = {} |
|
424 |
- http_response = conn.getresponse() |
|
425 |
- response["status"] = http_response.status |
|
426 |
- response["reason"] = http_response.reason |
|
427 |
- response["headers"] = convertTupleListToDict(http_response.getheaders()) |
|
428 |
- response["data"] = http_response.read() |
|
429 |
- response["size"] = size_total |
|
430 |
- conn.close() |
|
408 |
+ debug("SendFile: Sending %d bytes to the server" % len(data)) |
|
409 |
+ conn.send(data) |
|
410 |
+ |
|
411 |
+ size_left -= len(data) |
|
412 |
+ if throttle: |
|
413 |
+ time.sleep(throttle) |
|
414 |
+ ## Call progress meter from here |
|
415 |
+ debug("Sent %d bytes (%d %% of %d)" % ( |
|
416 |
+ (size_total - size_left), |
|
417 |
+ (size_total - size_left) * 100 / size_total, |
|
418 |
+ size_total)) |
|
419 |
+ md5_computed = md5_hash.hexdigest() |
|
420 |
+ response = {} |
|
421 |
+ http_response = conn.getresponse() |
|
422 |
+ response["status"] = http_response.status |
|
423 |
+ response["reason"] = http_response.reason |
|
424 |
+ response["headers"] = convertTupleListToDict(http_response.getheaders()) |
|
425 |
+ response["data"] = http_response.read() |
|
426 |
+ response["size"] = size_total |
|
427 |
+ conn.close() |
|
428 |
+ except Exception, e: |
|
429 |
+ if self.config.progress_meter: |
|
430 |
+ progress.done("failed") |
|
431 |
+ if retries: |
|
432 |
+ throttle = throttle and throttle * 5 or 0.01 |
|
433 |
+ warning("Request failed: %s (%s)" % (resource['uri'], e)) |
|
434 |
+ warning("Retrying on lower speed (throttle=%0.2f)" % throttle) |
|
435 |
+ warning("Waiting %d sec..." % self._fail_wait(retries)) |
|
436 |
+ time.sleep(self._fail_wait(retries)) |
|
437 |
+ # Connection error -> same throttle value |
|
438 |
+ return self.send_file(request, file, throttle, retries - 1) |
|
439 |
+ else: |
|
440 |
+ debug("Giving up on '%s' %s" % (file.name, e)) |
|
441 |
+ raise S3UploadError("Request failed for: %s" % resource['uri']) |
|
431 | 442 |
|
432 | 443 |
timestamp_end = time.time() |
433 | 444 |
response["elapsed"] = timestamp_end - timestamp_start |