in teamcity-s3-sdk/src/main/java/jetbrains/buildServer/artifacts/s3/publish/presigned/upload/S3PresignedMultipartUpload.java [58:144]
protected String upload() throws IOException {
LOGGER.debug(() -> "Multipart upload " + this + " started");
final long totalLength = myFile.length();
final int nParts = (int)(totalLength % myChunkSizeInBytes == 0 ? totalLength / myChunkSizeInBytes : totalLength / myChunkSizeInBytes + 1);
myProgressListener.beforeUploadStarted();
try {
splitFileToPartsWithChecksum(nParts);
assert myFileParts != null;
List<String> digests = myFileParts.stream()
.map(FilePart::getDigest)
.collect(Collectors.toList());
String contentType = getContentType(myFile);
final Pair<PresignedUrlDto, Long> resultPair = myS3SignedUploadManager.getMultipartUploadUrls(
myObjectKey,
contentType,
digests,
uploadId,
myTtl.get()
);
final PresignedUrlDto multipartUploadUrls = resultPair.first;
myProgressListener.urlsGenerated(Duration.ofNanos(resultPair.second));
if (uploadId == null) {
uploadId = multipartUploadUrls.getUploadId();
}
// initialize array of results
// it will be reused by upper-level retry procedure, that in some cases will be triggered
// (see jetbrains.buildServer.artifacts.s3.publish.presigned.upload.S3SignedUrlFileUploader::upload)
if (myEtags == null) {
myEtags = new AtomicReferenceArray<>(nParts);
}
// actually we don't need results from these futures.
// each completion will directly write result to the array of results called myEtags.
CompletableFuture[] chunkUploadFutures = multipartUploadUrls.getPresignedUrlParts()
.stream()
.map(partDto -> {
final int partIndex = partDto.getPartNumber() - 1;
final FilePart filePart = myFileParts.get(partIndex);
myProgressListener.beforePartUploadStarted(partIndex, filePart.getLength());
final String url = partDto.getUrl();
// this allows us to save time in case of huge files re-upload
if (myEtags.get(partIndex) != null) {
// part was already uploaded
return CompletableFuture.completedFuture(myEtags.get(partIndex));
}
return myRetrier.executeAsync(() -> {
try {
return myLowLevelS3Client.uploadFilePart(url, filePart);
} catch (URISyntaxException e) {
throw new AbortRetriesException(e);
}
})
.thenApply(onUploadSuccess(partDto, partIndex, url, filePart))
.exceptionally(e -> {
myProgressListener.onPartUploadFailed(e, partIndex);
ExceptionUtil.rethrowAsRuntimeException(e);
return null;
});
})
.toArray(CompletableFuture[]::new);
// await completion of all futures.
// in case of exception in any of them, we will cancel all the futures immediately
allOfTerminateOnFailure(chunkUploadFutures).get();
final Iterator<PresignedUrlPartDto> iterator = multipartUploadUrls.getPresignedUrlParts().iterator();
String digest = DigestUtil.multipartDigest(getEtags());
myProgressListener.onFileUploadSuccess(digest);
return digest;
} catch (final Exception e) {
Exception cause = stripRootCause(e);
boolean isRecoverable = canRetry(cause);
LOGGER.warnAndDebugDetails("Multipart upload for " + this + " failed", cause);
if (!isRecoverable) {
resetUploadId();
}
myProgressListener.onFileUploadFailed(cause.toString(), isRecoverable);
// InterruptedException will be re-thrown wrapped in RuntimeException
ExceptionUtil.rethrowAsRuntimeException(cause);
return null;
}
}