diff options
author | Kent Delante <kent.delante@proton.me> | 2025-06-10 17:17:36 +0800 |
---|---|---|
committer | Kent Delante <kent.delante@proton.me> | 2025-06-20 19:44:55 +0800 |
commit | e9aa004b9ba0706eaae56304f68b52af063bccad (patch) | |
tree | 9f3bef7d28089139a91b9df339bd003782dfb841 | |
parent | c5c6dcb027eed042b3fb1f91be2801d83889dd74 (diff) | |
download | nextcloud-server-backport/53419/stable30.tar.gz nextcloud-server-backport/53419/stable30.zip |
fix(s3): retry failed multipart uploads with decreased concurrencybackport/53419/stable30
Signed-off-by: Kent Delante <kent.delante@proton.me>
-rw-r--r-- | lib/private/Files/ObjectStore/S3ObjectTrait.php | 57 |
1 files changed, 41 insertions, 16 deletions
diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php index c08ee7a5a0a..0d0260e9a7b 100644 --- a/lib/private/Files/ObjectStore/S3ObjectTrait.php +++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php @@ -107,27 +107,52 @@ trait S3ObjectTrait { * @throws \Exception when something goes wrong, message will be logged */ protected function writeMultiPart(string $urn, StreamInterface $stream, ?string $mimetype = null): void { - $uploader = new MultipartUploader($this->getConnection(), $stream, [ - 'bucket' => $this->bucket, - 'concurrency' => $this->concurrency, - 'key' => $urn, - 'part_size' => $this->uploadPartSize, - 'params' => [ - 'ContentType' => $mimetype, - 'StorageClass' => $this->storageClass, - ] + $this->getSSECParameters(), - ]); + $attempts = 0; + $uploaded = false; + $concurrency = $this->concurrency; + $exception = null; + $state = null; + + // retry multipart upload once with concurrency at half on failure + while (!$uploaded && $attempts <= 1) { + $uploader = new MultipartUploader($this->getConnection(), $stream, [ + 'bucket' => $this->bucket, + 'concurrency' => $concurrency, + 'key' => $urn, + 'part_size' => $this->uploadPartSize, + 'state' => $state, + 'params' => [ + 'ContentType' => $mimetype, + 'StorageClass' => $this->storageClass, + ] + $this->getSSECParameters(), + ]); + + try { + $uploader->upload(); + $uploaded = true; + } catch (S3MultipartUploadException $e) { + $exception = $e; + $attempts++; + + if ($concurrency > 1) { + $concurrency = round($concurrency / 2); + } - try { - $uploader->upload(); - } catch (S3MultipartUploadException $e) { + if ($stream->isSeekable()) { + $stream->rewind(); + } + } + } + + if (!$uploaded) { // if anything goes wrong with multipart, make sure that you don“t poison and // slow down s3 bucket with orphaned fragments - $uploadInfo = $e->getState()->getId(); - if ($e->getState()->isInitiated() && (array_key_exists('UploadId', $uploadInfo))) { + $uploadInfo = $exception->getState()->getId(); + if ($exception->getState()->isInitiated() && (array_key_exists('UploadId', $uploadInfo))) { $this->getConnection()->abortMultipartUpload($uploadInfo); } - throw new \OCA\DAV\Connector\Sabre\Exception\BadGateway('Error while uploading to S3 bucket', 0, $e); + + throw new \OCA\DAV\Connector\Sabre\Exception\BadGateway('Error while uploading to S3 bucket', 0, $exception); } } |