We have a bug in Linux close mechanism whereby a lengthy linger interval
will block re-usage of a released file descriptor until the linger
interval expires.
The issue is that Linux's close call releases the file descriptor before
the linger interval expires. This allows the file descriptor to be re-used
by another newly created Socket but I/O on the newly created Socket will
be blocked until the linger interval for the previous usage expires. The
issue is specific to java.net.Socket.
The following test case demonstrates the issue (sorry I didn't have time
to clean it up - Alan) :-
import java.net.*;
import java.io.*;
public class Test {
static class Sender implements Runnable {
Socket s;
public Sender(Socket s) {
this.s = s;
}
public void run() {
try {
s.getOutputStream().write(new byte[128*1024]);
} catch (IOException ioe) {
}
}
}
static class Closer implements Runnable {
Socket s;
public Closer(Socket s) {
this.s = s;
}
public void run() {
try {
s.close();
} catch (IOException ioe) {
}
}
}
static class Another implements Runnable {
int port;
long delay;
boolean connected = false;
public Another(int port, long delay) {
this.port = port;
this.delay = delay;
}
public void run() {
try {
Thread.currentThread().sleep(delay);
Socket s = new Socket("localhost", port);
synchronized (this) {
connected = true;
}
s.close();
} catch (Exception ioe) {
ioe.printStackTrace();
}
}
public synchronized boolean connected() {
return connected;
}
}
public static void main(String args[]) throws Exception {
ServerSocket ss = new ServerSocket(0);
Socket s1 = new Socket("localhost", ss.getLocalPort());
Socket s2 = ss.accept();
// setup conditions for untransmitted data and lengthy
// linger interval
s1.setSendBufferSize(128*1024);
s1.setSoLinger(true, 30);
s2.setReceiveBufferSize(1*1024);
// start sender
Thread thr = new Thread(new Sender(s1));
thr.start();
// another thread that will connect after 5 seconds.
Another another = new Another(ss.getLocalPort(), 5000);
thr = new Thread(another);
thr.start();
// give sender time to queue the data
Thread.currentThread().sleep(1000);
// close the socket asynchronously
(new Thread(new Closer(s1))).start();
// give another time to run
Thread.currentThread().sleep(10000);
// check that another is done
if (!another.connected()) {
throw new RuntimeException("Another thread is blocked");
}
}
}