{"record":{"id":"79aecb245c674989","repo":"apache/iceberg","slug":"failed-to-close-changelog-scan-scan-79aecb","errorCode":null,"errorMessage":"Failed to close changelog scan: + scan","messagePattern":"Failed to close changelog scan: \\+ scan","errorType":"exception","errorClass":"UncheckedIOException","httpStatus":null,"severity":"error","filePath":"spark/v4.0/spark/src/main/java/org/apache/iceberg/spark/source/SparkChangelogScan.java","lineNumber":122,"sourceCode":"  @Override\n  public Batch toBatch() {\n    return new SparkBatch(\n        sparkContext,\n        table,\n        null != scan ? scan.fileIO() : table::io,\n        readConf,\n        EMPTY_GROUPING_KEY_TYPE,\n        taskGroups(),\n        expectedSchema,\n        hashCode());\n  }\n\n  private List<ScanTaskGroup<ChangelogScanTask>> taskGroups() {\n    if (taskGroups == null) {\n      try (CloseableIterable<ScanTaskGroup<ChangelogScanTask>> groups = scan.planTasks()) {\n        this.taskGroups = Lists.newArrayList(groups);\n      } catch (IOException e) {\n        throw new UncheckedIOException(\"Failed to close changelog scan: \" + scan, e);\n      }\n    }\n\n    return taskGroups;\n  }\n\n  @Override\n  public String description() {\n    return String.format(\n        Locale.ROOT,\n        \"%s [fromSnapshotId=%d, toSnapshotId=%d, filters=%s]\",\n        table,\n        startSnapshotId,\n        endSnapshotId,\n        Spark3Util.describe(filters));\n  }\n\n  @Override","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v4.0/spark/src/main/java/org/apache/iceberg/spark/source/SparkChangelogScan.java#L104-L140","documentation":"SparkChangelogScan.taskGroups plans changelog tasks with try-with-resources; if closing the planned task groups' CloseableIterable throws IOException, it is wrapped in UncheckedIOException('Failed to close changelog scan: <scan>'). The planning succeeded but resource cleanup failed, indicating FileIO-level trouble (file system errors while releasing resources).","triggerScenarios":"scan.planTasks() succeeds but the CloseableIterable.close() inside the try-with-resources throws IOException — e.g. HDFS/S3 error during manifest reader close, stream aborted by timeout, or disk/network failure on the executor.","commonSituations":"Object store throttling or token expiry during task-group close; flaky network between Spark executor and storage; HDFS client cache eviction errors.","solutions":["Inspect the chained IOException for the storage-level cause.","Retry the scan/stream micro-batch; close-time errors are usually transient.","Verify storage credentials/refresh (S3 session tokens, HDFS delegation tokens) cover the job duration.","Check network stability and storage endpoint health for executors."],"exampleFix":"// before\ntry (CloseableIterable<...> groups = scan.planTasks()) { ... }\n// after\ntry {\n  try (CloseableIterable<...> groups = scan.planTasks()) { ... }\n} catch (UncheckedIOException e) {\n  if (isTransient(e.getCause())) retry(); else throw e;\n}","handlingStrategy":"retry","validationCode":"// verify storage is reachable before planning\nFileSystem fs = FileSystem.get(table.location(), hadoopConf);\nfs.exists(new Path(table.location())); // throws early on connectivity issues","typeGuard":null,"tryCatchPattern":"try { planTaskGroups(); } catch (UncheckedIOException e) {\n  if (isTransientStorageError(e.getCause())) retryWithBackoff(); else throw e;\n}","preventionTips":["Use stable, highly available storage endpoints for warehouse.","Ensure long-lived credentials (instance profiles, keytabs) instead of short-lived tokens.","Set storage client timeouts/retries at the FileIO level."],"tags":["spark","changelog","io","resource-cleanup"],"backgroundTag":"file-read-failed","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}