From 8ffd15333ff2b57f1eb44b43a7a0f5a000a25687 Mon Sep 17 00:00:00 2001 From: Amanda Ruby Date: Thu, 30 Apr 2026 13:14:14 -0400 Subject: [PATCH 1/2] Add simple retries --- sendsafely/utilities.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/sendsafely/utilities.py b/sendsafely/utilities.py index a7d9a7b..15deb83 100644 --- a/sendsafely/utilities.py +++ b/sendsafely/utilities.py @@ -15,6 +15,7 @@ import json import os import secrets +import time import requests from .pgpy import PGPMessage, PGPKey @@ -195,7 +196,16 @@ def _upload_file_part_to_s3(encrypted_file_part, url): :param url: The S3 URL we're uploading to :return: The JSON response from S3. """ - return requests.put(url=url, data=encrypted_file_part) + max_attempts = 5 + max_backoff_seconds = 180 + for attempt in range(max_attempts): + try: + return requests.put(url=url, data=encrypted_file_part) + except (requests.exceptions.ConnectionError, requests.exceptions.Timeout, + requests.exceptions.ChunkedEncodingError): + if attempt == max_attempts - 1: + raise + time.sleep(min(2 ** attempt, max_backoff_seconds)) def _calculate_package_checksum(package_code, keycode): @@ -333,4 +343,4 @@ def _make_safe_for_urlsafebase64(client_secret): client_secret = client_secret.replace("=", "") client_secret = client_secret.replace("+", "-") client_secret = client_secret.replace("/", "_") - return client_secret \ No newline at end of file + return client_secret From aaaf08be991b4234eb5840c68b71b94ffee514d6 Mon Sep 17 00:00:00 2001 From: Amanda Ruby Date: Thu, 30 Apr 2026 17:41:24 -0400 Subject: [PATCH 2/2] Add retry logging --- sendsafely/utilities.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sendsafely/utilities.py b/sendsafely/utilities.py index 15deb83..346abb2 100644 --- a/sendsafely/utilities.py +++ b/sendsafely/utilities.py @@ -15,6 +15,7 @@ import json import os import secrets +import sys import time import requests @@ -202,10 +203,15 @@ def _upload_file_part_to_s3(encrypted_file_part, url): try: return requests.put(url=url, data=encrypted_file_part) except (requests.exceptions.ConnectionError, requests.exceptions.Timeout, - requests.exceptions.ChunkedEncodingError): + requests.exceptions.ChunkedEncodingError) as e: if attempt == max_attempts - 1: raise - time.sleep(min(2 ** attempt, max_backoff_seconds)) + sleep_seconds = min(2 ** attempt, max_backoff_seconds) + sys.stdout.write( + '\nRetrying S3 part upload (attempt {0}/{1}) after {2}: sleeping {3}s\n'.format( + attempt + 2, max_attempts, type(e).__name__, sleep_seconds)) + sys.stdout.flush() + time.sleep(sleep_seconds) def _calculate_package_checksum(package_code, keycode):