Fixes issues in CloseShieldChannel#799
Conversation
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.
garydgregory
left a comment
There was a problem hiding this comment.
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!
src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java
Outdated
Show resolved
Hide resolved
src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java
Outdated
Show resolved
Hide resolved
src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java
Outdated
Show resolved
Hide resolved
src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
Outdated
Show resolved
Hide resolved
src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java
Outdated
Show resolved
Hide resolved
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.
As you know, I prefer PRs over direct commits to the |
|
You initial concern about While it would be nice in theory to support every possible
In practice, the only real-world use case I’ve found so far is wrapping a |
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.
…annel-interface-detection
garydgregory
left a comment
There was a problem hiding this comment.
Hi @ppkarwasz
See my comment.
src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java
Outdated
Show resolved
Hide resolved
| static Stream<Class<? extends Channel>> testedInterfaces() { | ||
| // @formatter:off | ||
| return Stream.of( | ||
| AsynchronousByteChannel.class, |
There was a problem hiding this comment.
Hello @ppkarwasz
Why have these interfaces been removed?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Ahhh, thank you for the clarification! 😊
Two bugs in the
CloseShieldChannelhelper make it unreliable in practice:Type-erasure bug in
T wrap(T)The method signature only works correctly whenTis an interface extendingChannel. Since Java’s type system doesn’t allow constrainingTto “interface types only,” this could lead to unexpected runtimeClassCastExceptions even though the code compiles successfully.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
FileChanneldid not expose any of the interfaces declared onFileChannelitself.Fixes
This PR addresses both issues:
Reworks the API signature
T wrap(T)with its erasure:Channel wrap(Channel).T wrap(T, Class<T>), which allows callers to explicitly specify the interface type they expect. This version fails fast with a clearIllegalArgumentExceptionif the provided type is not an interface, instead of allowing aClassCastExceptionlater.Improves interface collection logic
Channelinterfaces are correctly proxied.