Skip to content

Commit 06b5ce5

Browse files
author
Kent Delante
committed
fix(s3): retry failed multipart uploads with decreased concurrency
Signed-off-by: Kent Delante <kent.delante@proton.me>
1 parent ebdb12f commit 06b5ce5

1 file changed

Lines changed: 43 additions & 17 deletions

File tree

lib/private/Files/ObjectStore/S3ObjectTrait.php

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -119,28 +119,54 @@ protected function writeSingle(string $urn, StreamInterface $stream, array $meta
119119
protected function writeMultiPart(string $urn, StreamInterface $stream, array $metaData): void {
120120
$mimetype = $metaData['mimetype'] ?? null;
121121
unset($metaData['mimetype']);
122-
$uploader = new MultipartUploader($this->getConnection(), $stream, [
123-
'bucket' => $this->bucket,
124-
'concurrency' => $this->concurrency,
125-
'key' => $urn,
126-
'part_size' => $this->uploadPartSize,
127-
'params' => [
128-
'ContentType' => $mimetype,
129-
'Metadata' => $this->buildS3Metadata($metaData),
130-
'StorageClass' => $this->storageClass,
131-
] + $this->getSSECParameters(),
132-
]);
133122

134-
try {
135-
$uploader->upload();
136-
} catch (S3MultipartUploadException $e) {
123+
$attempts = 0;
124+
$uploaded = false;
125+
$concurrency = $this->concurrency;
126+
$exception = null;
127+
$state = null;
128+
129+
// retry multipart upload once with concurrency at half on failure
130+
while (!$uploaded && $attempts <= 1) {
131+
$uploader = new MultipartUploader($this->getConnection(), $stream, [
132+
'bucket' => $this->bucket,
133+
'concurrency' => $concurrency,
134+
'key' => $urn,
135+
'part_size' => $this->uploadPartSize,
136+
'state' => $state,
137+
'params' => [
138+
'ContentType' => $mimetype,
139+
'Metadata' => $this->buildS3Metadata($metaData),
140+
'StorageClass' => $this->storageClass,
141+
] + $this->getSSECParameters(),
142+
]);
143+
144+
try {
145+
$uploader->upload();
146+
$uploaded = true;
147+
} catch (S3MultipartUploadException $e) {
148+
$exception = $e;
149+
$attempts++;
150+
151+
if ($concurrency > 1) {
152+
$concurrency = round($concurrency / 2);
153+
}
154+
155+
if ($stream->isSeekable()) {
156+
$stream->rewind();
157+
}
158+
}
159+
}
160+
161+
if (!$uploaded) {
137162
// if anything goes wrong with multipart, make sure that you don´t poison and
138163
// slow down s3 bucket with orphaned fragments
139-
$uploadInfo = $e->getState()->getId();
140-
if ($e->getState()->isInitiated() && (array_key_exists('UploadId', $uploadInfo))) {
164+
$uploadInfo = $exception->getState()->getId();
165+
if ($exception->getState()->isInitiated() && (array_key_exists('UploadId', $uploadInfo))) {
141166
$this->getConnection()->abortMultipartUpload($uploadInfo);
142167
}
143-
throw new \OCA\DAV\Connector\Sabre\Exception\BadGateway('Error while uploading to S3 bucket', 0, $e);
168+
169+
throw new \OCA\DAV\Connector\Sabre\Exception\BadGateway('Error while uploading to S3 bucket', 0, $exception);
144170
}
145171
}
146172

0 commit comments

Comments
 (0)