Skip to content

Commit 48ddba0

Browse files
committed
#16314 fix concurrency issue in Selenium Manager
... when executed by 2+ processes simultaneously on a machine with empty Selenium cache. Every process sees the file created by another process only when the file is fully completed.
1 parent f5ad7eb commit 48ddba0

File tree

1 file changed

+32
-6
lines changed

1 file changed

+32
-6
lines changed

java/src/org/openqa/selenium/manager/SeleniumManager.java

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
// under the License.
1717
package org.openqa.selenium.manager;
1818

19+
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
20+
import static java.util.Objects.requireNonNull;
21+
import static java.util.UUID.randomUUID;
1922
import static org.openqa.selenium.Platform.LINUX;
2023
import static org.openqa.selenium.Platform.MAC;
2124
import static org.openqa.selenium.Platform.UNIX;
@@ -150,12 +153,13 @@ private static Result runCommand(Path binary, List<String> arguments) {
150153
} catch (Exception e) {
151154
throw new WebDriverException("Failed to run command: " + arguments, e);
152155
}
153-
SeleniumManagerOutput jsonOutput = null;
156+
org.openqa.selenium.manager.SeleniumManagerOutput jsonOutput = null;
154157
JsonException failedToParse = null;
155158
String dump = output;
156159
if (!output.isEmpty()) {
157160
try {
158-
jsonOutput = new Json().toType(output, SeleniumManagerOutput.class);
161+
jsonOutput =
162+
new Json().toType(output, org.openqa.selenium.manager.SeleniumManagerOutput.class);
159163
jsonOutput
160164
.getLogs()
161165
.forEach(
@@ -213,11 +217,10 @@ private synchronized Path getBinary() {
213217
}
214218

215219
binary = getBinaryInCache(SELENIUM_MANAGER + extension);
216-
if (!binary.toFile().exists()) {
217-
String binaryPathInJar = String.format("%s/%s%s", folder, SELENIUM_MANAGER, extension);
218-
try (InputStream inputStream = this.getClass().getResourceAsStream(binaryPathInJar)) {
220+
if (!Files.exists(binary)) {
221+
try (InputStream inputStream = findBinaryInClasspath(folder, extension)) {
219222
Files.createDirectories(binary.getParent());
220-
Files.copy(inputStream, binary);
223+
saveToFileSafely(inputStream, binary);
221224
}
222225
}
223226
} catch (Exception e) {
@@ -233,6 +236,29 @@ private synchronized Path getBinary() {
233236
return binary;
234237
}
235238

239+
/**
240+
* Protect from concurrency issue when executed by 2+ processes simultaneously. Every process sees
241+
* the file created by another process only when the file is fully completed.
242+
*/
243+
private void saveToFileSafely(InputStream inputStream, Path target) throws IOException {
244+
Path temporaryFile = target.resolveSibling(target.getFileName() + "." + randomUUID() + ".tmp");
245+
Files.copy(inputStream, temporaryFile);
246+
try {
247+
if (!Files.exists(target)) {
248+
Files.move(temporaryFile, target, REPLACE_EXISTING);
249+
}
250+
} finally {
251+
Files.deleteIfExists(temporaryFile);
252+
}
253+
}
254+
255+
private InputStream findBinaryInClasspath(String folder, String extension) {
256+
String binaryPathInJar = String.format("%s/%s%s", folder, SELENIUM_MANAGER, extension);
257+
return requireNonNull(
258+
getClass().getResourceAsStream(binaryPathInJar),
259+
() -> "Resource not found in classpath: " + binaryPathInJar);
260+
}
261+
236262
/**
237263
* Executes Selenium Manager to get the locations of the requested assets
238264
*

0 commit comments

Comments
 (0)