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