jdk-24/test/jdk/java/nio/channels/Channels/SocketChannelStreams.java

481 lines
15 KiB
Java

/*
* 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<T, U> {
void accept(T t, U u) throws Exception;
}
/**
* Invokes the consumer with a connected pair of socket channels.
*/
private static void withConnection(ThrowingBiConsumer<SocketChannel, SocketChannel> 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);
}
}