diff --git a/jdk/src/share/classes/java/lang/Thread.java b/jdk/src/share/classes/java/lang/Thread.java index 52b57dde4bb..545cef6fcef 100644 --- a/jdk/src/share/classes/java/lang/Thread.java +++ b/jdk/src/share/classes/java/lang/Thread.java @@ -958,7 +958,7 @@ class Thread implements Runnable { Interruptible b = blocker; if (b != null) { interrupt0(); // Just to set the interrupt flag - b.interrupt(); + b.interrupt(this); return; } } diff --git a/jdk/src/share/classes/java/nio/channels/spi/AbstractInterruptibleChannel.java b/jdk/src/share/classes/java/nio/channels/spi/AbstractInterruptibleChannel.java index 7ed7f09d79e..a5160452c40 100644 --- a/jdk/src/share/classes/java/nio/channels/spi/AbstractInterruptibleChannel.java +++ b/jdk/src/share/classes/java/nio/channels/spi/AbstractInterruptibleChannel.java @@ -88,7 +88,7 @@ public abstract class AbstractInterruptibleChannel implements Channel, InterruptibleChannel { - private Object closeLock = new Object(); + private final Object closeLock = new Object(); private volatile boolean open = true; /** @@ -142,7 +142,7 @@ public abstract class AbstractInterruptibleChannel // -- Interruption machinery -- private Interruptible interruptor; - private volatile boolean interrupted = false; + private volatile Thread interrupted; /** * Marks the beginning of an I/O operation that might block indefinitely. @@ -155,12 +155,12 @@ public abstract class AbstractInterruptibleChannel protected final void begin() { if (interruptor == null) { interruptor = new Interruptible() { - public void interrupt() { + public void interrupt(Thread target) { synchronized (closeLock) { if (!open) return; - interrupted = true; open = false; + interrupted = target; try { AbstractInterruptibleChannel.this.implCloseChannel(); } catch (IOException x) { } @@ -168,8 +168,9 @@ public abstract class AbstractInterruptibleChannel }}; } blockedOn(interruptor); - if (Thread.currentThread().isInterrupted()) - interruptor.interrupt(); + Thread me = Thread.currentThread(); + if (me.isInterrupted()) + interruptor.interrupt(me); } /** @@ -195,12 +196,13 @@ public abstract class AbstractInterruptibleChannel throws AsynchronousCloseException { blockedOn(null); - if (completed) { - interrupted = false; - return; + Thread interrupted = this.interrupted; + if (interrupted != null && interrupted == Thread.currentThread()) { + interrupted = null; + throw new ClosedByInterruptException(); } - if (interrupted) throw new ClosedByInterruptException(); - if (!open) throw new AsynchronousCloseException(); + if (!completed && !open) + throw new AsynchronousCloseException(); } diff --git a/jdk/src/share/classes/java/nio/channels/spi/AbstractSelector.java b/jdk/src/share/classes/java/nio/channels/spi/AbstractSelector.java index c7e40b254af..0e4487ada08 100644 --- a/jdk/src/share/classes/java/nio/channels/spi/AbstractSelector.java +++ b/jdk/src/share/classes/java/nio/channels/spi/AbstractSelector.java @@ -206,13 +206,14 @@ public abstract class AbstractSelector protected final void begin() { if (interruptor == null) { interruptor = new Interruptible() { - public void interrupt() { + public void interrupt(Thread ignore) { AbstractSelector.this.wakeup(); }}; } AbstractInterruptibleChannel.blockedOn(interruptor); - if (Thread.currentThread().isInterrupted()) - interruptor.interrupt(); + Thread me = Thread.currentThread(); + if (me.isInterrupted()) + interruptor.interrupt(me); } /** diff --git a/jdk/src/share/classes/sun/nio/ch/FileChannelImpl.java b/jdk/src/share/classes/sun/nio/ch/FileChannelImpl.java index 6d1bed731ee..e37fe799d84 100644 --- a/jdk/src/share/classes/sun/nio/ch/FileChannelImpl.java +++ b/jdk/src/share/classes/sun/nio/ch/FileChannelImpl.java @@ -460,6 +460,16 @@ public class FileChannelImpl } finally { unmap(dbb); } + } catch (ClosedByInterruptException e) { + // target closed by interrupt as ClosedByInterruptException needs + // to be thrown after closing this channel. + assert !target.isOpen(); + try { + close(); + } catch (IOException ignore) { + // nothing we can do + } + throw e; } catch (IOException ioe) { // Only throw exception if no bytes have been written if (remaining == count) diff --git a/jdk/src/share/classes/sun/nio/ch/Interruptible.java b/jdk/src/share/classes/sun/nio/ch/Interruptible.java index e8d3824473e..8ca1cb959ce 100644 --- a/jdk/src/share/classes/sun/nio/ch/Interruptible.java +++ b/jdk/src/share/classes/sun/nio/ch/Interruptible.java @@ -23,14 +23,14 @@ * questions. */ -/* +/** + * An object that interrupts a thread blocked in an I/O operation. */ package sun.nio.ch; - public interface Interruptible { - public void interrupt(); + public void interrupt(Thread t); } diff --git a/jdk/test/java/nio/channels/FileChannel/ClosedByInterrupt.java b/jdk/test/java/nio/channels/FileChannel/ClosedByInterrupt.java new file mode 100644 index 00000000000..24e2119d4cc --- /dev/null +++ b/jdk/test/java/nio/channels/FileChannel/ClosedByInterrupt.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @bug 6979009 + * @summary Ensure ClosedByInterruptException is thrown when I/O operation + * interrupted by Thread.interrupt + */ + +import java.io.*; +import java.util.Random; +import java.nio.ByteBuffer; +import java.nio.channels.*; + +public class ClosedByInterrupt { + + static final int K = 1024; + static final Random rand = new Random(); + + static volatile boolean failed; + + public static void main(String[] args) throws Exception { + File f = File.createTempFile("blah", null); + f.deleteOnExit(); + + // create 1MB file. + byte[] b = new byte[K*K]; + rand.nextBytes(b); + ByteBuffer bb = ByteBuffer.wrap(b); + try (FileChannel fc = new FileOutputStream(f).getChannel()) { + while (bb.hasRemaining()) + fc.write(bb); + } + + // test with 1-8 concurrent threads + for (int i=1; i<=8; i++) { + System.out.format("%d thread(s)%n", i); + test(f, i); + if (failed) + break; + } + } + + /** + * Starts "nThreads" that do I/O on the given file concurrently. Continuously + * interrupts one of the threads to cause the file to be closed and + * ClosedByInterruptException to be thrown. The other threads should "fail" with + * ClosedChannelException (or the more specific AsynchronousCloseException). + */ + static void test(File f, int nThreads) throws Exception { + try (FileChannel fc = new RandomAccessFile(f, "rwd").getChannel()) { + Thread[] threads = new Thread[nThreads]; + + // start threads + for (int i=0; i