Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Client SocketInputStream.close() leads to more resource consumption?

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:

  • why is calling socketInputStream.close() on the client side harms in this case?
  • or what is wrong with the code?

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();
    }

}
like image 766
bourbert Avatar asked Aug 12 '15 13:08

bourbert


1 Answers

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:

  1. Preferable way is to continue reading from 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();
  2. The second option is to force the abortive release by setting
    clientSocket.setSoLinger(true, 0);
    In this case inputStream.close() will force client to send RST and abort the connection.

More about orderly and abortive TCP connection release.

like image 173
apangin Avatar answered Oct 10 '22 04:10

apangin