All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hubspot.singularity.s3.base.S3ArtifactChunkDownloader Maven / Gradle / Ivy

package com.hubspot.singularity.s3.base;

import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.slf4j.Logger;

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.hubspot.deploy.S3Artifact;
import com.hubspot.mesos.JavaUtils;
import com.hubspot.singularity.runner.base.sentry.SingularityRunnerExceptionNotifier;
import com.hubspot.singularity.s3.base.config.SingularityS3Configuration;

public class S3ArtifactChunkDownloader implements Callable {

  private final SingularityS3Configuration configuration;
  private final AmazonS3 s3;
  private final S3Artifact s3Artifact;
  private final Path downloadTo;
  private final int chunk;
  private final long chunkSize;
  private final long length;
  private final Logger log;
  private final SingularityRunnerExceptionNotifier exceptionNotifier;

  private int retryNum;

  public S3ArtifactChunkDownloader(SingularityS3Configuration configuration, Logger log, AmazonS3 s3, S3Artifact s3Artifact, Path downloadTo, int chunk, long chunkSize, long length, SingularityRunnerExceptionNotifier exceptionNotifier) {
    this.configuration = configuration;
    this.log = log;
    this.s3 = s3;
    this.s3Artifact = s3Artifact;
    this.downloadTo = downloadTo;
    this.chunk = chunk;
    this.chunkSize = chunkSize;
    this.length = length;
    this.exceptionNotifier = exceptionNotifier;
  }

  @Override
  public Path call() throws Exception {
    final long start = System.currentTimeMillis();

    final ExecutorService chunkExecutorService = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setDaemon(true).setNameFormat("S3ArtifactDownloaderChunk-" + chunk + "-Thread-%d").build());

    try {
      while (retryNum <= configuration.getS3ChunkRetries()) {
        final long timeout = System.currentTimeMillis();

        final Future future = chunkExecutorService.submit(createDownloader(retryNum));

        try {
          return future.get(configuration.getS3ChunkDownloadTimeoutMillis(), TimeUnit.MILLISECONDS);
        } catch (TimeoutException te) {
          log.error("Chunk {} (retry {}) for {} timed out after {} - total duration {}", chunk, retryNum, s3Artifact.getFilename(), JavaUtils.duration(timeout), JavaUtils.duration(start));
          future.cancel(true);
          if (retryNum == configuration.getS3ChunkRetries()) {
            exceptionNotifier.notify("Timeout downloading chunk", te, ImmutableMap.of("filename", s3Artifact.getFilename(), "chunk", Integer.toString(chunk), "retry", Integer.toString(retryNum)));
          }
        } catch (InterruptedException ie) {
          log.warn("Chunk {} (retry {}) for {} interrupted", chunk, retryNum, s3Artifact.getFilename());
          exceptionNotifier.notify("Interrupted during download", ie, ImmutableMap.of("filename", s3Artifact.getFilename(), "chunk", Integer.toString(chunk), "retry", Integer.toString(retryNum)));
        } catch (Throwable t) {
          log.error("Error while downloading chunk {} (retry {}) for {}", chunk, retryNum, s3Artifact.getFilename(), t);
          exceptionNotifier.notify(String.format("Error downloading chunk (%s)", t.getMessage()), t, ImmutableMap.of("filename", s3Artifact.getFilename(), "chunk", Integer.toString(chunk), "retry", Integer.toString(retryNum)));
        }

        retryNum++;
      }

      throw new IllegalStateException(String.format("Chunk %s for %s failed to download after %s tries", chunk, s3Artifact.getFilename(), retryNum + 1));
    } finally {
      chunkExecutorService.shutdownNow();
    }
  }

  private Callable createDownloader(final int retryNum) {
    return new Callable() {
      public Path call() throws Exception {
        final Path chunkPath = (chunk == 0) ? downloadTo : Paths.get(downloadTo + "_" + chunk + "_" + retryNum);
        chunkPath.toFile().deleteOnExit();

        final long startTime = System.currentTimeMillis();

        final long byteRangeStart = chunk * chunkSize;
        final long byteRangeEnd = Math.min((chunk + 1) * chunkSize - 1, length);

        log.info("Downloading {} - chunk {} (retry {}) ({}-{}) to {}", s3Artifact.getFilename(), chunk, retryNum, byteRangeStart, byteRangeEnd, chunkPath);

        GetObjectRequest getObjectRequest = new GetObjectRequest(s3Artifact.getS3Bucket(), s3Artifact.getS3ObjectKey())
            .withRange(byteRangeStart, byteRangeEnd);

        S3Object fetchedObject = s3.getObject(getObjectRequest);

        try (InputStream is = fetchedObject.getObjectContent()) {
          Files.copy(is, chunkPath, StandardCopyOption.REPLACE_EXISTING);
        }

        log.info("Finished downloading chunk {} (retry {}) of {} ({} bytes) in {}", chunk, retryNum, s3Artifact.getFilename(), byteRangeEnd - byteRangeStart, JavaUtils.duration(startTime));

        return chunkPath;
      };
    };
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy