protected String upload()

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;
    }
  }