diff --git a/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/S3AFileSystem.java b/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/S3AFileSystem.java index 6a6c26b24a..dffef15100 100644 --- a/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/S3AFileSystem.java +++ b/hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/S3AFileSystem.java @@ -31,6 +31,7 @@ import java.util.concurrent.ExecutorService; import java.util.Objects; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonServiceException; @@ -121,6 +122,7 @@ public class S3AFileSystem extends FileSystem { private S3AStorageStatistics storageStatistics; private long readAhead; private S3AInputPolicy inputPolicy; + private final AtomicBoolean closed = new AtomicBoolean(false); // The maximum number of entries that can be deleted in any call to s3 private static final int MAX_ENTRIES_TO_DELETE = 1000; @@ -1414,7 +1416,11 @@ private void innerCopyFromLocalFile(boolean delSrc, boolean overwrite, * @throws IOException IO problem */ @Override - public synchronized void close() throws IOException { + public void close() throws IOException { + if (closed.getAndSet(true)) { + // already closed + return; + } try { super.close(); } finally { diff --git a/hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/ITestS3AConfiguration.java b/hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/ITestS3AConfiguration.java index fca8e491da..b08bfe933d 100644 --- a/hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/ITestS3AConfiguration.java +++ b/hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/ITestS3AConfiguration.java @@ -409,6 +409,14 @@ public void testCustomUserAgent() throws Exception { awsConf.getUserAgent()); } + @Test + public void testCloseIdempotent() throws Throwable { + conf = new Configuration(); + fs = S3ATestUtils.createTestFileSystem(conf); + fs.close(); + fs.close(); + } + /** * Reads and returns a field from an object using reflection. If the field * cannot be found, is null, or is not the expected type, then this method