1616// under the License.
1717package 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 ;
1922import static org .openqa .selenium .Platform .LINUX ;
2023import static org .openqa .selenium .Platform .MAC ;
2124import 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