Browse code

* s3/s3.py: improved retrying in send_request() and send_file()

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
Showing 2 changed files
... ...
@@ -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