Repository: adobe/s3mock
Issue: Race condition for temp dir creation

Hi there,

we are running S3Mock embedded using the `S3MockExtension`. I know that this scenario is deprecated. ;-)

After adding a second unit test class using `S3MockExtension` we are regularly seeing root folder creation failures in CI where two threads generate the same tmp file name and only one of them succeeds with `mkdir` in `StoreConfiguration#rootFolder`.

I have prepared a PR that hopefully fixes this issue by using `Files#createTempDirectory`, which is thread-safe.

Regards,
Thilo

```
01:44:23.689 [ForkJoinPool-1-worker-1] INFO  com.adobe.testing.s3mock.store.StoreConfiguration - Successfully created "/tmp/s3mockFileStore1664243063689" as root folder. Will retain files on exit: false
01:44:23.692 [ForkJoinPool-1-worker-8] WARN  org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bucketStore' defined in class path resource [com/adobe/testing/s3mock/store/StoreConfiguration.class]: Unsatisfied dependency expressed through method 'bucketStore' parameter 1; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bucketRootFolder' defined in class path resource [com/adobe/testing/s3mock/store/StoreConfiguration.class]: Unsatisfied dependency expressed through method 'bucketRootFolder' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'rootFolder' defined in class path resource [com/adobe/testing/s3mock/store/StoreConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [java.io.File]: Factory method 'rootFolder' threw exception; nested exception is java.lang.IllegalStateException: Root folder could not be created. Path: /tmp/s3mockFileStore1664243063689
```

Changed files:
server/src/main/java/com/adobe/testing/s3mock/store/StoreConfiguration.java

diff --git a/server/src/main/java/com/adobe/testing/s3mock/store/StoreConfiguration.java b/server/src/main/java/com/adobe/testing/s3mock/store/StoreConfiguration.java
index 1a29f6fb..e11f0d32 100644
--- a/server/src/main/java/com/adobe/testing/s3mock/store/StoreConfiguration.java
+++ b/server/src/main/java/com/adobe/testing/s3mock/store/StoreConfiguration.java
@@ -18,9 +18,11 @@ package com.adobe.testing.s3mock.store;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
-import java.util.Date;
 import org.apache.commons.io.FileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -69,24 +71,37 @@ public class StoreConfiguration {
   @Bean
   File rootFolder(StoreProperties properties) {
     final File root;
-    if (properties.getRoot() == null || properties.getRoot().isEmpty()) {
-      root = new File(FileUtils.getTempDirectory(), "s3mockFileStore" + new Date().getTime());
+    final boolean createTempDir = properties.getRoot() == null || properties.getRoot().isEmpty();
+
+    if (createTempDir) {
+      final Path baseTempDir = FileUtils.getTempDirectory().toPath();
+      try {
+        root = Files.createTempDirectory(baseTempDir, "s3mockFileStore").toFile();
+      } catch (IOException e) {
+        throw new IllegalStateException("Root folder could not be created. Base temp dir: "
+            + baseTempDir, e);
+      }
+
+      LOG.info("Successfully created \\"{}\\" as root folder. Will retain files on exit: {}",
+          root.getAbsolutePath(), properties.isRetainFilesOnExit());
     } else {
       root = new File(properties.getRoot());
+
+      if (root.exists()) {
+        LOG.info("Using existing folder \\"{}\\" as root folder. Will retain files on exit: {}",
+            root.getAbsolutePath(), properties.isRetainFilesOnExit());
+      } else if (!root.mkdir()) {
+        throw new IllegalStateException("Root folder could not be created. Path: "
+            + root.getAbsolutePath());
+      } else {
+        LOG.info("Successfully created \\"{}\\" as root folder. Will retain files on exit: {}",
+            root.getAbsolutePath(), properties.isRetainFilesOnExit());
+      }
     }
+
     if (!properties.isRetainFilesOnExit()) {
       root.deleteOnExit();
     }
-    if (root.exists()) {
-      LOG.info("Using existing folder \\"{}\\" as root folder. Will retain files on exit: {}",
-          root.getAbsolutePath(), properties.isRetainFilesOnExit());
-    } else if (!root.mkdir()) {
-      throw new IllegalStateException("Root folder could not be created. Path: "
-          + root.getAbsolutePath());
-    } else {
-      LOG.info("Successfully created \\"{}\\" as root folder. Will retain files on exit: {}",
-          root.getAbsolutePath(), properties.isRetainFilesOnExit());
-    }
 
     return root;
   }