diff --git a/java/src/org/openqa/selenium/manager/SeleniumManager.java b/java/src/org/openqa/selenium/manager/SeleniumManager.java index 97e23c85f4cb6..606fb5235e7a1 100644 --- a/java/src/org/openqa/selenium/manager/SeleniumManager.java +++ b/java/src/org/openqa/selenium/manager/SeleniumManager.java @@ -16,6 +16,9 @@ // under the License. package org.openqa.selenium.manager; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static java.util.Objects.requireNonNull; +import static java.util.UUID.randomUUID; import static org.openqa.selenium.Platform.LINUX; import static org.openqa.selenium.Platform.MAC; import static org.openqa.selenium.Platform.UNIX; @@ -213,11 +216,10 @@ private synchronized Path getBinary() { } binary = getBinaryInCache(SELENIUM_MANAGER + extension); - if (!binary.toFile().exists()) { - String binaryPathInJar = String.format("%s/%s%s", folder, SELENIUM_MANAGER, extension); - try (InputStream inputStream = this.getClass().getResourceAsStream(binaryPathInJar)) { + if (!Files.exists(binary)) { + try (InputStream inputStream = findBinaryInClasspath(folder, extension)) { Files.createDirectories(binary.getParent()); - Files.copy(inputStream, binary); + saveToFileSafely(inputStream, binary); } } } catch (Exception e) { @@ -233,6 +235,29 @@ private synchronized Path getBinary() { return binary; } + /** + * Protect from concurrency issue when executed by 2+ processes simultaneously. Every process sees + * the file created by another process only when the file is fully completed. + */ + private void saveToFileSafely(InputStream inputStream, Path target) throws IOException { + Path temporaryFile = target.resolveSibling(target.getFileName() + "." + randomUUID() + ".tmp"); + Files.copy(inputStream, temporaryFile); + try { + if (!Files.exists(target)) { + Files.move(temporaryFile, target, REPLACE_EXISTING); + } + } finally { + Files.deleteIfExists(temporaryFile); + } + } + + private InputStream findBinaryInClasspath(String folder, String extension) { + String binaryPathInJar = String.format("%s/%s%s", folder, SELENIUM_MANAGER, extension); + return requireNonNull( + getClass().getResourceAsStream(binaryPathInJar), + () -> "Resource not found in classpath: " + binaryPathInJar); + } + /** * Executes Selenium Manager to get the locations of the requested assets *