Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,11 @@ public Selector(long connectionMaxIdleMS, int failedAuthenticationDelayMs, Metri
public void connect(String id, InetSocketAddress address, int sendBufferSize, int receiveBufferSize) throws IOException {
ensureNotRegistered(id);
SocketChannel socketChannel = SocketChannel.open();
SelectionKey key = null;
try {
configureSocketChannel(socketChannel, sendBufferSize, receiveBufferSize);
boolean connected = doConnect(socketChannel, address);
SelectionKey key = registerChannel(id, socketChannel, SelectionKey.OP_CONNECT);
key = registerChannel(id, socketChannel, SelectionKey.OP_CONNECT);

if (connected) {
// OP_CONNECT won't trigger for immediately connected channels
Expand All @@ -262,6 +263,9 @@ public void connect(String id, InetSocketAddress address, int sendBufferSize, in
key.interestOps(0);
}
} catch (IOException | RuntimeException e) {
if (key != null)
immediatelyConnectedKeys.remove(key);
channels.remove(id);
socketChannel.close();
throw e;
}
Expand Down Expand Up @@ -316,7 +320,7 @@ private void ensureNotRegistered(String id) {
throw new IllegalStateException("There is already a connection for id " + id + " that is still being closed");
}

private SelectionKey registerChannel(String id, SocketChannel socketChannel, int interestedOps) throws IOException {
protected SelectionKey registerChannel(String id, SocketChannel socketChannel, int interestedOps) throws IOException {
SelectionKey key = socketChannel.register(nioSelector, interestedOps);
KafkaChannel channel = buildAndAttachKafkaChannel(socketChannel, id, key);
this.channels.put(id, channel);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import java.util.Random;
import java.util.Set;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
Expand Down Expand Up @@ -380,16 +381,7 @@ public void testIdleExpiryWithoutReadyKeys() throws IOException {
@Test
public void testImmediatelyConnectedCleaned() throws Exception {
Metrics metrics = new Metrics(); // new metrics object to avoid metric registration conflicts
Selector selector = new Selector(5000, metrics, time, "MetricGroup", channelBuilder, new LogContext()) {
@Override
protected boolean doConnect(SocketChannel channel, InetSocketAddress address) throws IOException {
// Use a blocking connect to trigger the immediately connected path
channel.configureBlocking(true);
boolean connected = super.doConnect(channel, address);
channel.configureBlocking(false);
return connected;
}
};
Selector selector = new ImmediatelyConnectingSelector(5000, metrics, time, "MetricGroup", channelBuilder, new LogContext());

try {
testImmediatelyConnectedCleaned(selector, true);
Expand All @@ -400,6 +392,26 @@ protected boolean doConnect(SocketChannel channel, InetSocketAddress address) th
}
}

private static class ImmediatelyConnectingSelector extends Selector {
public ImmediatelyConnectingSelector(long connectionMaxIdleMS,
Metrics metrics,
Time time,
String metricGrpPrefix,
ChannelBuilder channelBuilder,
LogContext logContext) {
super(connectionMaxIdleMS, metrics, time, metricGrpPrefix, channelBuilder, logContext);
}

@Override
protected boolean doConnect(SocketChannel channel, InetSocketAddress address) throws IOException {
// Use a blocking connect to trigger the immediately connected path
channel.configureBlocking(true);
boolean connected = super.doConnect(channel, address);
channel.configureBlocking(false);
return connected;
}
}

private void testImmediatelyConnectedCleaned(Selector selector, boolean closeAfterFirstPoll) throws Exception {
String id = "0";
selector.connect(id, new InetSocketAddress("localhost", server.port), BUFFER_SIZE, BUFFER_SIZE);
Expand All @@ -412,6 +424,46 @@ private void testImmediatelyConnectedCleaned(Selector selector, boolean closeAft
verifySelectorEmpty(selector);
}

/**
* Verify that if Selector#connect fails and throws an Exception, all related objects
* are cleared immediately before the exception is propagated.
*/
@Test
public void testConnectException() throws Exception {
Metrics metrics = new Metrics();
AtomicBoolean throwIOException = new AtomicBoolean();
Selector selector = new ImmediatelyConnectingSelector(5000, metrics, time, "MetricGroup", channelBuilder, new LogContext()) {
@Override
protected SelectionKey registerChannel(String id, SocketChannel socketChannel, int interestedOps) throws IOException {
SelectionKey key = super.registerChannel(id, socketChannel, interestedOps);
key.cancel();
if (throwIOException.get())
throw new IOException("Test exception");
return key;
}
};

try {
verifyImmediatelyConnectedException(selector, "0");
throwIOException.set(true);
verifyImmediatelyConnectedException(selector, "1");
} finally {
selector.close();
metrics.close();
}
}

private void verifyImmediatelyConnectedException(Selector selector, String id) throws Exception {
try {
selector.connect(id, new InetSocketAddress("localhost", server.port), BUFFER_SIZE, BUFFER_SIZE);
fail("Expected exception not thrown");
} catch (Exception e) {
verifyEmptyImmediatelyConnectedKeys(selector);
assertNull("Channel not removed", selector.channel(id));
ensureEmptySelectorFields(selector);
}
}

@Test
public void testCloseOldestConnectionWithOneStagedReceive() throws Exception {
verifyCloseOldestConnectionWithStagedReceives(1);
Expand Down Expand Up @@ -715,6 +767,10 @@ private void verifySelectorEmpty(Selector selector) throws Exception {
}
selector.poll(0);
selector.poll(0); // Poll a second time to clear everything
ensureEmptySelectorFields(selector);
}

private void ensureEmptySelectorFields(Selector selector) throws Exception {
for (Field field : Selector.class.getDeclaredFields()) {
ensureEmptySelectorField(selector, field);
}
Expand Down