diff options
23 files changed, 844 insertions, 138 deletions
diff --git a/luni/src/main/java/java/io/FileInputStream.java b/luni/src/main/java/java/io/FileInputStream.java index 644f749..5debe64 100644 --- a/luni/src/main/java/java/io/FileInputStream.java +++ b/luni/src/main/java/java/io/FileInputStream.java @@ -21,10 +21,8 @@ import dalvik.system.CloseGuard; import java.nio.NioUtils; import java.nio.channels.FileChannel; -import java.util.Arrays; import libcore.io.ErrnoException; import libcore.io.IoBridge; -import libcore.io.IoUtils; import libcore.io.Libcore; import libcore.io.Streams; import static libcore.io.OsConstants.*; @@ -118,7 +116,7 @@ public class FileInputStream extends InputStream { channel.close(); } if (shouldClose) { - IoUtils.close(fd); + IoBridge.closeAndSignalBlockedThreads(fd); } else { // An owned fd has been invalidated by IoUtils.close, but // we need to explicitly stop using an unowned fd (http://b/4361076). diff --git a/luni/src/main/java/java/io/FileOutputStream.java b/luni/src/main/java/java/io/FileOutputStream.java index f5ba11e..e796e80 100644 --- a/luni/src/main/java/java/io/FileOutputStream.java +++ b/luni/src/main/java/java/io/FileOutputStream.java @@ -20,9 +20,8 @@ package java.io; import dalvik.system.CloseGuard; import java.nio.NioUtils; import java.nio.channels.FileChannel; -import java.util.Arrays; import libcore.io.IoBridge; -import libcore.io.IoUtils; + import static libcore.io.OsConstants.*; /** @@ -136,7 +135,7 @@ public class FileOutputStream extends OutputStream { channel.close(); } if (shouldClose) { - IoUtils.close(fd); + IoBridge.closeAndSignalBlockedThreads(fd); } else { // An owned fd has been invalidated by IoUtils.close, but // we need to explicitly stop using an unowned fd (http://b/4361076). diff --git a/luni/src/main/java/java/io/RandomAccessFile.java b/luni/src/main/java/java/io/RandomAccessFile.java index 6e88fe0..eac7641 100644 --- a/luni/src/main/java/java/io/RandomAccessFile.java +++ b/luni/src/main/java/java/io/RandomAccessFile.java @@ -25,7 +25,6 @@ import java.nio.charset.ModifiedUtf8; import java.util.Arrays; import libcore.io.ErrnoException; import libcore.io.IoBridge; -import libcore.io.IoUtils; import libcore.io.Libcore; import libcore.io.Memory; import libcore.io.SizeOf; @@ -163,7 +162,7 @@ public class RandomAccessFile implements DataInput, DataOutput, Closeable { channel.close(); channel = null; } - IoUtils.close(fd); + IoBridge.closeAndSignalBlockedThreads(fd); } } diff --git a/luni/src/main/java/java/net/InetAddress.java b/luni/src/main/java/java/net/InetAddress.java index 98ad098..885b472 100644 --- a/luni/src/main/java/java/net/InetAddress.java +++ b/luni/src/main/java/java/net/InetAddress.java @@ -28,8 +28,6 @@ import java.io.Serializable; import java.nio.ByteOrder; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; -import java.util.Enumeration; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; @@ -734,7 +732,7 @@ public class InetAddress implements Serializable { } } - IoBridge.closeSocket(fd); + IoBridge.closeAndSignalBlockedThreads(fd); return reached; } diff --git a/luni/src/main/java/java/net/PlainDatagramSocketImpl.java b/luni/src/main/java/java/net/PlainDatagramSocketImpl.java index edf7024..a9ade28 100644 --- a/luni/src/main/java/java/net/PlainDatagramSocketImpl.java +++ b/luni/src/main/java/java/net/PlainDatagramSocketImpl.java @@ -78,7 +78,7 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl { public synchronized void close() { guard.close(); try { - IoBridge.closeSocket(fd); + IoBridge.closeAndSignalBlockedThreads(fd); } catch (IOException ignored) { } } diff --git a/luni/src/main/java/java/net/PlainSocketImpl.java b/luni/src/main/java/java/net/PlainSocketImpl.java index d81303f..e3988ed 100644 --- a/luni/src/main/java/java/net/PlainSocketImpl.java +++ b/luni/src/main/java/java/net/PlainSocketImpl.java @@ -145,7 +145,7 @@ public class PlainSocketImpl extends SocketImpl { @Override protected synchronized void close() throws IOException { guard.close(); - IoBridge.closeSocket(fd); + IoBridge.closeAndSignalBlockedThreads(fd); } @Override diff --git a/luni/src/main/java/java/nio/DatagramChannelImpl.java b/luni/src/main/java/java/nio/DatagramChannelImpl.java index 883ffea..2f04b30 100644 --- a/luni/src/main/java/java/nio/DatagramChannelImpl.java +++ b/luni/src/main/java/java/nio/DatagramChannelImpl.java @@ -524,7 +524,7 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann @Override protected synchronized void implCloseSelectableChannel() throws IOException { // A closed channel is not connected. onDisconnect(true /* updateSocketState */); - IoBridge.closeSocket(fd); + IoBridge.closeAndSignalBlockedThreads(fd); multicastMembershipHandler = null; if (socket != null && !socket.isClosed()) { diff --git a/luni/src/main/java/java/nio/FileChannelImpl.java b/luni/src/main/java/java/nio/FileChannelImpl.java index 6206d1b..9a47706 100644 --- a/luni/src/main/java/java/nio/FileChannelImpl.java +++ b/luni/src/main/java/java/nio/FileChannelImpl.java @@ -50,7 +50,7 @@ final class FileChannelImpl extends FileChannel { } }; - private final Object stream; + private final Closeable ioObject; private final FileDescriptor fd; private final int mode; @@ -61,9 +61,9 @@ final class FileChannelImpl extends FileChannel { * Create a new file channel implementation class that wraps the given * fd and operates in the specified mode. */ - public FileChannelImpl(Object stream, FileDescriptor fd, int mode) { + public FileChannelImpl(Closeable ioObject, FileDescriptor fd, int mode) { this.fd = fd; - this.stream = stream; + this.ioObject = ioObject; this.mode = mode; } @@ -86,9 +86,7 @@ final class FileChannelImpl extends FileChannel { } protected void implCloseChannel() throws IOException { - if (stream instanceof Closeable) { - ((Closeable) stream).close(); - } + ioObject.close(); } private FileLock basicLock(long position, long size, boolean shared, boolean wait) throws IOException { diff --git a/luni/src/main/java/java/nio/NioUtils.java b/luni/src/main/java/java/nio/NioUtils.java index 34af76b..51adddb 100644 --- a/luni/src/main/java/java/nio/NioUtils.java +++ b/luni/src/main/java/java/nio/NioUtils.java @@ -16,6 +16,7 @@ package java.nio; +import java.io.Closeable; import java.io.FileDescriptor; import java.io.IOException; import java.net.SocketOption; @@ -48,8 +49,8 @@ public final class NioUtils { /** * Helps bridge between io and nio. */ - public static FileChannel newFileChannel(Object stream, FileDescriptor fd, int mode) { - return new FileChannelImpl(stream, fd, mode); + public static FileChannel newFileChannel(Closeable ioObject, FileDescriptor fd, int mode) { + return new FileChannelImpl(ioObject, fd, mode); } /** diff --git a/luni/src/main/java/java/nio/SelectorImpl.java b/luni/src/main/java/java/nio/SelectorImpl.java index d63fa63..3495523 100644 --- a/luni/src/main/java/java/nio/SelectorImpl.java +++ b/luni/src/main/java/java/nio/SelectorImpl.java @@ -17,18 +17,15 @@ package java.nio; import java.io.FileDescriptor; import java.io.IOException; +import java.io.InterruptedIOException; import java.nio.channels.ClosedSelectorException; import java.nio.channels.IllegalSelectorException; -import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; -import static java.nio.channels.SelectionKey.*; import java.nio.channels.Selector; -import java.nio.channels.SocketChannel; import java.nio.channels.spi.AbstractSelectableChannel; import java.nio.channels.spi.AbstractSelectionKey; import java.nio.channels.spi.AbstractSelector; import java.nio.channels.spi.SelectorProvider; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -40,8 +37,15 @@ import libcore.io.IoBridge; import libcore.io.IoUtils; import libcore.io.Libcore; import libcore.io.StructPollfd; -import libcore.util.EmptyArray; -import static libcore.io.OsConstants.*; + +import static java.nio.channels.SelectionKey.OP_ACCEPT; +import static java.nio.channels.SelectionKey.OP_CONNECT; +import static java.nio.channels.SelectionKey.OP_READ; +import static java.nio.channels.SelectionKey.OP_WRITE; +import static libcore.io.OsConstants.EINTR; +import static libcore.io.OsConstants.POLLHUP; +import static libcore.io.OsConstants.POLLIN; +import static libcore.io.OsConstants.POLLOUT; /* * Default implementation of java.nio.channels.Selector @@ -321,6 +325,7 @@ final class SelectorImpl extends AbstractSelector { try { Libcore.os.write(wakeupOut, new byte[] { 1 }, 0, 1); } catch (ErrnoException ignored) { + } catch (InterruptedIOException ignored) { } return this; } diff --git a/luni/src/main/java/java/nio/SocketChannelImpl.java b/luni/src/main/java/java/nio/SocketChannelImpl.java index d1e77d6..2ed11fc 100644 --- a/luni/src/main/java/java/nio/SocketChannelImpl.java +++ b/luni/src/main/java/java/nio/SocketChannelImpl.java @@ -525,9 +525,9 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { protected synchronized void implCloseSelectableChannel() throws IOException { if (status != SOCKET_STATUS_CLOSED) { status = SOCKET_STATUS_CLOSED; - // IoBridge.closeSocket(fd) is idempotent: It is safe to call on an already-closed file - // descriptor. - IoBridge.closeSocket(fd); + // IoBridge.closeAndSignalBlockedThreads(fd) is idempotent: It is safe to call on an + // already-closed file descriptor. + IoBridge.closeAndSignalBlockedThreads(fd); if (socket != null && !socket.isClosed()) { socket.onClose(); } diff --git a/luni/src/main/java/libcore/io/BlockGuardOs.java b/luni/src/main/java/libcore/io/BlockGuardOs.java index 05a756f..eaa7f0c 100644 --- a/luni/src/main/java/libcore/io/BlockGuardOs.java +++ b/luni/src/main/java/libcore/io/BlockGuardOs.java @@ -19,6 +19,7 @@ package libcore.io; import dalvik.system.BlockGuard; import dalvik.system.SocketTagger; import java.io.FileDescriptor; +import java.io.InterruptedIOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketException; @@ -158,6 +159,11 @@ public class BlockGuardOs extends ForwardingOs { os.mkdir(path, mode); } + @Override public void mkfifo(String path, int mode) throws ErrnoException { + BlockGuard.getThreadPolicy().onWriteToDisk(); + os.mkfifo(path, mode); + } + @Override public FileDescriptor open(String path, int flags, int mode) throws ErrnoException { BlockGuard.getThreadPolicy().onReadFromDisk(); if ((mode & O_ACCMODE) != O_RDONLY) { @@ -180,32 +186,32 @@ public class BlockGuardOs extends ForwardingOs { os.posix_fallocate(fd, offset, length); } - @Override public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException { + @Override public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onReadFromDisk(); return os.pread(fd, buffer, offset); } - @Override public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException { + @Override public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onReadFromDisk(); return os.pread(fd, bytes, byteOffset, byteCount, offset); } - @Override public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException { + @Override public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onWriteToDisk(); return os.pwrite(fd, buffer, offset); } - @Override public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException { + @Override public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onWriteToDisk(); return os.pwrite(fd, bytes, byteOffset, byteCount, offset); } - @Override public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { + @Override public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onReadFromDisk(); return os.read(fd, buffer); } - @Override public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { + @Override public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onReadFromDisk(); return os.read(fd, bytes, byteOffset, byteCount); } @@ -215,7 +221,7 @@ public class BlockGuardOs extends ForwardingOs { return os.readlink(path); } - @Override public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { + @Override public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onReadFromDisk(); return os.readv(fd, buffers, offsets, byteCounts); } @@ -283,17 +289,17 @@ public class BlockGuardOs extends ForwardingOs { os.symlink(oldPath, newPath); } - @Override public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { + @Override public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onWriteToDisk(); return os.write(fd, buffer); } - @Override public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { + @Override public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onWriteToDisk(); return os.write(fd, bytes, byteOffset, byteCount); } - @Override public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { + @Override public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onWriteToDisk(); return os.writev(fd, buffers, offsets, byteCounts); } diff --git a/luni/src/main/java/libcore/io/ForwardingOs.java b/luni/src/main/java/libcore/io/ForwardingOs.java index 2de35e9..0b49071 100644 --- a/luni/src/main/java/libcore/io/ForwardingOs.java +++ b/luni/src/main/java/libcore/io/ForwardingOs.java @@ -17,6 +17,7 @@ package libcore.io; import java.io.FileDescriptor; +import java.io.InterruptedIOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; @@ -90,6 +91,7 @@ public class ForwardingOs implements Os { public StructStat lstat(String path) throws ErrnoException { return os.lstat(path); } public void mincore(long address, long byteCount, byte[] vector) throws ErrnoException { os.mincore(address, byteCount, vector); } public void mkdir(String path, int mode) throws ErrnoException { os.mkdir(path, mode); } + public void mkfifo(String path, int mode) throws ErrnoException { os.mkfifo(path, mode); } public void mlock(long address, long byteCount) throws ErrnoException { os.mlock(address, byteCount); } public long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException { return os.mmap(address, byteCount, prot, flags, fd, offset); } public void msync(long address, long byteCount, int flags) throws ErrnoException { os.msync(address, byteCount, flags); } @@ -99,14 +101,14 @@ public class ForwardingOs implements Os { public FileDescriptor[] pipe() throws ErrnoException { return os.pipe(); } public int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException { return os.poll(fds, timeoutMs); } public void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException { os.posix_fallocate(fd, offset, length); } - public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException { return os.pread(fd, buffer, offset); } - public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException { return os.pread(fd, bytes, byteOffset, byteCount, offset); } - public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException { return os.pwrite(fd, buffer, offset); } - public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException { return os.pwrite(fd, bytes, byteOffset, byteCount, offset); } - public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { return os.read(fd, buffer); } - public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { return os.read(fd, bytes, byteOffset, byteCount); } + public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return os.pread(fd, buffer, offset); } + public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return os.pread(fd, bytes, byteOffset, byteCount, offset); } + public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return os.pwrite(fd, buffer, offset); } + public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return os.pwrite(fd, bytes, byteOffset, byteCount, offset); } + public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return os.read(fd, buffer); } + public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return os.read(fd, bytes, byteOffset, byteCount); } public String readlink(String path) throws ErrnoException { return os.readlink(path); } - public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { return os.readv(fd, buffers, offsets, byteCounts); } + public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return os.readv(fd, buffers, offsets, byteCounts); } public int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return os.recvfrom(fd, buffer, flags, srcAddress); } public int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress); } public void remove(String path) throws ErrnoException { os.remove(path); } @@ -143,7 +145,7 @@ public class ForwardingOs implements Os { public StructUtsname uname() { return os.uname(); } public void unsetenv(String name) throws ErrnoException { os.unsetenv(name); } public int waitpid(int pid, MutableInt status, int options) throws ErrnoException { return os.waitpid(pid, status, options); } - public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { return os.write(fd, buffer); } - public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { return os.write(fd, bytes, byteOffset, byteCount); } - public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { return os.writev(fd, buffers, offsets, byteCounts); } + public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return os.write(fd, buffer); } + public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return os.write(fd, bytes, byteOffset, byteCount); } + public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return os.writev(fd, buffers, offsets, byteCounts); } } diff --git a/luni/src/main/java/libcore/io/IoBridge.java b/luni/src/main/java/libcore/io/IoBridge.java index 08d4837..89070cd 100644 --- a/luni/src/main/java/libcore/io/IoBridge.java +++ b/luni/src/main/java/libcore/io/IoBridge.java @@ -177,9 +177,15 @@ public final class IoBridge { return detail; } - public static void closeSocket(FileDescriptor fd) throws IOException { - if (!fd.valid()) { - // Socket.close doesn't throw if you try to close an already-closed socket. + /** + * Closes the supplied file descriptor and sends a signal to any threads are currently blocking. + * In order for the signal to be sent the blocked threads must have registered with + * the AsynchronousCloseMonitor before they entered the blocking operation. + * + * <p>This method is a no-op if passed a {@code null} or already-closed file descriptor. + */ + public static void closeAndSignalBlockedThreads(FileDescriptor fd) throws IOException { + if (fd == null || !fd.valid()) { return; } int intFd = fd.getInt$(); diff --git a/luni/src/main/java/libcore/io/Os.java b/luni/src/main/java/libcore/io/Os.java index a4541a7..2065c70 100644 --- a/luni/src/main/java/libcore/io/Os.java +++ b/luni/src/main/java/libcore/io/Os.java @@ -17,6 +17,7 @@ package libcore.io; import java.io.FileDescriptor; +import java.io.InterruptedIOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; @@ -82,6 +83,7 @@ public interface Os { public StructStat lstat(String path) throws ErrnoException; public void mincore(long address, long byteCount, byte[] vector) throws ErrnoException; public void mkdir(String path, int mode) throws ErrnoException; + public void mkfifo(String path, int mode) throws ErrnoException; public void mlock(long address, long byteCount) throws ErrnoException; public long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException; public void msync(long address, long byteCount, int flags) throws ErrnoException; @@ -92,14 +94,14 @@ public interface Os { /* TODO: if we used the non-standard ppoll(2) behind the scenes, we could take a long timeout. */ public int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException; public void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException; - public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException; - public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException; - public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException; - public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException; - public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException; - public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException; + public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException; + public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException; + public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException; + public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException; + public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException; + public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException; public String readlink(String path) throws ErrnoException; - public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException; + public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException; public int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException; public int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException; public void remove(String path) throws ErrnoException; @@ -136,7 +138,7 @@ public interface Os { public StructUtsname uname(); public void unsetenv(String name) throws ErrnoException; public int waitpid(int pid, MutableInt status, int options) throws ErrnoException; - public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException; - public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException; - public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException; + public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException; + public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException; + public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException; } diff --git a/luni/src/main/java/libcore/io/Posix.java b/luni/src/main/java/libcore/io/Posix.java index c6f13ea..a5c3eb0 100644 --- a/luni/src/main/java/libcore/io/Posix.java +++ b/luni/src/main/java/libcore/io/Posix.java @@ -17,6 +17,7 @@ package libcore.io; import java.io.FileDescriptor; +import java.io.InterruptedIOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; @@ -84,6 +85,7 @@ public final class Posix implements Os { public native StructStat lstat(String path) throws ErrnoException; public native void mincore(long address, long byteCount, byte[] vector) throws ErrnoException; public native void mkdir(String path, int mode) throws ErrnoException; + public native void mkfifo(String path, int mode) throws ErrnoException; public native void mlock(long address, long byteCount) throws ErrnoException; public native long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException; public native void msync(long address, long byteCount, int flags) throws ErrnoException; @@ -93,44 +95,44 @@ public final class Posix implements Os { public native FileDescriptor[] pipe() throws ErrnoException; public native int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException; public native void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException; - public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException { + public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { if (buffer.isDirect()) { return preadBytes(fd, buffer, buffer.position(), buffer.remaining(), offset); } else { return preadBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining(), offset); } } - public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException { + public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { // This indirection isn't strictly necessary, but ensures that our public interface is type safe. return preadBytes(fd, bytes, byteOffset, byteCount, offset); } - private native int preadBytes(FileDescriptor fd, Object buffer, int bufferOffset, int byteCount, long offset) throws ErrnoException; - public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException { + private native int preadBytes(FileDescriptor fd, Object buffer, int bufferOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException; + public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { if (buffer.isDirect()) { return pwriteBytes(fd, buffer, buffer.position(), buffer.remaining(), offset); } else { return pwriteBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining(), offset); } } - public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException { + public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { // This indirection isn't strictly necessary, but ensures that our public interface is type safe. return pwriteBytes(fd, bytes, byteOffset, byteCount, offset); } - private native int pwriteBytes(FileDescriptor fd, Object buffer, int bufferOffset, int byteCount, long offset) throws ErrnoException; - public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { + private native int pwriteBytes(FileDescriptor fd, Object buffer, int bufferOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException; + public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { if (buffer.isDirect()) { return readBytes(fd, buffer, buffer.position(), buffer.remaining()); } else { return readBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining()); } } - public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { + public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { // This indirection isn't strictly necessary, but ensures that our public interface is type safe. return readBytes(fd, bytes, byteOffset, byteCount); } - private native int readBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException; + private native int readBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException, InterruptedIOException; public native String readlink(String path) throws ErrnoException; - public native int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException; + public native int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException; public int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { if (buffer.isDirect()) { return recvfromBytes(fd, buffer, buffer.position(), buffer.remaining(), flags, srcAddress); @@ -193,17 +195,17 @@ public final class Posix implements Os { public native StructUtsname uname(); public native void unsetenv(String name) throws ErrnoException; public native int waitpid(int pid, MutableInt status, int options) throws ErrnoException; - public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { + public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { if (buffer.isDirect()) { return writeBytes(fd, buffer, buffer.position(), buffer.remaining()); } else { return writeBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining()); } } - public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { + public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { // This indirection isn't strictly necessary, but ensures that our public interface is type safe. return writeBytes(fd, bytes, byteOffset, byteCount); } - private native int writeBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException; - public native int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException; + private native int writeBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException, InterruptedIOException; + public native int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException; } diff --git a/luni/src/main/native/AsynchronousSocketCloseMonitor.cpp b/luni/src/main/native/AsynchronousCloseMonitor.cpp index 9617e9d..92d5260 100644 --- a/luni/src/main/native/AsynchronousSocketCloseMonitor.cpp +++ b/luni/src/main/native/AsynchronousCloseMonitor.cpp @@ -14,9 +14,9 @@ * limitations under the License. */ -#define LOG_TAG "AsynchronousSocketCloseMonitor" +#define LOG_TAG "AsynchronousCloseMonitor" -#include "AsynchronousSocketCloseMonitor.h" +#include "AsynchronousCloseMonitor.h" #include "cutils/log.h" #include <errno.h> @@ -27,12 +27,12 @@ * We use an intrusive doubly-linked list to keep track of blocked threads. * This gives us O(1) insertion and removal, and means we don't need to do any allocation. * (The objects themselves are stack-allocated.) - * Waking potentially-blocked threads when a socket is closed is O(n) in the total number of - * blocked threads (not the number of threads actually blocked on the socket in question). - * For now at least, this seems like a good compromise for Android. + * Waking potentially-blocked threads when a file descriptor is closed is O(n) in the total number + * of blocked threads (not the number of threads actually blocked on the file descriptor in + * question). For now at least, this seems like a good compromise for Android. */ static pthread_mutex_t blockedThreadListMutex = PTHREAD_MUTEX_INITIALIZER; -static AsynchronousSocketCloseMonitor* blockedThreadList = NULL; +static AsynchronousCloseMonitor* blockedThreadList = NULL; /** * The specific signal chosen here is arbitrary. @@ -47,7 +47,7 @@ static void blockedThreadSignalHandler(int /*signal*/) { // Do nothing. We only sent this signal for its side-effect of interrupting syscalls. } -void AsynchronousSocketCloseMonitor::init() { +void AsynchronousCloseMonitor::init() { // Ensure that the signal we send interrupts system calls but doesn't kill threads. // Using sigaction(2) lets us ensure that the SA_RESTART flag is not set. // (The whole reason we're sending this signal is to unblock system calls!) @@ -61,21 +61,27 @@ void AsynchronousSocketCloseMonitor::init() { } } -void AsynchronousSocketCloseMonitor::signalBlockedThreads(int fd) { +void AsynchronousCloseMonitor::signalBlockedThreads(int fd) { ScopedPthreadMutexLock lock(&blockedThreadListMutex); - for (AsynchronousSocketCloseMonitor* it = blockedThreadList; it != NULL; it = it->mNext) { + for (AsynchronousCloseMonitor* it = blockedThreadList; it != NULL; it = it->mNext) { if (it->mFd == fd) { + it->mSignaled = true; pthread_kill(it->mThread, BLOCKED_THREAD_SIGNAL); // Keep going, because there may be more than one thread... } } } -AsynchronousSocketCloseMonitor::AsynchronousSocketCloseMonitor(int fd) { +bool AsynchronousCloseMonitor::wasSignaled() const { + return mSignaled; +} + +AsynchronousCloseMonitor::AsynchronousCloseMonitor(int fd) { ScopedPthreadMutexLock lock(&blockedThreadListMutex); // Who are we, and what are we waiting for? mThread = pthread_self(); mFd = fd; + mSignaled = false; // Insert ourselves at the head of the intrusive doubly-linked list... mPrev = NULL; mNext = blockedThreadList; @@ -85,7 +91,7 @@ AsynchronousSocketCloseMonitor::AsynchronousSocketCloseMonitor(int fd) { blockedThreadList = this; } -AsynchronousSocketCloseMonitor::~AsynchronousSocketCloseMonitor() { +AsynchronousCloseMonitor::~AsynchronousCloseMonitor() { ScopedPthreadMutexLock lock(&blockedThreadListMutex); // Unlink ourselves from the intrusive doubly-linked list... if (mNext != NULL) { diff --git a/luni/src/main/native/AsynchronousSocketCloseMonitor.h b/luni/src/main/native/AsynchronousCloseMonitor.h index 3370e22..eefbbdf 100644 --- a/luni/src/main/native/AsynchronousSocketCloseMonitor.h +++ b/luni/src/main/native/AsynchronousCloseMonitor.h @@ -14,47 +14,53 @@ * limitations under the License. */ -#ifndef ASYNCHRONOUS_SOCKET_CLOSE_MONITOR_H_included -#define ASYNCHRONOUS_SOCKET_CLOSE_MONITOR_H_included +#ifndef ASYNCHRONOUS_CLOSE_MONITOR_H_included +#define ASYNCHRONOUS_CLOSE_MONITOR_H_included #include "ScopedPthreadMutexLock.h" #include <pthread.h> /** - * AsynchronousSocketCloseMonitor helps implement Java's asynchronous Socket.close semantics. + * AsynchronousCloseMonitor helps implement Java's asynchronous close semantics. * - * AsynchronousSocketCloseMonitor::init must be called before anything else. + * AsynchronousCloseMonitor::init must be called before anything else. * - * Every blocking network I/O operation must be surrounded by an AsynchronousSocketCloseMonitor + * Every blocking I/O operation must be surrounded by an AsynchronousCloseMonitor * instance. For example: * * { - * AsynchronousSocketCloseMonitor monitor(fd); + * AsynchronousCloseMonitor monitor(fd); * byteCount = ::read(fd, buf, sizeof(buf)); * } * * To interrupt all threads currently blocked on file descriptor 'fd', call signalBlockedThreads: * - * AsynchronousSocketCloseMonitor::signalBlockedThreads(fd); + * AsynchronousCloseMonitor::signalBlockedThreads(fd); + * + * To test to see if the interruption was due to the signalBlockedThreads call: + * + * monitor.wasSignaled(); */ -class AsynchronousSocketCloseMonitor { +class AsynchronousCloseMonitor { public: - AsynchronousSocketCloseMonitor(int fd); - ~AsynchronousSocketCloseMonitor(); + AsynchronousCloseMonitor(int fd); + ~AsynchronousCloseMonitor(); + bool wasSignaled() const; static void init(); static void signalBlockedThreads(int fd); private: - AsynchronousSocketCloseMonitor* mPrev; - AsynchronousSocketCloseMonitor* mNext; + AsynchronousCloseMonitor* mPrev; + AsynchronousCloseMonitor* mNext; pthread_t mThread; int mFd; + bool mSignaled; // Disallow copy and assignment. - AsynchronousSocketCloseMonitor(const AsynchronousSocketCloseMonitor&); - void operator=(const AsynchronousSocketCloseMonitor&); + AsynchronousCloseMonitor(const AsynchronousCloseMonitor&); + void operator=(const AsynchronousCloseMonitor&); }; -#endif // ASYNCHRONOUS_SOCKET_CLOSE_MONITOR_H_included +#endif // ASYNCHRONOUS_CLOSE_MONITOR_H_included diff --git a/luni/src/main/native/libcore_io_AsynchronousCloseMonitor.cpp b/luni/src/main/native/libcore_io_AsynchronousCloseMonitor.cpp index 4f50ce5..a27e7b8 100644 --- a/luni/src/main/native/libcore_io_AsynchronousCloseMonitor.cpp +++ b/luni/src/main/native/libcore_io_AsynchronousCloseMonitor.cpp @@ -16,20 +16,20 @@ #define LOG_TAG "AsynchronousCloseMonitor" -#include "AsynchronousSocketCloseMonitor.h" +#include "AsynchronousCloseMonitor.h" #include "JNIHelp.h" #include "JniConstants.h" #include "jni.h" static void AsynchronousCloseMonitor_signalBlockedThreads(JNIEnv* env, jclass, jobject javaFd) { int fd = jniGetFDFromFileDescriptor(env, javaFd); - AsynchronousSocketCloseMonitor::signalBlockedThreads(fd); + AsynchronousCloseMonitor::signalBlockedThreads(fd); } static JNINativeMethod gMethods[] = { NATIVE_METHOD(AsynchronousCloseMonitor, signalBlockedThreads, "(Ljava/io/FileDescriptor;)V"), }; void register_libcore_io_AsynchronousCloseMonitor(JNIEnv* env) { - AsynchronousSocketCloseMonitor::init(); + AsynchronousCloseMonitor::init(); jniRegisterNativeMethods(env, "libcore/io/AsynchronousCloseMonitor", gMethods, NELEM(gMethods)); } diff --git a/luni/src/main/native/libcore_io_Posix.cpp b/luni/src/main/native/libcore_io_Posix.cpp index b9f16a7..65ee36f 100644 --- a/luni/src/main/native/libcore_io_Posix.cpp +++ b/luni/src/main/native/libcore_io_Posix.cpp @@ -16,7 +16,7 @@ #define LOG_TAG "Posix" -#include "AsynchronousSocketCloseMonitor.h" +#include "AsynchronousCloseMonitor.h" #include "cutils/log.h" #include "ExecStrings.h" #include "JNIHelp.h" @@ -68,31 +68,77 @@ struct addrinfo_deleter { }; /** - * Used to retry networking system calls that can return EINTR. Unlike TEMP_FAILURE_RETRY, - * this also handles the case where the reason for failure is that another thread called - * Socket.close. This macro also throws exceptions on failure. + * Used to retry networking system calls that can be interrupted with a signal. Unlike + * TEMP_FAILURE_RETRY, this also handles the case where + * AsynchronousCloseMonitor::signalBlockedThreads(fd) is used to signal a close() or + * Thread.interrupt(). Other signals that result in an EINTR result are ignored and the system call + * is retried. * - * Returns the result of 'exp', though a Java exception will be pending if the result is -1. + * Returns the result of the system call though a Java exception will be pending if the result is + * -1: a SocketException if signaled via AsynchronousCloseMonitor, or ErrnoException for other + * failures. */ #define NET_FAILURE_RETRY(jni_env, return_type, syscall_name, java_fd, ...) ({ \ return_type _rc = -1; \ do { \ + bool _wasSignaled; \ + int _syscallErrno; \ { \ int _fd = jniGetFDFromFileDescriptor(jni_env, java_fd); \ - AsynchronousSocketCloseMonitor _monitor(_fd); \ + AsynchronousCloseMonitor _monitor(_fd); \ _rc = syscall_name(_fd, __VA_ARGS__); \ + _syscallErrno = errno; \ + _wasSignaled = _monitor.wasSignaled(); \ } \ - if (_rc == -1) { \ - if (jniGetFDFromFileDescriptor(jni_env, java_fd) == -1) { \ - jniThrowException(jni_env, "java/net/SocketException", "Socket closed"); \ + if (_wasSignaled) { \ + jniThrowException(jni_env, "java/net/SocketException", "Socket closed"); \ + break; \ + } \ + if (_rc == -1 && _syscallErrno != EINTR) { \ + /* TODO: with a format string we could show the arguments too, like strace(1). */ \ + throwErrnoException(jni_env, # syscall_name); \ + break; \ + } \ + } while (_rc == -1); /* _syscallErrno == EINTR && !_wasSignaled */ \ + _rc; }) + +/** + * Used to retry system calls that can be interrupted with a signal. Unlike TEMP_FAILURE_RETRY, this + * also handles the case where AsynchronousCloseMonitor::signalBlockedThreads(fd) is used to signal + * a close() or Thread.interrupt(). Other signals that result in an EINTR result are ignored and the + * system call is retried. + * + * Returns the result of the system call though a Java exception will be pending if the result is + * -1: an IOException if the file descriptor is already closed, a InterruptedIOException if signaled + * via AsynchronousCloseMonitor, or ErrnoException for other failures. + */ +#define IO_FAILURE_RETRY(jni_env, return_type, syscall_name, java_fd, ...) ({ \ + return_type _rc = -1; \ + int _fd = jniGetFDFromFileDescriptor(jni_env, java_fd); \ + if (_fd == -1) { \ + jniThrowException(jni_env, "java/io/IOException", "File descriptor closed"); \ + } else { \ + do { \ + bool _wasSignaled; \ + int _syscallErrno; \ + { \ + int _fd = jniGetFDFromFileDescriptor(jni_env, java_fd); \ + AsynchronousCloseMonitor _monitor(_fd); \ + _rc = syscall_name(_fd, __VA_ARGS__); \ + _syscallErrno = errno; \ + _wasSignaled = _monitor.wasSignaled(); \ + } \ + if (_wasSignaled) { \ + jniThrowException(jni_env, "java/io/InterruptedIOException", # syscall_name " interrupted"); \ break; \ - } else if (errno != EINTR) { \ + } \ + if (_rc == -1 && _syscallErrno != EINTR) { \ /* TODO: with a format string we could show the arguments too, like strace(1). */ \ throwErrnoException(jni_env, # syscall_name); \ break; \ } \ - } \ - } while (_rc == -1); \ + } while (_rc == -1); /* && _syscallErrno == EINTR && !_wasSignaled */ \ + } \ _rc; }) static void throwException(JNIEnv* env, jclass exceptionClass, jmethodID ctor3, jmethodID ctor2, @@ -907,6 +953,14 @@ static void Posix_mkdir(JNIEnv* env, jobject, jstring javaPath, jint mode) { throwIfMinusOne(env, "mkdir", TEMP_FAILURE_RETRY(mkdir(path.c_str(), mode))); } +static void Posix_mkfifo(JNIEnv* env, jobject, jstring javaPath, jint mode) { + ScopedUtfChars path(env, javaPath); + if (path.c_str() == NULL) { + return; + } + throwIfMinusOne(env, "mkfifo", TEMP_FAILURE_RETRY(mkfifo(path.c_str(), mode))); +} + static void Posix_mlock(JNIEnv* env, jobject, jlong address, jlong byteCount) { void* ptr = reinterpret_cast<void*>(static_cast<uintptr_t>(address)); throwIfMinusOne(env, "mlock", TEMP_FAILURE_RETRY(mlock(ptr, byteCount))); @@ -990,11 +1044,9 @@ static jint Posix_poll(JNIEnv* env, jobject, jobjectArray javaStructs, jint time ++count; } - // Since we don't know which fds -- if any -- are sockets, be conservative and register - // all fds for asynchronous socket close monitoring. - std::vector<AsynchronousSocketCloseMonitor*> monitors; + std::vector<AsynchronousCloseMonitor*> monitors; for (size_t i = 0; i < count; ++i) { - monitors.push_back(new AsynchronousSocketCloseMonitor(fds[i].fd)); + monitors.push_back(new AsynchronousCloseMonitor(fds[i].fd)); } int rc = poll(fds.get(), count, timeoutMs); for (size_t i = 0; i < monitors.size(); ++i) { @@ -1029,8 +1081,7 @@ static jint Posix_preadBytes(JNIEnv* env, jobject, jobject javaFd, jobject javaB if (bytes.get() == NULL) { return -1; } - int fd = jniGetFDFromFileDescriptor(env, javaFd); - return throwIfMinusOne(env, "pread", TEMP_FAILURE_RETRY(pread64(fd, bytes.get() + byteOffset, byteCount, offset))); + return IO_FAILURE_RETRY(env, ssize_t, pread64, javaFd, bytes.get() + byteOffset, byteCount, offset); } static jint Posix_pwriteBytes(JNIEnv* env, jobject, jobject javaFd, jbyteArray javaBytes, jint byteOffset, jint byteCount, jlong offset) { @@ -1038,8 +1089,7 @@ static jint Posix_pwriteBytes(JNIEnv* env, jobject, jobject javaFd, jbyteArray j if (bytes.get() == NULL) { return -1; } - int fd = jniGetFDFromFileDescriptor(env, javaFd); - return throwIfMinusOne(env, "pwrite", TEMP_FAILURE_RETRY(pwrite64(fd, bytes.get() + byteOffset, byteCount, offset))); + return IO_FAILURE_RETRY(env, ssize_t, pwrite64, javaFd, bytes.get() + byteOffset, byteCount, offset); } static jint Posix_readBytes(JNIEnv* env, jobject, jobject javaFd, jobject javaBytes, jint byteOffset, jint byteCount) { @@ -1047,8 +1097,7 @@ static jint Posix_readBytes(JNIEnv* env, jobject, jobject javaFd, jobject javaBy if (bytes.get() == NULL) { return -1; } - int fd = jniGetFDFromFileDescriptor(env, javaFd); - return throwIfMinusOne(env, "read", TEMP_FAILURE_RETRY(read(fd, bytes.get() + byteOffset, byteCount))); + return IO_FAILURE_RETRY(env, ssize_t, read, javaFd, bytes.get() + byteOffset, byteCount); } static jstring Posix_readlink(JNIEnv* env, jobject, jstring javaPath) { @@ -1070,8 +1119,7 @@ static jint Posix_readv(JNIEnv* env, jobject, jobject javaFd, jobjectArray buffe if (!ioVec.init(buffers, offsets, byteCounts)) { return -1; } - int fd = jniGetFDFromFileDescriptor(env, javaFd); - return throwIfMinusOne(env, "readv", TEMP_FAILURE_RETRY(readv(fd, ioVec.get(), ioVec.size()))); + return IO_FAILURE_RETRY(env, ssize_t, readv, javaFd, ioVec.get(), ioVec.size()); } static jint Posix_recvfromBytes(JNIEnv* env, jobject, jobject javaFd, jobject javaBytes, jint byteOffset, jint byteCount, jint flags, jobject javaInetSocketAddress) { @@ -1417,8 +1465,7 @@ static jint Posix_writeBytes(JNIEnv* env, jobject, jobject javaFd, jbyteArray ja if (bytes.get() == NULL) { return -1; } - int fd = jniGetFDFromFileDescriptor(env, javaFd); - return throwIfMinusOne(env, "write", TEMP_FAILURE_RETRY(write(fd, bytes.get() + byteOffset, byteCount))); + return IO_FAILURE_RETRY(env, ssize_t, write, javaFd, bytes.get() + byteOffset, byteCount); } static jint Posix_writev(JNIEnv* env, jobject, jobject javaFd, jobjectArray buffers, jintArray offsets, jintArray byteCounts) { @@ -1426,8 +1473,7 @@ static jint Posix_writev(JNIEnv* env, jobject, jobject javaFd, jobjectArray buff if (!ioVec.init(buffers, offsets, byteCounts)) { return -1; } - int fd = jniGetFDFromFileDescriptor(env, javaFd); - return throwIfMinusOne(env, "writev", TEMP_FAILURE_RETRY(writev(fd, ioVec.get(), ioVec.size()))); + return IO_FAILURE_RETRY(env, ssize_t, writev, javaFd, ioVec.get(), ioVec.size()); } static JNINativeMethod gMethods[] = { @@ -1486,6 +1532,7 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(Posix, lstat, "(Ljava/lang/String;)Llibcore/io/StructStat;"), NATIVE_METHOD(Posix, mincore, "(JJ[B)V"), NATIVE_METHOD(Posix, mkdir, "(Ljava/lang/String;I)V"), + NATIVE_METHOD(Posix, mkfifo, "(Ljava/lang/String;I)V"), NATIVE_METHOD(Posix, mlock, "(JJ)V"), NATIVE_METHOD(Posix, mmap, "(JJIILjava/io/FileDescriptor;J)J"), NATIVE_METHOD(Posix, msync, "(JJI)V"), diff --git a/luni/src/main/native/sub.mk b/luni/src/main/native/sub.mk index e8b6e4a..6bec778 100644 --- a/luni/src/main/native/sub.mk +++ b/luni/src/main/native/sub.mk @@ -4,7 +4,7 @@ # or BUILD_*_LIBRARY. LOCAL_SRC_FILES := \ - AsynchronousSocketCloseMonitor.cpp \ + AsynchronousCloseMonitor.cpp \ ExecStrings.cpp \ IcuUtilities.cpp \ JniException.cpp \ diff --git a/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java b/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java index e5fd39f..30ae7eb 100755 --- a/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java +++ b/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java @@ -207,7 +207,10 @@ public final class InterruptedStreamTest extends TestCase { private static void confirmInterrupted(Thread thread) throws InterruptedException { // validate and clear interrupted bit before join - assertTrue(Thread.interrupted()); - thread.join(); + try { + assertTrue(Thread.interrupted()); + } finally { + thread.join(); + } } } diff --git a/luni/src/test/java/libcore/java/nio/channels/FileIOInterruptTest.java b/luni/src/test/java/libcore/java/nio/channels/FileIOInterruptTest.java new file mode 100644 index 0000000..2ac8827 --- /dev/null +++ b/luni/src/test/java/libcore/java/nio/channels/FileIOInterruptTest.java @@ -0,0 +1,628 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package libcore.java.nio.channels; + +import junit.framework.TestCase; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousCloseException; +import java.nio.channels.ClosedByInterruptException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.FileChannel; +import libcore.io.ErrnoException; +import libcore.io.Libcore; +import libcore.io.OsConstants; + +import static libcore.io.IoUtils.closeQuietly; + +/** + * A test for file interrupt behavior. Because forcing a real file to block on read or write is + * difficult this test uses Unix FIFO / Named Pipes. FIFOs appear to Java as files but the test + * has more control over the available data. Reader will block until the other end writes, and + * writers can also be made to block. + * + * <p>Using FIFOs has a few drawbacks: + * <ol> + * <li>FIFOs are not supported from Java or the command-line on Android, so this test includes + * native code to create the FIFO. + * <li>FIFOs will not open() until there is both a reader and a writer of the FIFO; each test must + * always attach both ends or experience a blocked test. + * <li>FIFOs are not supported on some file systems. e.g. VFAT, so the test has to be particular + * about the temporary directory it uses to hold the FIFO. + * <li>Writes to FIFOs are buffered by the OS which makes blocking behavior more difficult to + * induce. See {@link ChannelWriter} and {@link StreamWriter}. + * </ol> + */ +public class FileIOInterruptTest extends TestCase { + + private static File VOGAR_DEVICE_TEMP_DIR = new File("/data/data/file_io_interrupt_test"); + + private File fifoFile; + + @Override + public void setUp() throws Exception { + super.setUp(); + + // This test relies on a FIFO file. The file system must support FIFOs, so we check the path. + String tmpDirName = System.getProperty("java.io.tmpdir"); + File tmpDir; + if (tmpDirName.startsWith("/sdcard")) { + // Vogar execution on device runs in /sdcard. Unfortunately the file system used does not + // support FIFOs so the test must use one that is more likely to work. + if (!VOGAR_DEVICE_TEMP_DIR.exists()) { + assertTrue(VOGAR_DEVICE_TEMP_DIR.mkdir()); + } + VOGAR_DEVICE_TEMP_DIR.deleteOnExit(); + tmpDir = VOGAR_DEVICE_TEMP_DIR; + } else { + tmpDir = new File(tmpDirName); + } + fifoFile = new File(tmpDir, "fifo_file.tmp"); + if (fifoFile.exists()) { + fifoFile.delete(); + } + fifoFile.deleteOnExit(); + + // Create the fifo. This will throw an exception if the file system does not support it. + Libcore.os.mkfifo(fifoFile.getAbsolutePath(), OsConstants.S_IRWXU); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + fifoFile.delete(); + VOGAR_DEVICE_TEMP_DIR.delete(); + } + + public void testStreamRead_exceptionWhenAlreadyClosed() throws Exception { + FifoWriter fifoWriter = new FifoWriter(fifoFile); + fifoWriter.start(); + + FileInputStream fis = new FileInputStream(fifoFile); + fis.close(); + + byte[] buffer = new byte[10]; + try { + fis.read(buffer); + fail(); + } catch (IOException expected) { + assertSame(IOException.class, expected.getClass()); + } + + fifoWriter.tidyUp(); + } + + // This test fails on the RI: close() does not wake up a blocking FileInputStream.read() call. + public void testStreamRead_exceptionOnCloseWhenBlocked() throws Exception { + FifoWriter fifoWriter = new FifoWriter(fifoFile); + fifoWriter.start(); + + FileInputStream fis = new FileInputStream(fifoFile); + StreamReader streamReader = new StreamReader(fis); + Thread streamReaderThread = createAndStartThread("StreamReader", streamReader); + + // Delay until we can be fairly sure the reader thread is blocking. + streamReader.waitForThreadToBlock(); + + // Now close the OutputStream to see what happens. + fis.close(); + + // Test for expected behavior in the reader thread. + waitToDie(streamReaderThread); + assertSame(InterruptedIOException.class, streamReader.ioe.getClass()); + assertFalse(streamReader.wasInterrupted); + + // Tidy up the writer thread. + fifoWriter.tidyUp(); + } + + public void testStreamWrite_exceptionWhenAlreadyClosed() throws Exception { + FifoReader fifoReader = new FifoReader(fifoFile); + fifoReader.start(); + + FileOutputStream fos = new FileOutputStream(fifoFile); + byte[] buffer = new byte[10]; + fos.close(); + + try { + fos.write(buffer); + fail(); + } catch (IOException expected) { + assertSame(IOException.class, expected.getClass()); + } + + fifoReader.tidyUp(); + } + + // This test fails on the RI: close() does not wake up a blocking FileInputStream.write() call. + public void testStreamWrite_exceptionOnCloseWhenBlocked() throws Exception { + FifoReader fifoReader = new FifoReader(fifoFile); + fifoReader.start(); + + FileOutputStream fos = new FileOutputStream(fifoFile); + StreamWriter streamWriter = new StreamWriter(fos); + Thread streamWriterThread = createAndStartThread("StreamWriter", streamWriter); + + // Delay until we can be fairly sure the writer thread is blocking. + streamWriter.waitForThreadToBlock(); + + // Now close the OutputStream to see what happens. + fos.close(); + + // Test for expected behavior in the writer thread. + waitToDie(streamWriterThread); + assertSame(InterruptedIOException.class, streamWriter.ioe.getClass()); + assertFalse(streamWriter.wasInterrupted); + + // Tidy up the reader thread. + fifoReader.tidyUp(); + } + + public void testChannelRead_exceptionWhenAlreadyClosed() throws Exception { + testChannelRead_exceptionWhenAlreadyClosed(ChannelReader.Method.READ); + } + + public void testChannelReadV_exceptionWhenAlreadyClosed() throws Exception { + testChannelRead_exceptionWhenAlreadyClosed(ChannelReader.Method.READV); + } + + private void testChannelRead_exceptionWhenAlreadyClosed(ChannelReader.Method method) + throws Exception { + FifoWriter fifoWriter = new FifoWriter(fifoFile); + fifoWriter.start(); + FileInputStream fis = new FileInputStream(fifoFile); + FileChannel fileInputChannel = fis.getChannel(); + fileInputChannel.close(); + + ByteBuffer buffer = ByteBuffer.allocateDirect(10); + try { + if (method == ChannelReader.Method.READ) { + fileInputChannel.read(buffer); + } else { + ByteBuffer buffer2 = ByteBuffer.allocateDirect(10); + fileInputChannel.read(new ByteBuffer[] { buffer, buffer2}); + } + fail(); + } catch (IOException expected) { + assertSame(ClosedChannelException.class, expected.getClass()); + } + + fifoWriter.tidyUp(); + } + + public void testChannelRead_exceptionOnCloseWhenBlocked() throws Exception { + testChannelRead_exceptionOnCloseWhenBlocked(ChannelReader.Method.READ); + } + + public void testChannelReadV_exceptionOnCloseWhenBlocked() throws Exception { + testChannelRead_exceptionOnCloseWhenBlocked(ChannelReader.Method.READV); + } + + private void testChannelRead_exceptionOnCloseWhenBlocked(ChannelReader.Method method) + throws Exception { + FifoWriter fifoWriter = new FifoWriter(fifoFile); + fifoWriter.start(); + FileInputStream fis = new FileInputStream(fifoFile); + FileChannel fileInputChannel = fis.getChannel(); + + ChannelReader channelReader = new ChannelReader(fileInputChannel, method); + Thread channelReaderThread = createAndStartThread("ChannelReader", channelReader); + + // Delay until we can be fairly sure the reader thread is blocking. + channelReader.waitForThreadToBlock(); + + // Now close the FileChannel to see what happens. + fileInputChannel.close(); + + // Test for expected behavior in the reader thread. + waitToDie(channelReaderThread); + assertSame(AsynchronousCloseException.class, channelReader.ioe.getClass()); + assertFalse(channelReader.wasInterrupted); + + // Tidy up the writer thread. + fifoWriter.tidyUp(); + } + + public void testChannelRead_exceptionOnInterrupt() throws Exception { + testChannelRead_exceptionOnInterrupt(ChannelReader.Method.READ); + } + + public void testChannelReadV_exceptionOnInterrupt() throws Exception { + testChannelRead_exceptionOnInterrupt(ChannelReader.Method.READV); + } + + private void testChannelRead_exceptionOnInterrupt(ChannelReader.Method method) throws Exception { + FifoWriter fifoWriter = new FifoWriter(fifoFile); + fifoWriter.start(); + FileChannel fileChannel = new FileInputStream(fifoFile).getChannel(); + + ChannelReader channelReader = new ChannelReader(fileChannel, method); + Thread channelReaderThread = createAndStartThread("ChannelReader", channelReader); + + // Delay until we can be fairly sure the reader thread is blocking. + channelReader.waitForThreadToBlock(); + + // Now interrupt the reader thread to see what happens. + channelReaderThread.interrupt(); + + // Test for expected behavior in the reader thread. + waitToDie(channelReaderThread); + assertSame(ClosedByInterruptException.class, channelReader.ioe.getClass()); + assertTrue(channelReader.wasInterrupted); + + // Tidy up the writer thread. + fifoWriter.tidyUp(); + } + + public void testChannelWrite_exceptionWhenAlreadyClosed() throws Exception { + testChannelWrite_exceptionWhenAlreadyClosed(ChannelWriter.Method.WRITE); + } + + public void testChannelWriteV_exceptionWhenAlreadyClosed() throws Exception { + testChannelWrite_exceptionWhenAlreadyClosed(ChannelWriter.Method.WRITEV); + } + + private void testChannelWrite_exceptionWhenAlreadyClosed(ChannelWriter.Method method) + throws Exception { + FifoReader fifoReader = new FifoReader(fifoFile); + fifoReader.start(); + FileChannel fileOutputChannel = new FileOutputStream(fifoFile).getChannel(); + fileOutputChannel.close(); + + ByteBuffer buffer = ByteBuffer.allocateDirect(10); + try { + if (method == ChannelWriter.Method.WRITE) { + fileOutputChannel.write(buffer); + } else { + ByteBuffer buffer2 = ByteBuffer.allocateDirect(10); + fileOutputChannel.write(new ByteBuffer[] { buffer, buffer2 }); + } + fail(); + } catch (IOException expected) { + assertSame(ClosedChannelException.class, expected.getClass()); + } + + fifoReader.tidyUp(); + } + + public void testChannelWrite_exceptionOnCloseWhenBlocked() throws Exception { + testChannelWrite_exceptionOnCloseWhenBlocked(ChannelWriter.Method.WRITE); + } + + public void testChannelWriteV_exceptionOnCloseWhenBlocked() throws Exception { + testChannelWrite_exceptionOnCloseWhenBlocked(ChannelWriter.Method.WRITEV); + } + + private void testChannelWrite_exceptionOnCloseWhenBlocked(ChannelWriter.Method method) + throws Exception { + FifoReader fifoReader = new FifoReader(fifoFile); + fifoReader.start(); + FileChannel fileOutputChannel = new FileOutputStream(fifoFile).getChannel(); + + ChannelWriter channelWriter = new ChannelWriter(fileOutputChannel, method); + Thread channelWriterThread = createAndStartThread("ChannelWriter", channelWriter); + + // Delay until we can be fairly sure the writer thread is blocking. + channelWriter.waitForThreadToBlock(); + + // Now close the channel to see what happens. + fileOutputChannel.close(); + + // Test for expected behavior in the writer thread. + waitToDie(channelWriterThread); + // The RI throws ChannelClosedException. AsynchronousCloseException is more correct according to + // the docs. + assertSame(AsynchronousCloseException.class, channelWriter.ioe.getClass()); + assertFalse(channelWriter.wasInterrupted); + + // Tidy up the writer thread. + fifoReader.tidyUp(); + } + + public void testChannelWrite_exceptionOnInterrupt() throws Exception { + testChannelWrite_exceptionOnInterrupt(ChannelWriter.Method.WRITE); + } + + public void testChannelWriteV_exceptionOnInterrupt() throws Exception { + testChannelWrite_exceptionOnInterrupt(ChannelWriter.Method.WRITEV); + } + + private void testChannelWrite_exceptionOnInterrupt(ChannelWriter.Method method) throws Exception { + FifoReader fifoReader = new FifoReader(fifoFile); + fifoReader.start(); + + FileChannel fileChannel = new FileOutputStream(fifoFile).getChannel(); + ChannelWriter channelWriter = new ChannelWriter(fileChannel, method); + Thread channelWriterThread = createAndStartThread("ChannelWriter", channelWriter); + + // Delay until we can be fairly sure the writer thread is blocking. + channelWriter.waitForThreadToBlock(); + + // Now interrupt the writer thread to see what happens. + channelWriterThread.interrupt(); + + // Test for expected behavior in the writer thread. + waitToDie(channelWriterThread); + assertSame(ClosedByInterruptException.class, channelWriter.ioe.getClass()); + assertTrue(channelWriter.wasInterrupted); + + // Tidy up the reader thread. + fifoReader.tidyUp(); + } + + private static class StreamReader implements Runnable { + + private final FileInputStream inputStream; + volatile boolean started; + volatile IOException ioe; + volatile boolean wasInterrupted; + + StreamReader(FileInputStream inputStream) { + this.inputStream = inputStream; + } + + @Override + public void run() { + byte[] buffer = new byte[10]; + try { + started = true; + int bytesRead = inputStream.read(buffer); + fail("This isn't supposed to happen: read() returned: " + bytesRead); + } catch (IOException e) { + this.ioe = e; + } + wasInterrupted = Thread.interrupted(); + } + + public void waitForThreadToBlock() { + for (int i = 0; i < 10 && !started; i++) { + delay(100); + } + assertTrue(started); + // Just give it some more time to start blocking. + delay(100); + } + } + + private static class StreamWriter implements Runnable { + + private final FileOutputStream outputStream; + volatile int bytesWritten; + volatile IOException ioe; + volatile boolean wasInterrupted; + + StreamWriter(FileOutputStream outputStream) { + this.outputStream = outputStream; + } + + @Override + public void run() { + // Writes to FIFOs are buffered. We try to fill the buffer and induce blocking (the + // buffer is typically 64k). + byte[] buffer = new byte[10000]; + while (true) { + try { + outputStream.write(buffer); + bytesWritten += buffer.length; + } catch (IOException e) { + this.ioe = e; + break; + } + wasInterrupted = Thread.interrupted(); + } + } + + public void waitForThreadToBlock() { + int lastCount = bytesWritten; + for (int i = 0; i < 10; i++) { + delay(500); + int newBytesWritten = bytesWritten; + if (newBytesWritten > 0 && lastCount == newBytesWritten) { + // The thread is probably blocking. + return; + } + lastCount = bytesWritten; + } + fail("Writer never started blocking. Bytes written: " + bytesWritten); + } + } + + private static class ChannelReader implements Runnable { + enum Method { + READ, + READV, + } + + private final FileChannel channel; + private final Method method; + volatile boolean started; + volatile IOException ioe; + volatile boolean wasInterrupted; + + ChannelReader(FileChannel channel, Method method) { + this.channel = channel; + this.method = method; + } + + @Override + public void run() { + ByteBuffer buffer = ByteBuffer.allocateDirect(10); + try { + started = true; + if (method == Method.READ) { + channel.read(buffer); + } else { + ByteBuffer buffer2 = ByteBuffer.allocateDirect(10); + channel.read(new ByteBuffer[] { buffer, buffer2 }); + } + fail("All tests should block until an exception"); + } catch (IOException e) { + this.ioe = e; + } + wasInterrupted = Thread.interrupted(); + } + + public void waitForThreadToBlock() { + for (int i = 0; i < 10 && !started; i++) { + delay(100); + } + assertTrue(started); + // Just give it some more time to start blocking. + delay(100); + } + } + + private static class ChannelWriter implements Runnable { + enum Method { + WRITE, + WRITEV, + } + + private final FileChannel channel; + private final Method method; + volatile int bytesWritten; + volatile IOException ioe; + volatile boolean wasInterrupted; + + ChannelWriter(FileChannel channel, Method method) { + this.channel = channel; + this.method = method; + } + + @Override + public void run() { + ByteBuffer buffer1 = ByteBuffer.allocateDirect(10000); + ByteBuffer buffer2 = ByteBuffer.allocateDirect(10000); + // Writes to FIFOs are buffered. We try to fill the buffer and induce blocking (the + // buffer is typically 64k). + while (true) { + // Make the buffers look non-empty. + buffer1.position(0).limit(buffer1.capacity()); + buffer2.position(0).limit(buffer2.capacity()); + try { + if (method == Method.WRITE) { + bytesWritten += channel.write(buffer1); + } else { + bytesWritten += channel.write(new ByteBuffer[]{ buffer1, buffer2 }); + } + } catch (IOException e) { + this.ioe = e; + break; + } + } + wasInterrupted = Thread.interrupted(); + } + + public void waitForThreadToBlock() { + int lastCount = bytesWritten; + for (int i = 0; i < 10; i++) { + delay(500); + int newBytesWritten = bytesWritten; + if (newBytesWritten > 0 && lastCount == newBytesWritten) { + // The thread is probably blocking. + return; + } + lastCount = bytesWritten; + } + fail("Writer never started blocking. Bytes written: " + bytesWritten); + } + } + + /** + * Opens a FIFO for writing. Exists to unblock the other end of the FIFO. + */ + private static class FifoWriter extends Thread { + + private final File file; + private FileOutputStream fos; + + public FifoWriter(File file) { + super("FifoWriter"); + this.file = file; + } + + @Override + public void run() { + try { + fos = new FileOutputStream(file); + } catch (IOException ignored) { + } + } + + public void tidyUp() { + FileIOInterruptTest.waitToDie(this); + closeQuietly(fos); + } + } + + /** + * Opens a FIFO for reading. Exists to unblock the other end of the FIFO. + */ + private static class FifoReader extends Thread { + + private final File file; + private FileInputStream fis; + + public FifoReader(File file) { + super("FifoReader"); + this.file = file; + } + + @Override + public void run() { + try { + fis = new FileInputStream(file); + } catch (IOException ignored) { + } + } + + public void tidyUp() { + FileIOInterruptTest.waitToDie(this); + closeQuietly(fis); + } + } + + private static Thread createAndStartThread(String name, Runnable runnable) { + Thread t = new Thread(runnable, name); + t.setDaemon(true); + t.start(); + return t; + } + + private static void waitToDie(Thread thread) { + try { + thread.join(5000); + } catch (InterruptedException ignored) { + } + + if (thread.isAlive()) { + fail("Thread \"" + thread.getName() + "\" did not exit."); + } + } + + private static void delay(int millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException ignored) { + } + } + +} |