Don't Share Java FileChannels
pkolaczk.github.io
pkolaczk.github.io
[1]: https://github.com/openjdk/jdk/blame/c313e1ac7b3305b1c012755...
[2]: https://stackoverflow.com/questions/1161297/
---
Edit: marginalia_nu's and cesarb's comments make sense. Searching for "interruptiblechannel" yields:
https://www.taogenjia.com/2020/07/13/Java-understanding-nio/
>NIO’s designers chose to shut down a channel when a blocked thread is interrupted because they couldn’t find a way to reliably handle interrupted I/O operations in the same manner across operating systems. The only way to guarantee deterministic behavior was to shut down the channel.
Since the interrupts being talked about here are a Java concept, Java would need to interrupt the syscall itself, and it makes sense they chose to close the file since there's no way to do that equally well on all OSes.
https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/...
See an old discussion about that here:
https://mail.openjdk.org/pipermail/nio-dev/2018-February/004...
It's something Java-specific: https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.h...
You should still be able to share FileChannels between threads when all immediate client code is under your control so you can handle that situation gracefully.
[1] It’s just a flag you can set on a thread, requesting the thread to stop its operation (the thread is not required to comply), and longer-running operations like I/O can choose to test that flag, and typically throw some exception when it is set. The default exception type for this is InterruptedException, but here they chose to throw a subclass of IOException, probably exactly because it also closes the channel.
I wonder if the virtual threads from Project Loom could let the JVM do the right thing here, and interrupt the virtual thread without waiting for the carrier thread to return from the system call. That might be extremely hairy, though - your thread could return from a read call with an InterruptedException, and then some time later, the data from the read could end up in your buffer as if by magic!
Could be similar why rust have Pin macros.
If target thread does not sleep in a blocking I/O (or uses some kind of unsupported blocking I/O, e.g. implemented with third-party JNI library), only some internal flag sets and no magic happens.
It's pretty simple implementation actually. Thread is not cancelled or corrupted in any way, if you handle this situation, it's perfectly OK to continue execution in this thread. Usually interrupt is used to coordinate termination, indeed, but that's not the only use.
So apparently they didn't find a better way to cancel a blocking I/O operation than just to close file handle from the other thread which causes OS to return some kind of error code from blocking call which translates to Java exception in the end.
I had a few notes:
1. It is really nice to have such thorough documentation. Even if a programmer doesn’t always have or make the time to read it.
2. I _think_ I remember reading an interview with Peter Norvig in Coders at Work where he talks about programmers never having the time to fully grok their API docs (it may well have been a more general statement about rushing to get stuff done).
Some time ago I personally learned a virtualization discovery API whereby one could make some calls and learn, through some sort of traversal, how a topology of VMs was laid out. My title at the time was Intermediate Software Developer. I remember I was pretty happy with my solution and shared during standup that it was working well but was kind of slow, and that there was another, more complicated and finicky type of traversal mentioned in the docs, and that my reading of the docs was that learning and coding this other method was necessary or helpful is some use cases but that for our situation it would not make a difference and would just add complexity. Well within a couple of weeks another team member - Junior Software Developer - read the same docs and tried out the more finicky version and wouldn’t you know, the discovery process suddenly became blazing fast.
EDIT: Looking closer, it's just that they caught IOException instead of the ClosedByInterruptException subclass and expected it to be a non exceptional exception. One would hope they'd log the subclass type in that case and if so then yeah they should've seen that.
Since this isn't an actual leak, raising the limit should be fine. The default limit on Linux is 1024 due to some issues with SELECT, but you can easily raise it to a much higher value if you don't use that API.
Lots of ways to skin this cat, but it really depends on what the application is doing and why.
https://docs.oracle.com/javase/7/docs/api/java/util/concurre...
They are more powerful than synchronized and can produce log messages when things go sideways with locking, dead locking or external crashes.
Finding and patching all possible locations which could interrupt your threads doing file operations is probably a foolish effort.
So, raising the limit, or load balancing (depending on the type of application) is probably the best solution.
The other question is why the threads are receiving interrupts in the first place. Depending on the reason, a different course of action might be appropriate.
[0] A major caveat, but often true for "boring" business logic applications.