62
62
import java .io .IOException ;
63
63
import java .io .InputStream ;
64
64
import java .io .LineNumberReader ;
65
+ import java .io .PrintWriter ;
66
+ import java .io .StringWriter ;
65
67
import java .io .UncheckedIOException ;
66
68
import java .net .URL ;
67
69
import java .nio .charset .StandardCharsets ;
@@ -489,6 +491,13 @@ public void freeze() {
489
491
configurationFrozen .set (true );
490
492
}
491
493
494
+ private static String throwableToString (Throwable t ) {
495
+ StringWriter sw = new StringWriter ();
496
+ PrintWriter pw = new PrintWriter (sw );
497
+ t .printStackTrace (pw );
498
+ return sw .toString ();
499
+ }
500
+
492
501
@ Override
493
502
public synchronized void start () {
494
503
LOGGER .info ("Starting `{}`" , this );
@@ -505,19 +514,23 @@ public synchronized void start() {
505
514
// make sure we always start fresh
506
515
if (Files .exists (workingDir )) {
507
516
if (preserveDataDir ) {
508
- Files .list (workingDir )
509
- .filter (path -> path .equals (confPathData ) == false )
510
- .forEach (path -> fileSystemOperations .delete (d -> d .delete (path )));
517
+ Files .list (workingDir ).filter (path -> path .equals (confPathData ) == false ).forEach (this ::uncheckedDeleteWithRetry );
511
518
} else {
512
- fileSystemOperations . delete ( d -> d . delete ( workingDir ) );
519
+ deleteWithRetry ( workingDir );
513
520
}
514
521
}
515
522
isWorkingDirConfigured = true ;
516
523
}
517
524
setupNodeDistribution (getExtractedDistributionDir ());
518
525
createWorkingDir ();
519
526
} catch (IOException e ) {
520
- throw new UncheckedIOException ("Failed to create working directory for " + this , e );
527
+ String msg = "Failed to create working directory for " + this + ", with: " + e + throwableToString (e );
528
+ logToProcessStdout (msg );
529
+ throw new UncheckedIOException (msg , e );
530
+ } catch (org .gradle .api .UncheckedIOException e ) {
531
+ String msg = "Failed to create working directory for " + this + ", with: " + e + throwableToString (e );
532
+ logToProcessStdout (msg );
533
+ throw e ;
521
534
}
522
535
523
536
copyExtraJars ();
@@ -1192,9 +1205,75 @@ private void waitForProcessToExit(ProcessHandle processHandle) {
1192
1205
}
1193
1206
}
1194
1207
1208
+ private static final int RETRY_DELETE_MILLIS = OS .current () == OS .WINDOWS ? 500 : 0 ;
1209
+ private static final int MAX_RETRY_DELETE_TIMES = OS .current () == OS .WINDOWS ? 15 : 0 ;
1210
+
1211
+ /**
1212
+ * Deletes a path, retrying if necessary.
1213
+ *
1214
+ * @param path the path to delete
1215
+ * @throws IOException
1216
+ * if an I/O error occurs
1217
+ */
1218
+ void deleteWithRetry (Path path ) throws IOException {
1219
+ try {
1220
+ deleteWithRetry0 (path );
1221
+ } catch (InterruptedException x ) {
1222
+ throw new IOException ("Interrupted while deleting." , x );
1223
+ }
1224
+ }
1225
+
1226
+ /** Unchecked variant of deleteWithRetry. */
1227
+ void uncheckedDeleteWithRetry (Path path ) {
1228
+ try {
1229
+ deleteWithRetry0 (path );
1230
+ } catch (IOException e ) {
1231
+ throw new UncheckedIOException (e );
1232
+ } catch (InterruptedException x ) {
1233
+ throw new UncheckedIOException ("Interrupted while deleting." , new IOException ());
1234
+ }
1235
+ }
1236
+
1237
+ // The exception handling here is loathsome, but necessary!
1238
+ private void deleteWithRetry0 (Path path ) throws IOException , InterruptedException {
1239
+ int times = 0 ;
1240
+ IOException ioe = null ;
1241
+ while (true ) {
1242
+ try {
1243
+ fileSystemOperations .delete (d -> d .delete (path ));
1244
+ times ++;
1245
+ // Checks for absence of the file. Semantics of Files.exists() is not the same.
1246
+ while (Files .notExists (path ) == false ) {
1247
+ if (times > MAX_RETRY_DELETE_TIMES ) {
1248
+ throw new IOException ("File still exists after " + times + " waits." );
1249
+ }
1250
+ Thread .sleep (RETRY_DELETE_MILLIS );
1251
+ // retry
1252
+ fileSystemOperations .delete (d -> d .delete (path ));
1253
+ times ++;
1254
+ }
1255
+ break ;
1256
+ } catch (NoSuchFileException ignore ) {
1257
+ // already deleted, ignore
1258
+ break ;
1259
+ } catch (org .gradle .api .UncheckedIOException | IOException x ) {
1260
+ if (x .getCause () instanceof NoSuchFileException ) {
1261
+ // already deleted, ignore
1262
+ break ;
1263
+ }
1264
+ // Backoff/retry in case another process is accessing the file
1265
+ times ++;
1266
+ if (ioe == null ) ioe = new IOException ();
1267
+ ioe .addSuppressed (x );
1268
+ if (times > MAX_RETRY_DELETE_TIMES ) throw ioe ;
1269
+ Thread .sleep (RETRY_DELETE_MILLIS );
1270
+ }
1271
+ }
1272
+ }
1273
+
1195
1274
private void createWorkingDir () throws IOException {
1196
1275
// Start configuration from scratch in case of a restart
1197
- fileSystemOperations . delete ( d -> d . delete ( configFile .getParent () ));
1276
+ deleteWithRetry ( configFile .getParent ());
1198
1277
Files .createDirectories (configFile .getParent ());
1199
1278
Files .createDirectories (confPathRepo );
1200
1279
Files .createDirectories (confPathData );
0 commit comments