Skip to content

API: Close encryption manager after FileIO close failure#17329

Open
hkwi wants to merge 1 commit into
apache:mainfrom
hkwi:fix/encrypting-fileio-close
Open

API: Close encryption manager after FileIO close failure#17329
hkwi wants to merge 1 commit into
apache:mainfrom
hkwi:fix/encrypting-fileio-close

Conversation

@hkwi

@hkwi hkwi commented Jul 22, 2026

Copy link
Copy Markdown

Summary

  • close a closeable encryption manager even when the delegate FileIO fails to close
  • retain the delegate failure as the primary exception and suppress a subsequent encryption-manager close failure
  • add regression coverage for both failure paths

Motivation

EncryptingFileIO.close() currently closes its delegate before closing a closeable encryption
manager. If the delegate throws, the encryption manager is never closed. This can leak resources
owned by the encryption manager, such as a KMS client.

The updated implementation uses try-with-resources to preserve the existing close order while
ensuring both resources are closed with standard suppressed-exception behavior.

This was identified while reviewing the KMS client lifecycle for Apache Polaris:

apache/polaris#5060 (comment)

Testing

  • ./gradlew :iceberg-api:test --tests org.apache.iceberg.encryption.TestEncryptingFileIO --no-daemon --console=plain
  • ./gradlew :iceberg-api:spotlessCheck --no-daemon --console=plain
  • ./gradlew :iceberg-api:check --no-daemon --console=plain

AI Disclosure

  • Model: GPT-5
  • Platform/Tool: Codex
  • Human Oversight: partially reviewed
  • Prompt Summary: Fix EncryptingFileIO.close() so all owned resources are closed safely when the delegate close fails, and add regression tests.

Ensure a closeable encryption manager is closed even when the delegate FileIO fails to close, while preserving secondary close failures as suppressed exceptions.

Generated-by: Codex
@github-actions github-actions Bot added the API label Jul 22, 2026
@hkwi
hkwi marked this pull request as ready for review July 22, 2026 03:07
public void close() {
io.close();

if (em instanceof Closeable) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this implementation we write io.close() twice. Maybe running it first as before but in a try-finally block results cleaner code. What I have in mind:

try {
      io.close();
    } finally {
      if (em instanceof Closeable closeableManager) {
        try {
          closeableManager.close();
        } catch (IOException e) {
          throw new UncheckedIOException("Failed to close encryption manager", e);
        }
      }
    }

WDYT?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point — I agree that keeping io.close() in one place would make this cleaner. How about using a nullable try-with-resources resource instead?

@Override
public void close() {
  try (Closeable closeableManager =
      em instanceof Closeable ? (Closeable) em : null) {
    io.close();
  } catch (IOException e) {
    throw new UncheckedIOException("Failed to close encryption manager", e);
  }
}

This removes the duplication while preserving try-with-resources suppression semantics. If both io.close() and closeableManager.close() fail, the FileIO failure remains the primary exception and the manager failure is added as suppressed. With the proposed finally, the UncheckedIOException from the manager would replace the FileIO failure. WDYT?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems good to me. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants