In the following example, I have one file being used by two threads (in the real example I could have any number of threads)
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class A {
static volatile boolean running = true;
public static void main(String[] args) throws IOException, InterruptedException {
String name = "delete.me";
new File(name).deleteOnExit();
RandomAccessFile raf = new RandomAccessFile(name, "rw");
FileChannel fc = raf.getChannel();
Thread monitor = new Thread(() -> {
try {
while (running) {
System.out.println(name + " is " + (fc.size() >> 10) + " KB");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted");
Thread.currentThread().interrupt();
}
}
} catch (IOException e) {
System.err.println("Monitor thread died");
e.printStackTrace();
}
});
monitor.setDaemon(true);
monitor.start();
Thread writer = new Thread(() -> {
ByteBuffer bb = ByteBuffer.allocateDirect(32);
try {
while (running) {
bb.position(0).limit(32);
fc.write(bb);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
System.out.println("Interrupted");
Thread.currentThread().interrupt();
}
}
} catch (IOException e) {
System.err.println("Writer thread died");
e.printStackTrace();
}
});
writer.setDaemon(true);
writer.start();
Thread.sleep(5000);
monitor.interrupt();
Thread.sleep(2000);
running = false;
raf.close();
}
}
Rather creating a RandomAccessFile and a memory mapping for each thread, I have one file and one memory mapping shared between threads, but there is a catch, if any thread is interrupted the resource is closed.
delete.me is 0 KB
delete.me is 2 KB
delete.me is 4 KB
delete.me is 6 KB
delete.me is 8 KB
Interrupted
Monitor thread died
java.nio.channels.ClosedByInterruptException
at java.nio.channels.spi.AbstractInterruptibleChannel.end(AbstractInterruptibleChannel.java:202)
at sun.nio.ch.FileChannelImpl.size(FileChannelImpl.java:315)
at A.lambda$main$0(A.java:19)
at java.lang.Thread.run(Thread.java:748)
Writer thread died
java.nio.channels.ClosedChannelException
at sun.nio.ch.FileChannelImpl.ensureOpen(FileChannelImpl.java:110)
at sun.nio.ch.FileChannelImpl.write(FileChannelImpl.java:199)
at A.lambda$main$1(A.java:41)
at java.lang.Thread.run(Thread.java:748)
Is there any way to prevent the FileChannel from being closed just because one thread using it was interrupted?
EDIT What I want to avoid doing is as I suspect it won't work for Java 9+
private void doNotCloseOnInterrupt(FileChannel fc) {
try {
Field field = AbstractInterruptibleChannel.class
.getDeclaredField("interruptor");
field.setAccessible(true);
field.set(fc, (Interruptible) thread
-> Jvm.warn().on(getClass(), fc + " not closed on interrupt"));
} catch (Exception e) {
Jvm.warn().on(getClass(), "Couldn't disable close on interrupt", e);
}
}
BTW The call to fc.size()
returns the size as expected with the above hack.
Since you said you want “one memory mapping shared between threads”, there is no such problem at all, as memory mapping is not affect by the closing of a FileChannel
. In fact, it’s a good strategy to close the channel as soon as possible, to reduce the resources held by the application.
E.g.
static volatile boolean running = true;
public static void main(String[] args) throws IOException {
Path name = Paths.get("delete.me");
MappedByteBuffer mapped;
try(FileChannel fc1 = FileChannel.open(name, READ,WRITE,CREATE_NEW,DELETE_ON_CLOSE)) {
mapped = fc1.map(FileChannel.MapMode.READ_WRITE, 0, 4096);
}
Thread thread1 = new Thread(() -> {
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(50));
while(running && !Thread.interrupted()) {
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));
byte[] b = new byte[5];
mapped.position(4000);
mapped.get(b);
System.out.println("read "+new String(b, StandardCharsets.US_ASCII));
}
});
thread1.setDaemon(true);
thread1.start();
Thread thread2 = new Thread(() -> {
byte[] b = "HELLO".getBytes(StandardCharsets.US_ASCII);
while(running && !Thread.interrupted()) {
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));
mapped.position(4000);
mapped.put(b);
System.out.println("wrote "+new String(b, StandardCharsets.US_ASCII));
byte b1 = b[0];
System.arraycopy(b, 1, b, 0, b.length-1);
b[b.length-1] = b1;
}
mapped.force();
});
thread2.setDaemon(true);
thread2.start();
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
thread2.interrupt();
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
running = false;
This demonstrates how the threads can read and write their data after the channel has been closed and interrupting the writing thread does not stop the reading thread.
If you need to perform FileChannel
operations in addition to memory mapped I/O, there is no problem in using multiple FileChannel
instances, so closing one channel does not affect the other. E.g.
static volatile boolean running = true;
public static void main(String[] args) throws IOException {
Path name = Paths.get("delete.me");
try(FileChannel fc1 = FileChannel.open(name,READ,WRITE,CREATE_NEW,DELETE_ON_CLOSE);
FileChannel fc2 = FileChannel.open(name,READ,WRITE)) {
Thread thread1 = new Thread(() -> {
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(50));
try {
MappedByteBuffer mapped = fc1.map(FileChannel.MapMode.READ_WRITE, 0, 4096);
while(running && !Thread.interrupted()) {
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));
byte[] b = new byte[5];
mapped.position(4000);
mapped.get(b);
System.out.println("read from map "
+new String(b, StandardCharsets.US_ASCII)
+", file size "+fc1.size());
}
}catch(IOException ex) {
ex.printStackTrace();
}
});
thread1.setDaemon(true);
thread1.start();
Thread thread2 = new Thread(() -> {
byte[] b = "HELLO".getBytes(StandardCharsets.US_ASCII);
try {
MappedByteBuffer mapped = fc2.map(FileChannel.MapMode.READ_WRITE, 0, 4096);
fc2.position(4096);
try {
while(running && !Thread.interrupted()) {
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));
mapped.position(4000);
mapped.put(b);
System.out.println("wrote to mapped "
+new String(b, StandardCharsets.US_ASCII));
byte b1 = b[0];
System.arraycopy(b, 1, b, 0, b.length-1);
b[b.length-1] = b1;
fc2.write(ByteBuffer.wrap(b));
}
} finally { mapped.force(); }
}catch(IOException ex) {
ex.printStackTrace();
}
});
thread2.setDaemon(true);
thread2.start();
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
thread2.interrupt();
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
running = false;
}
}
Here, the interruption of one thread does close its channel, but does not affect the other. Further, even when each thread acquires its own MappedByteBuffer
from its own channel, changes show through to the other, even without the use of force()
. Of course, the latter is defined to be a system dependent behavior, not guaranteed to work on every system.
But as shown with the first example, you still may create shared buffers from only one of the channels at the start, while performing the I/O operations on a different channel, one per thread, and it doesn’t matter whether and which channels get closed, the mapped buffers are not affected by it.
You can use reflection to access the interruptor
field illegaly and get the sun.nio.ch.Interruptible
class type from there to create a proxy instance:
private void doNotCloseOnInterrupt(FileChannel fc) {
try {
Field field = AbstractInterruptibleChannel.class.getDeclaredField("interruptor");
Class<?> interruptibleClass = field.getType();
field.setAccessible(true);
field.set(fc, Proxy.newProxyInstance(
interruptibleClass.getClassLoader(),
new Class[] { interruptibleClass },
new InterruptibleInvocationHandler()));
} catch (final Exception e) {
Jvm.warn().on(getClass(), "Couldn't disable close on interrupt", e);
}
}
public class InterruptibleInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
// TODO: Check method and handle accordingly
return null;
}
}
In Java9 this works with a single warning, as it runs per default with --illegal-access=permit
.
However this flag might be removed in future versions and the best way to ensure this works long-term is to use the flag --add-opens
:
--add-opens java.base/sun.nio.ch=your-module
--add-opens java.base/java.nio.channels.spi=your-module
Or, if you aren't working with modules (not recommended):
--add-opens java.base/sun.nio.ch=ALL-UNNAMED
--add-opens java.base/java.nio.channels.spi=ALL-UNNAMED
This works with Java 9, Java 10 and the current JDK 11 Early-Access Build (28 (2018/8/23)).
By using an AsynchronousFileChannel then ClosedByInterruptException is never thrown It just does not seem to care about the interrupt
Test done using jdk 1.8.0_72
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.atomic.AtomicLong;
public class A {
static volatile boolean running = true;
public static void main(String[] args) throws IOException, InterruptedException {
String name = "delete.me";
Path path = new File(name).toPath();
AtomicLong position = new AtomicLong(0);
AsynchronousFileChannel fc = AsynchronousFileChannel.open(path,
StandardOpenOption.CREATE_NEW, StandardOpenOption.DELETE_ON_CLOSE ,
StandardOpenOption.READ, StandardOpenOption.WRITE,
StandardOpenOption.WRITE, StandardOpenOption.SYNC);
CompletionHandler<Integer, Object> handler =
new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {
//System.out.println(attachment + " completed with " + result + " bytes written");
position.getAndAdd(result);
}
@Override
public void failed(Throwable e, Object attachment) {
System.err.println(attachment + " failed with:");
e.printStackTrace();
}
};
Runnable monitorRun = () -> {
try {
while (running) {
System.out.println(name + " is " + (fc.size() >> 10) + " KB");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted");
Thread.currentThread().interrupt();
System.out.println("Interrupt call failed so return");
return;
}
}
} catch (IOException e) {
System.err.println("Monitor thread died");
e.printStackTrace();
}
};
Thread monitor = new Thread(monitorRun);
monitor.setDaemon(true);
monitor.start();
Thread writer = new Thread(() -> {
ByteBuffer bb = ByteBuffer.allocateDirect(32);
try {
while (running) {
bb.position(0).limit(32);
fc.write(bb,position.get(),null,handler);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
System.out.println("Interrupted");
Thread.currentThread().interrupt();
}
}
} catch (Exception e) {
System.err.println("Writer thread died");
e.printStackTrace();
}
});
writer.setDaemon(true);
writer.start();
Thread.sleep(5000);
monitor.interrupt();
Thread.sleep(2000);
monitor = new Thread(monitorRun);
monitor.start();
Thread.sleep(5000);
running = false;
fc.close();
}
}
Generate the following output:
delete.me is 0 KB
delete.me is 3 KB
delete.me is 6 KB
delete.me is 9 KB
delete.me is 12 KB
Interrupted
Interrupt call failed so return
delete.me is 21 KB
delete.me is 24 KB
delete.me is 27 KB
delete.me is 30 KB
delete.me is 33 KB
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With