Skip to content

Commit 8f47287

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 8f47287

File tree

1 file changed

+28
-6
lines changed

1 file changed

+28
-6
lines changed

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

Lines changed: 28 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.ATOMIC_MOVE;
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,12 @@ 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 = new Json().toType(output, org.openqa.selenium.manager.SeleniumManagerOutput.class);
159162
jsonOutput
160163
.getLogs()
161164
.forEach(
@@ -213,11 +216,10 @@ private synchronized Path getBinary() {
213216
}
214217

215218
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)) {
219+
if (!Files.exists(binary)) {
220+
try (InputStream inputStream = findBinaryInClasspath(folder, extension)) {
219221
Files.createDirectories(binary.getParent());
220-
Files.copy(inputStream, binary);
222+
saveToFileSafely(inputStream, binary);
221223
}
222224
}
223225
} catch (Exception e) {
@@ -233,6 +235,26 @@ private synchronized Path getBinary() {
233235
return binary;
234236
}
235237

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

0 commit comments

Comments
 (0)