8293696: java/nio/channels/DatagramChannel/SelectWhenRefused.java fails with "Unexpected wakeup"

Reviewed-by: dfuchs, msheppar
This commit is contained in:
Darragh Clarke 2022-11-11 11:56:41 +00:00 committed by Aleksei Efimov
parent 4a300818fe
commit fdabd37960

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 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
@ -25,16 +25,25 @@
* @bug 6935563 7044870
* @summary Test that Selector does not select an unconnected DatagramChannel when
* ICMP port unreachable received
* @run junit/othervm SelectWhenRefused
*/
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.net.*;
import java.io.IOException;
import java.util.Set;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
public class SelectWhenRefused {
static final int MAX_TRIES = 3;
static final String GREETINGS_MESSAGE = "Greetings from SelectWhenRefused!";
public static void main(String[] args) throws IOException {
@Test
public void test() throws IOException {
DatagramChannel dc1 = DatagramChannel.open().bind(new InetSocketAddress(0));
int port = dc1.socket().getLocalPort();
@ -50,53 +59,193 @@ public class SelectWhenRefused {
dc.register(sel, SelectionKey.OP_READ);
/* Test 1: not connected so ICMP port unreachable should not be received */
sendDatagram(dc, refuser);
int n = sel.select(2000);
if (n > 0) {
sel.selectedKeys().clear();
// BindException will be thrown if another service is using
// our expected refuser port, cannot run just exit.
DatagramChannel.open().bind(refuser).close();
throw new RuntimeException("Unexpected wakeup");
for (int i = 1; i <= MAX_TRIES; i++) {
if (testNoPUEBeforeConnection(dc, refuser, sel, i)) {
break; // test succeeded
}
assertNotEquals(i, MAX_TRIES, "testNoPUEBeforeConnection: too many retries");
}
/* Test 2: connected so ICMP port unreachable may be received */
dc.connect(refuser);
try {
sendDatagram(dc, refuser);
n = sel.select(2000);
if (n > 0) {
sel.selectedKeys().clear();
try {
n = dc.read(ByteBuffer.allocate(100));
throw new RuntimeException("Unexpected datagram received");
} catch (PortUnreachableException pue) {
// expected
for (int i = 1; i <= MAX_TRIES; i++) {
if (testPUEOnConnect(dc, refuser, sel, i)) {
break; // test passed
}
assertNotEquals(i, MAX_TRIES, "testPUEOnConnect: too many retries");
}
} finally {
dc.disconnect();
}
/* Test 3: not connected so ICMP port unreachable should not be received */
sendDatagram(dc, refuser);
n = sel.select(2000);
if (n > 0) {
throw new RuntimeException("Unexpected wakeup after disconnect");
for (int i = 1; i <= MAX_TRIES; i++) {
if (testNoPUEAfterDisconnect(dc, refuser, sel, i)) {
break; // test passed
}
assertNotEquals(i, MAX_TRIES, "testNoPUEAfterDisconnect: too many retries");
}
} catch(BindException e) {
} catch (BindException e) {
// Do nothing, some other test has used this port
System.out.println("Skipping test: refuser port has been reused: " + e);
} finally {
sel.close();
dc.close();
}
}
/*
* Send a datagram to non existent unconnected UDP end point
* This shouldn't result in an PortUnreachableException
* Handle unexpected read events on the senders DC with
* retry when message received is external and Throw Exception
* on receipt of own message
*/
static boolean testNoPUEBeforeConnection(DatagramChannel dc,
SocketAddress refuser,
Selector sel,
int retryCount) throws IOException {
sendDatagram(dc, refuser);
int n = sel.select(2000);
if (n > 0) {
boolean mayRetry = checkUnexpectedWakeup(sel.selectedKeys());
boolean tooManyRetries = retryCount >= MAX_TRIES;
sel.selectedKeys().clear();
if (mayRetry && !tooManyRetries) {
return false; // will retry
}
// BindException will be thrown if another service is using
// our expected refuser port, cannot run just exit.
DatagramChannel.open().bind(refuser).close();
throw new RuntimeException("Unexpected wakeup");
}
return true; // test passed
}
/*
* Send a datagram to a connected UDP end point
* This should result in an PortUnreachableException
* Handle unexpected read events on the senders DC with
* retry when message received is external and Throw Exception
* on receipt of own message
*/
static boolean testPUEOnConnect(DatagramChannel dc,
SocketAddress refuser,
Selector sel,
int retryCount) throws IOException {
sendDatagram(dc, refuser);
int n = sel.select(2000);
if (n > 0) {
sel.selectedKeys().clear();
try {
// Attempt to read from Selected Key
ByteBuffer buf = ByteBuffer.allocate(100);
SocketAddress sa = dc.receive(buf);
if (sa != null) {
buf.flip();
byte[] bytes = new byte[buf.remaining()];
buf.get(bytes);
String message = new String(bytes);
System.out.format("received %s at %s from %s%n", message, dc.getLocalAddress(), sa);
// If any received data contains the message from sendDatagram then throw exception
if (message.contains(GREETINGS_MESSAGE)) {
throw new RuntimeException("Unexpected datagram received");
}
}
boolean mayRetry = retryCount < MAX_TRIES;
if (mayRetry) {
return false; // will retry
}
// BindException will be thrown if another service is using
// our expected refuser port, cannot run just exit.
DatagramChannel.open().bind(refuser).close();
throw new RuntimeException("PortUnreachableException not raised");
} catch (PortUnreachableException pue) {
System.out.println("Got expected PortUnreachableException " + pue);
}
}
return true; // test passed
}
/*
* Send a datagram to a disconnected UDP end point
* This should result in an PortUnreachableException
* Handle unexpected read events on the senders DC with
* retry when message received is external and Throw Exception
* on receipt of own message
*/
static boolean testNoPUEAfterDisconnect(DatagramChannel dc,
SocketAddress refuser,
Selector sel,
int retryCount) throws IOException {
sendDatagram(dc, refuser);
int n = sel.select(2000);
if (n > 0) {
boolean mayRetry = checkUnexpectedWakeup(sel.selectedKeys());
boolean tooManyRetries = retryCount >= MAX_TRIES;
sel.selectedKeys().clear();
if (mayRetry && !tooManyRetries) {
return false; // will retry
}
throw new RuntimeException("Unexpected wakeup after disconnect");
}
return true; // test passed
}
static void sendDatagram(DatagramChannel dc, SocketAddress remote)
throws IOException
{
ByteBuffer bb = ByteBuffer.wrap("Greetings!".getBytes());
throws IOException {
ByteBuffer bb = ByteBuffer.wrap(GREETINGS_MESSAGE.getBytes());
dc.send(bb, remote);
}
/*
* Attempt to read and Log the data from SelectedKeys,
* If a message can be received, and it came from
* another test return True
*
*/
static boolean checkUnexpectedWakeup(Set<SelectionKey> selectedKeys) {
System.out.format("Received %d keys%n", selectedKeys.size());
for (SelectionKey key : selectedKeys) {
if (!key.isValid() || !key.isReadable()) {
System.out.println("Invalid or unreadable key: " + key);
continue;
}
try {
System.out.println("Attempting to read datagram from key: " + key);
DatagramChannel datagramChannel = (DatagramChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(100);
SocketAddress sa = datagramChannel.receive(buf);
if (sa != null) {
buf.flip();
byte[] bytes = new byte[buf.remaining()];
buf.get(bytes);
String message = new String(bytes);
System.out.format("received %s at %s from %s%n", message, datagramChannel.getLocalAddress(), sa);
// If any received data contains the message from sendDatagram then return false
if (message.contains(GREETINGS_MESSAGE)) {
return false;
}
}
} catch (IOException io) {
System.out.println("Unable to read from datagram " + io);
}
}
return true;
}
}