Skip to content

Fixes issues in CloseShieldChannel#799

Merged
garydgregory merged 9 commits intomasterfrom
fix/close-shield-channel-interface-detection
Oct 16, 2025
Merged

Fixes issues in CloseShieldChannel#799
garydgregory merged 9 commits intomasterfrom
fix/close-shield-channel-interface-detection

Conversation

@ppkarwasz
Copy link
Contributor

Two bugs in the CloseShieldChannel helper make it unreliable in practice:

  1. Type-erasure bug in T wrap(T) The method signature only works correctly when T is an interface extending Channel. Since Java’s type system doesn’t allow constraining T to “interface types only,” this could lead to unexpected runtime ClassCastExceptions even though the code compiles successfully.

  2. Incomplete interface discovery The implementation only inspected interfaces directly implemented by the given channel’s class, ignoring those inherited from its superclasses. As a result, proxies for types such as FileChannel did not expose any of the interfaces declared on FileChannel itself.

Fixes

This PR addresses both issues:

  • Reworks the API signature

    • Replaces T wrap(T) with its erasure: Channel wrap(Channel).
    • Introduces a new overload: T wrap(T, Class<T>), which allows callers to explicitly specify the interface type they expect. This version fails fast with a clear IllegalArgumentException if the provided type is not an interface, instead of allowing a ClassCastException later.
  • Improves interface collection logic

    • Updates the implementation to include interfaces declared on superclasses, ensuring all relevant Channel interfaces are correctly proxied.

Two bugs in the `CloseShieldChannel` helper make it unreliable in practice:

1. **Type-erasure bug in `T wrap(T)`**
   The method signature only works correctly when `T` is an **interface** extending `Channel`.
   Since Java’s type system doesn’t allow constraining `T` to “interface types only,” this could lead to unexpected runtime `ClassCastException`s even though the code compiles successfully.

2. **Incomplete interface discovery**
   The implementation only inspected interfaces **directly** implemented by the given channel’s class, ignoring those inherited from its superclasses.
   As a result, proxies for types such as `FileChannel` did not expose any of the interfaces declared on `FileChannel` itself.

#### Fixes

This PR addresses both issues:

* **Reworks the API signature**

  * Replaces `T wrap(T)` with its erasure: `Channel wrap(Channel)`.
  * Introduces a new overload: `T wrap(T, Class<T>)`, which allows callers to explicitly specify the interface type they expect.
    This version fails fast with a clear `IllegalArgumentException` if the provided type is not an interface, instead of allowing a `ClassCastException` later.

* **Improves interface collection logic**

  * Updates the implementation to include interfaces declared on superclasses, ensuring all relevant `Channel` interfaces are correctly proxied.
Copy link
Member

@garydgregory garydgregory left a comment

Choose a reason for hiding this comment

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

Hello @ppkarwasz

The bug fix in collectChannelInterfaces makes sense, but I'm not sure about the rest of the PR regarding generics changes.

Unlike all of our other IO shield classes, this shield implementation uses proxies, which should be an internal detail, but isn't. Using proxies is nice because it allows us a single implementation for channels, but it doesn't cover all cases, like the test points out with FileChannel, a class implementing many Channel interfaces.

Please see the comments I have in the PR.

I can bring in the collectChannelInterfaces change separately if that helps (I have it locally as I've been experimenting).

TY!

This PR is split from #799.

The `CloseShieldChannel` implementation only inspects interfaces **directly** implemented by the given channel’s class, ignoring those inherited from its superclasses.
As a result, proxies for types such as `FileChannel` does not expose any of the interfaces declared on `FileChannel` itself.
@ppkarwasz
Copy link
Contributor Author

I can bring in the collectChannelInterfaces change separately if that helps (I have it locally as I've been experimenting).

As you know, I prefer PRs over direct commits to the master branch. I split off the collectChannelInterfaces problem into #800, so we concentrate on the generics problem here.

@ppkarwasz
Copy link
Contributor Author

@garydgregory,

You initial concern about CloseShieldChannel being overly complex due to its use of proxies was spot on: the current implementation does not correctly handle many interfaces (like AsynchronousByteChannel).

While it would be nice in theory to support every possible Channel sub-interface, the practical utility of doing so hasn’t really been demonstrated.
So in this PR, I’ve taken a step back and simplified the design:

  • Limited the proxy to implement only those interfaces that are actually covered by unit tests.
  • Removed the generic T wrap(T) method: it can never be implemented correctly without generating class proxies (and that would be massive overkill here).
  • Introduced a simpler Channel wrap(Channel) method plus a few overloads for the most common interface types (those typically returned by Files and Channels factory methods).

In practice, the only real-world use case I’ve found so far is wrapping a ReadableByteChannel to consume and close a ChecksumInputStream without closing its underlying channel.

garydgregory pushed a commit that referenced this pull request Oct 14, 2025
This PR is split from #799.

The `CloseShieldChannel` implementation only inspects interfaces **directly** implemented by the given channel’s class, ignoring those inherited from its superclasses.
As a result, proxies for types such as `FileChannel` does not expose any of the interfaces declared on `FileChannel` itself.
Copy link
Member

@garydgregory garydgregory left a comment

Choose a reason for hiding this comment

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

Hi @ppkarwasz
See my comment.

@garydgregory garydgregory merged commit 08573a6 into master Oct 16, 2025
21 checks passed
@garydgregory garydgregory deleted the fix/close-shield-channel-interface-detection branch October 16, 2025 14:02
static Stream<Class<? extends Channel>> testedInterfaces() {
// @formatter:off
return Stream.of(
AsynchronousByteChannel.class,
Copy link
Member

Choose a reason for hiding this comment

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

Hello @ppkarwasz
Why have these interfaces been removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They would require additional code to work correctly.

AsynchronousByteChannel.read and AsynchronousByteChannel.write for example do not throw an exception after close(), but report the problem through Future or CompletionHandler.

Copy link
Member

Choose a reason for hiding this comment

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

Ahhh, thank you for the clarification! 😊

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants