/* * Copyright (c) 2022, 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 8279339 * @run testng SocketChannelStreams * @summary Exercise InputStream/OutputStream returned by Channels.newXXXStream * when channel is a SocketChannel */ import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.channels.Channels; import java.nio.channels.IllegalBlockingModeException; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.testng.annotations.*; import static org.testng.Assert.*; @Test public class SocketChannelStreams { private ScheduledExecutorService executor; @BeforeClass() public void init() { executor = Executors.newSingleThreadScheduledExecutor(); } @AfterClass public void finish() { executor.shutdown(); } /** * Test read when bytes are available. */ public void testRead1() throws Exception { withConnection((sc, peer) -> { write(peer, 99); int n = Channels.newInputStream(sc).read(); assertEquals(n, 99); }); } /** * Test read blocking before bytes are available. */ public void testRead2() throws Exception { withConnection((sc, peer) -> { scheduleWrite(peer, 99, 1000); int n = Channels.newInputStream(sc).read(); assertEquals(n, 99); }); } /** * Test read after peer has closed connection. */ public void testRead3() throws Exception { withConnection((sc, peer) -> { peer.close(); int n = Channels.newInputStream(sc).read(); assertEquals(n, -1); }); } /** * Test read blocking before peer closes connection. */ public void testRead4() throws Exception { withConnection((sc, peer) -> { scheduleClose(peer, 1000); int n = Channels.newInputStream(sc).read(); assertEquals(n, -1); }); } /** * Test async close of channel when thread blocked in read. */ public void testRead5() throws Exception { withConnection((sc, peer) -> { scheduleClose(sc, 2000); InputStream in = Channels.newInputStream(sc); expectThrows(IOException.class, () -> in.read()); }); } /** * Test async close of input stream, when thread blocked in read. */ public void testRead6() throws Exception { withConnection((sc, peer) -> { InputStream in = Channels.newInputStream(sc); scheduleClose(in, 2000); expectThrows(IOException.class, () -> in.read()); }); } /** * Test interrupt status set before read. */ public void testRead7() throws Exception { withConnection((sc, peer) -> { Thread.currentThread().interrupt(); try { InputStream in = Channels.newInputStream(sc); expectThrows(IOException.class, () -> in.read()); } finally { Thread.interrupted(); // clear interrupt } assertFalse(sc.isOpen()); }); } /** * Test interrupt of thread blocked in read. */ public void testRead8() throws Exception { withConnection((sc, peer) -> { Future interrupter = scheduleInterrupt(Thread.currentThread(), 2000); try { InputStream in = Channels.newInputStream(sc); expectThrows(IOException.class, () -> in.read()); } finally { interrupter.cancel(true); Thread.interrupted(); // clear interrupt } assertFalse(sc.isOpen()); }); } /** * Test that read is untimed when SO_TIMEOUT is set on the Socket adaptor. */ public void testRead9() throws Exception { withConnection((sc, peer) -> { sc.socket().setSoTimeout(100); scheduleWrite(peer, 99, 2000); // read should block until bytes are available int b = Channels.newInputStream(sc).read(); assertTrue(b == 99); }); } /** * Test write. */ public void testWrite1() throws Exception { withConnection((sc, peer) -> { OutputStream out = Channels.newOutputStream(sc); out.write(99); int n = read(peer); assertEquals(n, 99); }); } /** * Test async close of channel when thread blocked in write. */ public void testWrite2() throws Exception { withConnection((sc, peer) -> { scheduleClose(sc, 2000); expectThrows(IOException.class, () -> { OutputStream out = Channels.newOutputStream(sc); byte[] data = new byte[64*1000]; while (true) { out.write(data); } }); }); } /** * Test async close of output stream when thread blocked in write. */ public void testWrite3() throws Exception { withConnection((sc, peer) -> { OutputStream out = Channels.newOutputStream(sc); scheduleClose(out, 2000); expectThrows(IOException.class, () -> { byte[] data = new byte[64*1000]; while (true) { out.write(data); } }); }); } /** * Test interrupt status set before write. */ public void testWrite4() throws Exception { withConnection((sc, peer) -> { Thread.currentThread().interrupt(); try { OutputStream out = Channels.newOutputStream(sc); expectThrows(IOException.class, () -> out.write(99)); } finally { Thread.interrupted(); // clear interrupt } assertFalse(sc.isOpen()); }); } /** * Test interrupt of thread blocked in write. */ public void testWrite5() throws Exception { withConnection((sc, peer) -> { Future interrupter = scheduleInterrupt(Thread.currentThread(), 2000); try { expectThrows(IOException.class, () -> { OutputStream out = Channels.newOutputStream(sc); byte[] data = new byte[64*1000]; while (true) { out.write(data); } }); } finally { interrupter.cancel(true); Thread.interrupted(); // clear interrupt } assertFalse(sc.isOpen()); }); } /** * Test read when another thread is blocked in write. The read should * complete immediately. */ public void testConcurrentReadWrite1() throws Exception { withConnection((sc, peer) -> { InputStream in = Channels.newInputStream(sc); OutputStream out = Channels.newOutputStream(sc); // block thread in write fork(() -> { var data = new byte[64*1024]; for (;;) { out.write(data); } }); Thread.sleep(1000); // give writer time to block // test read, should not be blocked by writer thread write(peer, 99); int n = in.read(); assertEquals(n, 99); }); } /** * Test read when another thread is blocked in write. The read should * block until bytes are available. */ public void testConcurrentReadWrite2() throws Exception { withConnection((sc, peer) -> { InputStream in = Channels.newInputStream(sc); OutputStream out = Channels.newOutputStream(sc); // block thread in write fork(() -> { var data = new byte[64*1024]; for (;;) { out.write(data); } }); Thread.sleep(1000); // give writer time to block // test read, should not be blocked by writer thread scheduleWrite(peer, 99, 500); int n = in.read(); assertEquals(n, 99); }); } /** * Test writing when another thread is blocked in read. */ public void testConcurrentReadWrite3() throws Exception { withConnection((sc, peer) -> { InputStream in = Channels.newInputStream(sc); OutputStream out = Channels.newOutputStream(sc); // block thread in read fork(() -> { in.read(); }); Thread.sleep(100); // give reader time to block // test write, should not be blocked by reader thread out.write(99); int n = read(peer); assertEquals(n, 99); }); } /** * Test read/write when channel configured non-blocking. */ public void testIllegalBlockingMode() throws Exception { withConnection((sc, peer) -> { InputStream in = Channels.newInputStream(sc); OutputStream out = Channels.newOutputStream(sc); sc.configureBlocking(false); expectThrows(IllegalBlockingModeException.class, () -> in.read()); expectThrows(IllegalBlockingModeException.class, () -> out.write(99)); }); } /** * Test NullPointerException. */ public void testNullPointerException() throws Exception { withConnection((sc, peer) -> { InputStream in = Channels.newInputStream(sc); OutputStream out = Channels.newOutputStream(sc); expectThrows(NullPointerException.class, () -> in.read(null)); expectThrows(NullPointerException.class, () -> in.read(null, 0, 0)); expectThrows(NullPointerException.class, () -> out.write(null)); expectThrows(NullPointerException.class, () -> out.write(null, 0, 0)); }); } /** * Test IndexOutOfBoundsException. */ public void testIndexOutOfBoundsException() throws Exception { withConnection((sc, peer) -> { InputStream in = Channels.newInputStream(sc); OutputStream out = Channels.newOutputStream(sc); byte[] ba = new byte[100]; expectThrows(IndexOutOfBoundsException.class, () -> in.read(ba, -1, 1)); expectThrows(IndexOutOfBoundsException.class, () -> in.read(ba, 0, -1)); expectThrows(IndexOutOfBoundsException.class, () -> in.read(ba, 0, 1000)); expectThrows(IndexOutOfBoundsException.class, () -> in.read(ba, 1, 100)); expectThrows(IndexOutOfBoundsException.class, () -> out.write(ba, -1, 1)); expectThrows(IndexOutOfBoundsException.class, () -> out.write(ba, 0, -1)); expectThrows(IndexOutOfBoundsException.class, () -> out.write(ba, 0, 1000)); expectThrows(IndexOutOfBoundsException.class, () -> out.write(ba, 1, 100)); }); } // -- test infrastructure -- private interface ThrowingTask { void run() throws Exception; } private interface ThrowingBiConsumer { void accept(T t, U u) throws Exception; } /** * Invokes the consumer with a connected pair of socket channels. */ private static void withConnection(ThrowingBiConsumer consumer) throws Exception { var loopback = InetAddress.getLoopbackAddress(); try (ServerSocketChannel listener = ServerSocketChannel.open()) { listener.bind(new InetSocketAddress(loopback, 0)); try (SocketChannel sc = SocketChannel.open(listener.getLocalAddress())) { try (SocketChannel peer = listener.accept()) { consumer.accept(sc, peer); } } } } /** * Forks a thread to execute the given task. */ private Future fork(ThrowingTask task) { ExecutorService pool = Executors.newFixedThreadPool(1); try { return pool.submit(() -> { task.run(); return null; }); } finally { pool.shutdown(); } } /** * Read a byte from the given socket channel. */ private int read(SocketChannel sc) throws IOException { return sc.socket().getInputStream().read(); } /** * Write a byte to the given socket channel. */ private void write(SocketChannel sc, int b) throws IOException { sc.socket().getOutputStream().write(b); } /** * Writes the given data to the socket channel after a delay. */ private Future scheduleWrite(SocketChannel sc, byte[] data, long delay) { return schedule(() -> { try { sc.socket().getOutputStream().write(data); } catch (IOException ioe) { } }, delay); } /** * Writes a byte to the socket channel after a delay. */ private Future scheduleWrite(SocketChannel sc, int b, long delay) { return scheduleWrite(sc, new byte[] { (byte)b }, delay); } /** * Closes the given object after a delay. */ private Future scheduleClose(Closeable c, long delay) { return schedule(() -> { try { c.close(); } catch (IOException ioe) { } }, delay); } /** * Interrupts the given Thread after a delay. */ private Future scheduleInterrupt(Thread t, long delay) { return schedule(() -> t.interrupt(), delay); } /** * Schedules the given task to run after a delay. */ private Future schedule(Runnable task, long delay) { return executor.schedule(task, delay, TimeUnit.MILLISECONDS); } }