peco
peco

Reputation: 1451

Socket connect timeout ceiling

I'm working with Java Sockets. I have this line of code:

Socket webSocket = new Socket();
webSocket.connect(new InetSocketAddress(domain, 80), 120000);

The specified timeout is 120000ms (2 minutes). I'm curious whether this timeout is actually honored, or whether it is capped at the platform default connection timeout value. Also, how do I check the platform's default connection timeout value? In other words, what is the timeout when I call this code:

Socket webSocket = new Socket(domain, 80);

In the first place, is this timeout platform dependent? I know there's SO_TIMEOUT, but I think it only affects the read() timeout and not connect().

I think there's a default timeout, because not specifying the timeout value still causes Connection timed out: connect.

Upvotes: 0

Views: 2984

Answers (2)

initramfs
initramfs

Reputation: 8415

Investigating further, you'll find that there is a subtle difference between a java connection time out and a system-level connection time out.

By changing the line:

webSocket.connect(new InetSocketAddress(domain, 80), 120000);

to something like:

webSocket.connect(new InetSocketAddress(domain, 80), 10);

You'll see you get a whole different exception: java.net.SocketTimeoutException rather than the usual java.net.ConnectException.

The actual call to Socket.connect() is actually being deferred to a SocketImpl abstract class such that the exact mechanics are implementation-defined. As seen in this extract from the connect() method in Socket:

...
if (!oldImpl)
    impl.connect(epoint, timeout);
else if (timeout == 0) {
    if (epoint.isUnresolved())
        impl.connect(addr.getHostName(), port);
    else
        impl.connect(addr, port);
...

Fundamentally, the second exception (ConnectException) is a implementation-dependent exception meaning that the OS itself has a maximum timeout that is being reached before the java one is set. Exploring further, upon inspection of the stack trace of the ConnectException we see something like this (on a windows machine):

Exception in thread "main" java.net.ConnectException: Connection timed out: connect
    at java.net.DualStackPlainSocketImpl.connect0(Native Method)
    at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
    at java.net.Socket.connect(Socket.java:589)
    at java.net.Socket.connect(Socket.java:538)

Note the exception actually propagated from a native method, namely from this section (as provided by OpenJDK):

rv = connect(fd, (struct sockaddr *)&sa, sa_len);
if (rv == SOCKET_ERROR) {
    int err = WSAGetLastError();
    if (err == WSAEWOULDBLOCK) {
        return java_net_DualStackPlainSocketImpl_WOULDBLOCK;
    } else if (err == WSAEADDRNOTAVAIL) {
        JNU_ThrowByName(env, JNU_JAVANETPKG "ConnectException",
            "connect: Address is invalid on local machine, or port is not valid on remote machine");
    } else {
        NET_ThrowNew(env, err, "connect");
    }
    return -1;  // return value not important.
}

The line:

rv = connect(fd, (struct sockaddr *)&sa, sa_len);

Is actually a call to the Winsock2 connect function whose MSDN page can be viewed here. The Winsock2 socket itself has been setup as a default socket (optval in setsockopt was zero). Meaning it inherits whatever system default timeouts are for both sending and receiving. If a connection attempt exceeds the default timeout value, a SOCKET_ERROR is placed into rv which causes the line:

NET_ThrowNew(env, err, "connect");

to run, propagating the ConnectException back up the stack. (If you don't believe me check out DualStackPlainSocketImpl.c in the OpenJDK sources).

Well... Where did the java-set timeout go? The answer turns out to be one stack-level up before the transition into the native layer in the file DualStackPlainSocketImpl.java under the method socketConnect(), in particular this fragment:

configureBlocking(nativefd, false);
try {
    connectResult = connect0(nativefd, address, port);
    if (connectResult == WOULDBLOCK) {
        waitForConnect(nativefd, timeout);
    }
} finally {
    configureBlocking(nativefd, true);
}

where connect0 is the actual call to the native function.

In essence, when java is presented with a finite, non-zero timeout, it starts the native socket connection in asynchronous mode and waits for the timeout to expire or the native method to complete, whichever occurs first. The problem with setting a large timeout value is that the native connect function may itself timeout and throw a ConnectException instead of the usual SocketTimeoutException. (On my particular Windows 10 system under Java 8).

The same exercise can be done on various systems to determine the exact mechanism in which socket timeouts work.


tl;dr:

The consensus is that basically all socket operations are implementation defined. Even from the first stack layer you'll find Socket defers most of it's work to some abstract SocketImpl that gets implemented by the OS. As such, it is entirely possible (as proven above) for a OS to have a timeout that is longer, shorter or completely non-existent. In a sense, a java timeout would only work properly if it were shorter than the OS default timeout (if it has one) so you should be prepared to handle both cases (where the OS throws an exception due to it's native timeout vs a java-specific timeout expiry).

Upvotes: 6

Achintha Gunasekara
Achintha Gunasekara

Reputation: 1165

The default socket timeout is 0, which means never timeout. If it actually time out after x number of minutes, This means it has been set within your code. In your case it's 2 minutes.

Upvotes: -1

Related Questions