{"record":{"id":"d679827285b91228","repo":"apache/iceberg","slug":"unclosed-s3fileio-instance-created-by-n-t","errorCode":null,"errorMessage":"Unclosed S3FileIO instance created by:\\n\\t{}","messagePattern":"Unclosed S3FileIO instance created by:\\\\n\\\\t(.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"aws/src/main/java/org/apache/iceberg/aws/s3/S3FileIO.java","lineNumber":562,"sourceCode":"      }\n      if (refreshFuture != null) {\n        refreshFuture.cancel(true);\n        refreshFuture = null;\n      }\n    }\n  }\n\n  @SuppressWarnings({\"checkstyle:NoFinalizer\", \"Finalize\", \"deprecation\"})\n  @Override\n  protected void finalize() throws Throwable {\n    super.finalize();\n    if (!isResourceClosed.get()) {\n      close();\n\n      if (null != createStack) {\n        String trace =\n            Joiner.on(\"\\n\\t\").join(Arrays.copyOfRange(createStack, 1, createStack.length));\n        LOG.warn(\"Unclosed S3FileIO instance created by:\\n\\t{}\", trace);\n      }\n    }\n  }\n\n  @Override\n  public boolean recoverFile(String path) {\n    PrefixedS3Client client = clientForStoragePath(path);\n    S3URI location = new S3URI(path, client.s3FileIOProperties().bucketToAccessPointMapping());\n    ListObjectVersionsIterable response =\n        client\n            .s3()\n            .listObjectVersionsPaginator(\n                builder -> builder.bucket(location.bucket()).prefix(location.key()));\n\n    // Recover to the last modified version, not isLatest,\n    // since isLatest is true for deletion markers.\n    Optional<ObjectVersion> recoverVersion =\n        response.versions().stream().max(Comparator.comparing(ObjectVersion::lastModified));","sourceCodeStart":544,"sourceCodeEnd":580,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/aws/src/main/java/org/apache/iceberg/aws/s3/S3FileIO.java#L544-L580","documentation":"S3FileIO.finalize() detects FileIO instances that were never close()d before garbage collection. It closes the resource, then logs this warning with the stack trace captured at creation time so developers can find the leak. Unclosed S3FileIO instances leak executor threads and credential-refresh schedulers.","triggerScenarios":"Application code creates S3FileIO (directly or via a catalog/loader) and never calls close(), then the instance becomes garbage.","commonSituations":"Per-file S3FileIO instantiation instead of sharing one; jobs that build Hadoop-aware FileIO without try-with-resources; leaked references in long-running services (Spark/Flink tasks).","solutions":["Close S3FileIO in try-with-resources or a finally block","Reuse a single S3FileIO instance across operations instead of creating per call","Use the logged create-stack trace to locate the leaking construction site","Ensure catalog-managed FileIO instances are closed when the table/catalog is discarded"],"exampleFix":"// before\nFileIO io = new S3FileIO();\nio.deleteFile(path); // never closed\n// after\ntry (FileIO io = new S3FileIO()) {\n  io.deleteFile(path);\n}","handlingStrategy":"type-guard","validationCode":"// assert closure at end of job lifecycle\nif (!ioClosed) throw new IllegalStateException(\"S3FileIO must be closed before shutdown\");","typeGuard":null,"tryCatchPattern":"try (S3FileIO io = new S3FileIO()) { io.initialize(props); /* use io */ }","preventionTips":["Always use try-with-resources for S3FileIO","Share one instance per job/table rather than instantiating per file","Treat the create-stack in this warning as the definitive leak location"],"tags":["resource-leak","finalizer","s3","logging"],"backgroundTag":"resource-not-closed","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}