If I execute the JUnit test below WITHOUT the line "inputStream.close()" (see below), more than 60000 requests can be processed (I killed the process then). WITH this line, I did not manage making more than 15000 requests, because of:
java.net.SocketException: No buffer space available (maximum connections reached?): connect
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:351)
at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:213)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:200)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366)
at java.net.Socket.connect(Socket.java:529)
at java.net.Socket.connect(Socket.java:478)
at java.net.Socket.<init>(Socket.java:375)
at java.net.Socket.<init>(Socket.java:189)
at SocketTest.callServer(SocketTest.java:60)
at SocketTest.testResourceConsumption(SocketTest.java:52)
I run it on Windows, before starting the test I wait for the netstat list to be back to normal.
Questions:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import junit.framework.TestCase;
public class SocketTest extends TestCase {
private static final int PORT = 12345;
private ServerSocket serverSocket;
public void setUp() throws Exception {
serverSocket = new ServerSocket(PORT);
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
try {
final Socket socket = serverSocket.accept();
new Thread(new Runnable() {
@Override
public void run() {
try {
OutputStream outputStream = socket.getOutputStream();
for(int i = 0; i < 100; i++) {
outputStream.write(i);
}
outputStream.close();
// in fact the previous line calls this already:
// socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}).start();
}
public void testResourceConsumption() throws Exception {
for (int i=0; i<1000000; i++) {
callServer();
if (i % 1000 == 0) {
System.out.println(i);
}
}
}
private void callServer() throws Exception {
Socket clientSocket = new Socket("localhost", PORT);
InputStream inputStream = clientSocket.getInputStream();
for (int i = 0; i < 100; i++) {
assertEquals(i, inputStream.read());
}
///////////////// THIS LINE IS INTERESTING
inputStream.close();
// in fact the previous line calls this already:
// clientSocket.close();
}
public void tearDown() throws Exception {
serverSocket.close();
}
}
When you explicitly call inputStream.close()
you change the order of TCP graceful connection release. In this case the client side of the connection is closed prior to receiving FIN packet from the server, thus leaving the socket in TIME_WAIT state. At some point all local ports for outgoing connections become occupied with these TIME_WAIT sockets, and no more outgoing connections can be made.
When you don't call inputStream.close()
, the connections are shut down by the server side with outputStream.close()
call. The client sockets have enough time to receive FIN from the server, and then at garbage collection time they get gracefully closed by the finalizer method.
There are two options to fix the procedure in your test:
inputStream
until you receive -1, which means that the other side has initiated the connection shutdown (i.e. FIN is received). Just insert assertEquals(-1, inputStream.read());
before inputStream.close();
clientSocket.setSoLinger(true, 0);
inputStream.close()
will force client to send RST and abort the connection.More about orderly and abortive TCP connection release.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With