{"record":{"id":"19c596be9687c084","repo":"apache/iceberg","slug":"failed-to-close-table-loader","errorCode":null,"errorMessage":"Failed to close table loader","messagePattern":"Failed to close table loader","errorType":"exception","errorClass":"UncheckedIOException","httpStatus":null,"severity":"error","filePath":"flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/source/IcebergSource.java","lineNumber":166,"sourceCode":"   */\n  private List<IcebergSourceSplit> planSplitsForBatch(String threadName) {\n    if (batchSplits != null) {\n      return batchSplits;\n    }\n\n    ExecutorService workerPool =\n        ThreadPools.newFixedThreadPool(threadName, scanContext.planParallelism());\n    try (TableLoader loader = tableLoader.clone()) {\n      loader.open();\n      this.batchSplits =\n          FlinkSplitPlanner.planIcebergSourceSplits(loader.loadTable(), scanContext, workerPool);\n      LOG.info(\n          \"Discovered {} splits from table {} during job initialization\",\n          batchSplits.size(),\n          tableName);\n      return batchSplits;\n    } catch (IOException e) {\n      throw new UncheckedIOException(\"Failed to close table loader\", e);\n    } finally {\n      workerPool.shutdown();\n    }\n  }\n\n  @Override\n  public Boundedness getBoundedness() {\n    return scanContext.isStreaming() ? Boundedness.CONTINUOUS_UNBOUNDED : Boundedness.BOUNDED;\n  }\n\n  @Override\n  public SourceReader<T, IcebergSourceSplit> createReader(SourceReaderContext readerContext) {\n    IcebergSourceReaderMetrics metrics =\n        new IcebergSourceReaderMetrics(readerContext.metricGroup(), tableName);\n    return new IcebergSourceReader<>(\n        emitter, metrics, readerFunction, splitComparator, readerContext);\n  }\n","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/source/IcebergSource.java#L148-L184","documentation":"IcebergSource.planSplitsForBatch plans batch splits inside a try-with-resources on the TableLoader; an IOException during closing the table loader is wrapped in UncheckedIOException with the (arguably misleading) message 'Failed to close table loader'. The failure occurs during batch split discovery when the loader's IO cannot be cleanly closed after planning.","triggerScenarios":"Batch mode IcebergSource split planning where TableLoader.close() throws IOException (e.g. underlying FileSystem/FileIO close fails, cached connections broken), or an earlier IOException from planning propagated through the resource-close path.","commonSituations":"Hadoop FileSystem cache/close issues, storage clients with leaked connections after long-running jobs, custom TableLoader implementations throwing on close.","solutions":["Check the chained IOException cause to see whether the real failure is close() or the planning step","Upgrade Iceberg — loader close failures are often fixed in newer versions' FileIO implementations","If using a custom TableLoader, make close() idempotent and null-safe","Retry job startup; investigate storage connectivity if close failures recur"],"exampleFix":"// before\nFlinkSource.forRowData().tableLoader(customLoader).build(); // customLoader.close() throws\n// after: make close idempotent\npublic void close() {\n  if (!closed) {\n    closed = true;\n    io.close();\n  }\n}","handlingStrategy":"try-catch","validationCode":"// ensure the custom loader closes cleanly\nTableLoader loader = TableLoader.fromCatalog(...);\nloader.open();\nloader.loadTable();\nloader.close();","typeGuard":null,"tryCatchPattern":"try {\n  source = IcebergSource.forRowData().tableLoader(loader).build();\n} catch (UncheckedIOException e) {\n  LOG.error(\"Table loader lifecycle failure\", e.getCause());\n  throw e;\n}","preventionTips":["Use stock TableLoader implementations where possible","Make custom TableLoader.close() idempotent","Upgrade Iceberg if close-time IOExceptions appear in FileIO implementations"],"tags":["flink","source","io","table-loader","close"],"backgroundTag":"resource-cleanup-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"}