AmazonS3: Getting warning: S3AbortableInputStream:Not all bytes were read from the S3ObjectInputStream, aborting HTTP connection
Asked Answered
L

7

41

Here's the warning that I am getting:

S3AbortableInputStream:Not all bytes were read from the S3ObjectInputStream, aborting HTTP connection. This is likely an error and may result in sub-optimal behavior. Request only the bytes you need via a ranged GET or drain the input stream after use.

I tried using try with resources but S3ObjectInputStream doesn't seem to close via this method.

 try (S3Object s3object = s3Client.getObject(new GetObjectRequest(bucket, key));
      S3ObjectInputStream s3ObjectInputStream = s3object.getObjectContent();
      BufferedReader reader = new BufferedReader(new InputStreamReader(s3ObjectInputStream, StandardCharsets.UTF_8));
    ){
  //some code here blah blah blah
 }

I also tried below code and explicitly closing but that doesn't work either:

S3Object s3object = s3Client.getObject(new GetObjectRequest(bucket, key));
S3ObjectInputStream s3ObjectInputStream = s3object.getObjectContent();

try (BufferedReader reader = new BufferedReader(new InputStreamReader(s3ObjectInputStream, StandardCharsets.UTF_8));
){
     //some code here blah blah
     s3ObjectInputStream.close();
     s3object.close();
}

Any help would be appreciated.

PS: I am only reading two lines of the file from S3 and the file has more data.

Linkous answered 10/8, 2017 at 22:11 Comment(0)
L
35

Got the answer via other medium. Sharing it here:

The warning indicates that you called close() without reading the whole file. This is problematic because S3 is still trying to send the data and you're leaving the connection in a sad state.

There's two options here:

  1. Read the rest of the data from the input stream so the connection can be reused.
  2. Call s3ObjectInputStream.abort() to close the connection without reading the data. The connection won't be reused, so you take some performance hit with the next request to re-create the connection. This may be worth it if it's going to take a long time to read the rest of the file.
Linkous answered 10/8, 2017 at 23:21 Comment(3)
What do yo mean by - "connection can be reused"? I thought that one HTTP connection is created for every S3 GET requestVshaped
Hi Chirag, can you elaborate a bit more on point 1 (Read the rest of the data from the input stream so the connection can be reused)? My line is like this: S3ObjectInputStream targetStream = confige.getObjectContent(); XSSFWorkbook excelf = new XSSFWorkbook(targetStream); not sure how to suppress this warning.Tullusus
@Vshaped The SDK maintains a connection pool under the covers and can re-use a connection for a subsequent request for better performance.Kesha
G
7

Following option #1 of Chirag Sejpal's answer I used the below statement to drain the S3AbortableInputStream to ensure the connection can be reused:

com.amazonaws.util.IOUtils.drainInputStream(s3ObjectInputStream);
 
Gaspar answered 11/8, 2020 at 13:19 Comment(0)
A
4

I ran into the same problem and the following class helped me

@Data
@AllArgsConstructor
public class S3ObjectClosable implements Closeable {
    private final S3Object s3Object;

    @Override
    public void close() throws IOException {
        s3Object.getObjectContent().abort();
        s3Object.close();
    }
}

and now you can use without warning

try (final var s3ObjectClosable = new S3ObjectClosable(s3Client.getObject(bucket, key))) {

//same code

}

Assizes answered 25/6, 2020 at 13:43 Comment(1)
wait why use this, S3Object is already closeable. aws.amazon.com/blogs/developer/closeable-s3objectsPetry
E
2

To add an example to Chirag Sejpal's answer (elaborating on option #1), the following can be used to read the rest of the data from the input stream before closing it:

S3Object s3object = s3Client.getObject(new GetObjectRequest(bucket, key));

try (S3ObjectInputStream s3ObjectInputStream = s3object.getObjectContent()) {
  try {
    // Read from stream as necessary
  } catch (Exception e) {
    // Handle exceptions as necessary
  } finally {
    while (s3ObjectInputStream != null && s3ObjectInputStream.read() != -1) {
      // Read the rest of the stream
    }
  }

  // The stream will be closed automatically by the try-with-resources statement
}
Elin answered 16/5, 2019 at 1:22 Comment(4)
I don't think this will work if an exception is thrown during the // Read from stream as necessary partRadloff
@Radloff I've updated the example so that the rest of the stream is read even if there was an exception during the // Read from stream as necessary part.Elin
I'd tried you method, it shows java.io.IOException: Attempted read on closed streamGalloromance
This try-with-resources code style won't work because the resource will be closed before either catch or finally block is executed. So to get that code working you'd have to switch back to old style try/catch/finally block with manual closing in finally block.Pardew
C
0

I ran into the same error.

As others have pointed out, the /tmp space in lambda is limited to 512 MB. And if the lambda context is re-used for a new invocation, then the /tmp space is already half-full.

So, when reading the S3 objects and writing all the files to the /tmp directory (as I was doing), I ran out of disk space somewhere in between. Lambda exited with error, but NOT all bytes from the S3ObjectInputStream were read.

So, two things one need to keep in mind:

1) If the first execution causes the problem, be stingy with your /tmp space. We have only 512 MB

2) If the second execution causes the problem, then this could be resolved by attacking the root problem. Its not possible to delete the /tmp folder. So, delete all the files in the /tmp folder after the execution is finished.

In java, here is what I did, which successfully resolved the problem.

public String handleRequest(Map < String, String > keyValuePairs, Context lambdaContext) {
  try {
    // All work here
  } catch (Exception e) {
    logger.error("Error {}", e.toString());
    return "Error";
  } finally {
    deleteAllFilesInTmpDir();
  }
}
private void deleteAllFilesInTmpDir() {
  Path path = java.nio.file.Paths.get(File.separator, "tmp", File.separator);
  try {
    if (Files.exists(path)) {
      deleteDir(path.toFile());
      logger.info("Successfully cleaned up the tmp directory");
    }
  } catch (Exception ex) {
    logger.error("Unable to clean up the tmp directory");
  }
}
public void deleteDir(File dir) {
  File[] files = dir.listFiles();
  if (files != null) {
    for (final File file: files) {
      deleteDir(file);
    }
  }
  dir.delete();
}
Cele answered 2/6, 2020 at 16:27 Comment(0)
K
0

This is my solution. I'm using spring boot 2.4.3

Create an amazon s3 client

AmazonS3 amazonS3Client = AmazonS3ClientBuilder
                .standard()
                .withRegion("your-region")
                .withCredentials(
                        new AWSStaticCredentialsProvider(
                            new BasicAWSCredentials("your-access-key", "your-secret-access-key")))
                .build();

Create an amazon transfer client.

TransferManager transferManagerClient = TransferManagerBuilder.standard()
                .withS3Client(amazonS3Client)
                .build();

Create a temporary file in /tmp/{your-s3-key} so that we can put the file we download in this file.

File file = new File(System.getProperty("java.io.tmpdir"), "your-s3-key"); 

try {
    file.createNewFile(); // Create temporary file
} catch (IOException e) {
    e.printStackTrace();
}

file.mkdirs();  // Create the directory of the temporary file

Then, we download the file from s3 using transfer manager client

// Note that in this line the s3 file downloaded has been transferred in to the temporary file that we created
Download download = transferManagerClient.download(
               new GetObjectRequest("your-s3-bucket-name", "your-s3-key"), file); 

// This line blocks the thread until the download is finished
download.waitForCompletion();  

Now that the s3 file has been successfully transferred into the temporary file that we created. We can get the InputStream of the temporary file.

InputStream input = new DataInputStream(new FileInputStream(file));

Because the temporary file is not needed anymore, we just delete it.

file.delete();
Kavanaugh answered 12/3, 2021 at 10:57 Comment(0)
D
0

I resolved a warning issue by incorporating the s3Object.getObjectContent().abort() method.

Further details about my scenario:

My goal was to compress images exceeding 600KB. After fetching the S3Object, I checked and compressed if size > 600KB. However, for sizes <= 600KB, warnings occurred as Try-with-resources closed the connection without calling abort(). Using abort() solved the issue.

Here one question can come in your mind like why need to abort even i did not request content but when you fetch s3Object looks like it also starts fetching content hence we need to abort when we do not need content

try (S3Object s3Object = awsService.getS3Object(s3Bucket, s3PublicPath)) {

  long fileSizeInKB = s3Object.getObjectMetadata().getContentLength() / 1024;
  if (fileSizeInKB > 600) { // code for image compress or resize}

  // after 

  else { s3Object.getObjectContent().abort(); }
}
Dina answered 30/12, 2023 at 7:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.