apache/iceberg · error · RuntimeIOException

Failed to close

Error message

Failed to close

What it means

DeleteFileIndex.loadDeleteFiles reads manifest entries inside Tasks.foreach with a CloseableIterable; an IOException while closing/reading the iterable during parallel loading is wrapped in RuntimeIOException with message 'Failed to close'. The deletes index therefore fails to build.

Source

Thrown at core/src/main/java/org/apache/iceberg/DeleteFileIndex.java:516

          .throwFailureWhenFinished()
          .executeWith(executorService)
          .run(
              deleteFile -> {
                try (CloseableIterable<ManifestEntry<DeleteFile>> reader = deleteFile) {
                  for (ManifestEntry<DeleteFile> entry : reader) {
                    if (entry.dataSequenceNumber() > minSequenceNumber) {
                      DeleteFile file = entry.file();
                      // keep minimum stats to avoid memory pressure
                      Set<Integer> columns =
                          file.content() == FileContent.POSITION_DELETES
                              ? Set.of(MetadataColumns.DELETE_FILE_PATH.fieldId())
                              : Set.copyOf(file.equalityFieldIds());
                      // copy with stats for better filtering against data file stats
                      files.add(ContentFileUtil.copy(file, true, columns));
                    }
                  }
                } catch (IOException e) {
                  throw new RuntimeIOException(e, "Failed to close");
                }
              });
      return files;
    }

    private Collection<Schema> schemas() {
      if (schemasById != null) {
        return schemasById.values();
      } else {
        return specsById.values().stream().map(PartitionSpec::schema).collect(Collectors.toList());
      }
    }

    DeleteFileIndex build() {
      Map<Integer, Types.NestedField> fieldsById = Schema.indexFields(schemas());
      Function<Integer, Types.NestedField> fieldLookup = fieldsById::get;
      Iterable<DeleteFile> files = deleteFiles != null ? filterDeleteFiles() : loadDeleteFiles();

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Inspect the cause (e.getCause()) for the underlying IOException and fix storage access/credentials.
  2. Retry the scan; transient cloud storage errors often resolve.
  3. Ensure snapshot expiry is not racing with scans; stop expiry during active query windows.
  4. Verify delete manifests referenced by the snapshot still exist and are readable.

Example fix

// before
CloseableIterable<...> items = ...; // leaked, failure occurs on implicit close
// after
try (CloseableIterable<...> items = ...) {
  ... // ensure proper resource handling around manifest reads
}
Defensive patterns

Strategy: retry

Validate before calling

// before scanning, verify delete manifests are present
table.operations().current().currentSnapshot().dataManifests(table.io())
    .forEach(m -> Preconditions.check(table.io().newInputFile(m.path()).exists(), "missing " + m.path()));

Try / catch

try { CloseableIterable<DeleteFile> f = index.files(); } catch (RuntimeIOException e) {
  LOG.error("delete index load failed", e.getCause());
  throw e;
}

Prevention

When it happens

Trigger: Building a DeleteFileIndex (e.g. via files() during a scan) where reading or closing a delete-manifest stream throws IOException — storage outage, deleted/missing manifest, credentials revoked mid-scan.

Common situations: S3/GCS transient errors during large delete-index loads; concurrent snapshot expiration deleting manifests while a scan plans; misconfigured FileIO credentials.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/bf6712c9f28762ca. Report an issue: GitHub.