elastic/elasticsearch · error · IOException

File still exists after {} waits.

Error message

File still exists after {} waits.

What it means

Thrown inside deleteWithRetry0 when, after MAX_RETRY_DELETE_TIMES attempts (15 on Windows, 0 elsewhere), Files.notExists(path) is still false — the file survived every delete call. This is a Windows-specific retry path; on Unix the retry loop is effectively disabled (0 iterations) so this throw is Windows-only in practice.

Source

Thrown at build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java:1262

        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } catch (InterruptedException x) {
            throw new UncheckedIOException("Interrupted while deleting.", new IOException());
        }
    }

    // The exception handling here is loathsome, but necessary!
    private void deleteWithRetry0(Path path) throws IOException, InterruptedException {
        int times = 0;
        IOException ioe = null;
        while (true) {
            try {
                fileSystemOperations.delete(d -> d.delete(path));
                times++;
                // Checks for absence of the file. Semantics of Files.exists() is not the same.
                while (Files.notExists(path) == false) {
                    if (times > MAX_RETRY_DELETE_TIMES) {
                        throw new IOException("File still exists after " + times + " waits.");
                    }
                    Thread.sleep(RETRY_DELETE_MILLIS);
                    // retry
                    fileSystemOperations.delete(d -> d.delete(path));
                    times++;
                }
                break;
            } catch (NoSuchFileException ignore) {
                // already deleted, ignore
                break;
            } catch (org.gradle.api.UncheckedIOException | IOException x) {
                if (x.getCause() instanceof NoSuchFileException) {
                    // already deleted, ignore
                    break;
                }
                // Backoff/retry in case another process is accessing the file
                times++;
                if (ioe == null) ioe = new IOException();

View on GitHub (pinned to db6a809a66)

Solutions

  1. Add antivirus exclusions for the gradle build / testclusters directory on Windows.
  2. Ensure all child processes (ML, watchers) are stopped before teardown — check the node's children list in stopHandle.
  3. Close any file explorer / IDE that has the data dir open.
  4. As a last resort, manually delete the leftover path under build/testclusters and re-run.
Defensive patterns

Strategy: retry

Prevention

When it happens

Trigger: fileSystemOperations.delete succeeds without throwing but the file remains on disk (or reappears), and Files.notExists stays false for 15 attempts spaced 500ms apart (total ~7.5s). Classic Windows behavior where a file handle is held by another process.

Common situations: Antivirus or search indexer holds a handle on a shard/lock file under the testcluster data dir; a child ML/monitoring process is still dying and holds the file; Windows file-delete semantics where the file is marked for deletion but not gone until the last handle closes.

Related errors


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/01e60f402406ec9e. Report an issue: GitHub.