blob: 78ee3ab8de2bec8e269c64ca9137d3bc9c8856a2 [file] [log] [blame]
/*
* Copyright (C) 2017 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.net;
import org.junit.Test;
import java.io.Closeable;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.channels.ServerSocketChannel;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import libcore.util.EmptyArray;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Tests socket timeout behavior for various different socket types.
*/
public class SocketTimeoutTest {
private static final int TIMEOUT_MILLIS = 500;
private static final InetSocketAddress UNREACHABLE_ADDRESS
= new InetSocketAddress("192.0.2.0", 0); // RFC 5737
@FunctionalInterface
private interface SocketOperation<T> {
void operate(T s) throws IOException;
}
@FunctionalInterface
private interface SocketConstructor<T> {
T get() throws IOException;
}
private static <T extends Closeable> void checkOperationTimesOut(SocketConstructor<T> construct,
SocketOperation<T> op) throws Exception {
try (T socket = construct.get()) {
long startingTime = System.currentTimeMillis();
try {
op.operate(socket);
fail();
} catch (SocketTimeoutException timeoutException) {
long timeElapsed = System.currentTimeMillis() - startingTime;
assertTrue(
Math.abs(((float) timeElapsed / TIMEOUT_MILLIS) - 1)
< 0.2f); // Allow some error.
}
}
}
@Test
public void testSocketConnectTimeout() throws Exception {
// #connect(SocketAddress endpoint, int timeout)
checkOperationTimesOut(() -> new Socket(), s -> s.connect(UNREACHABLE_ADDRESS,
TIMEOUT_MILLIS));
// Setting SO_TIMEOUT should not affect connect timeout.
checkOperationTimesOut(() -> new Socket(),
s -> {
s.setSoTimeout(TIMEOUT_MILLIS / 2);
s.connect(UNREACHABLE_ADDRESS, TIMEOUT_MILLIS);
});
}
@Test
public void testSocketReadTimeout() throws Exception {
// #read()
try (ServerSocket ss = new ServerSocket(0)) {
// The server socket will accept the connection without explicitly calling accept() due
// to TCP backlog.
checkOperationTimesOut(() -> new Socket(), s -> {
s.connect(ss.getLocalSocketAddress());
s.setSoTimeout(TIMEOUT_MILLIS);
s.getInputStream().read();
});
}
}
@Test
public void testSocketWriteNeverTimeouts() throws Exception {
// #write() should block if the buffers are full, and does not drop packets or throw
// SocketTimeoutException.
try (Socket sock = new Socket();
ServerSocket serverSocket = new ServerSocket(0)) {
// Setting this option should not affect behaviour, as specified by the spec.
sock.setSoTimeout(TIMEOUT_MILLIS);
// Set SO_SNDBUF and SO_RCVBUF to minimum value allowed by kernel.
sock.setSendBufferSize(1);
serverSocket.setReceiveBufferSize(1);
int actualSize = sock.getSendBufferSize() + serverSocket.getReceiveBufferSize();
sock.connect(serverSocket.getLocalSocketAddress());
CountDownLatch threadStarted = new CountDownLatch(1);
CountDownLatch writeCompleted = new CountDownLatch(1);
Thread thread = new Thread(() -> {
threadStarted.countDown();
try {
// Should block
sock.getOutputStream().write(new byte[actualSize + 1]);
writeCompleted.countDown();
} catch (IOException ignored) {
} finally {
writeCompleted.countDown();
}
});
thread.start();
// Wait for the thread to start.
assertTrue(threadStarted.await(500, TimeUnit.MILLISECONDS));
// Wait for TIMEOUT_MILLIS + slop. If write does not complete by then, we assume it has
// blocked.
boolean blocked =
!writeCompleted.await(TIMEOUT_MILLIS * 2, TimeUnit.MILLISECONDS);
assertTrue(blocked);
// Make sure the writing thread completes after the socket is closed.
sock.close();
assertTrue(writeCompleted.await(5000, TimeUnit.MILLISECONDS));
}
}
@Test
public void testServerSocketAcceptTimeout() throws Exception {
// #accept()
checkOperationTimesOut(() -> new ServerSocket(0),
s -> {
s.setSoTimeout(TIMEOUT_MILLIS);
s.accept();
});
}
@Test
public void testServerSocketChannelAcceptTimeout() throws Exception {
// #accept()
checkOperationTimesOut(() -> ServerSocketChannel.open(),
s -> {
s.bind(null, 0);
s.socket().setSoTimeout(TIMEOUT_MILLIS);
s.socket().accept();
});
}
@Test
public void testDatagramSocketReceive() throws Exception {
checkOperationTimesOut(() -> new DatagramSocket(), s -> {
s.setSoTimeout(TIMEOUT_MILLIS);
s.receive(new DatagramPacket(EmptyArray.BYTE, 0));
});
}
// TODO(yikong), http://b/35867657:
// Add tests for SocksSocketImpl once a mock Socks server is implemented.
}