JDK-4796166 : Linger interval delays usage of released file descriptor
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.net
  • Affected Version: 1.4.2
  • Priority: P2
  • Status: Resolved
  • Resolution: Fixed
  • OS: linux_redhat_7.1
  • CPU: generic
  • Submitted: 2002-12-19
  • Updated: 2003-03-28
  • Resolved: 2003-01-14
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other Other
1.4.1_03 03Fixed 1.4.2Fixed
Description
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");
        }

    }

}


Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: 1.4.1_03 mantis-beta FIXED IN: 1.4.1_03 mantis-beta INTEGRATED IN: 1.4.1_03 mantis-b13 mantis-beta VERIFIED IN: mantis-beta
14-06-2004

SUGGESTED FIX ------- PlainSocketImpl.java ------- *** /tmp/sccs.PPaG73 Fri Dec 20 20:27:13 2002 --- PlainSocketImpl.java Fri Dec 20 20:30:31 2002 *************** *** 439,445 **** if (fd != null) { if (fdUseCount == 0) { closePending = true; ! socketClose0(false); fd = null; return; } else { --- 439,457 ---- if (fd != null) { if (fdUseCount == 0) { closePending = true; ! /* ! * We close the FileDescriptor in two-steps - first the ! * "pre-close" which closes the socket but doesn't ! * release the underlying file descriptor. This operation ! * may be lengthy due to untransmitted data and a long ! * linger interval. Once the pre-close is done we do the ! * actual socket to release the fd. ! */ ! try { ! socketPreClose(); ! } finally { ! socketClose(); ! } fd = null; return; } else { *************** *** 452,458 **** if (!closePending) { closePending = true; fdUseCount--; ! socketClose0(true); } } } --- 464,470 ---- if (!closePending) { closePending = true; fdUseCount--; ! socketPreClose(); } } } *************** *** 526,532 **** if (fdUseCount == -1) { if (fd != null) { try { ! socketClose0(false); } catch (IOException e) { } finally { fd = null; --- 538,544 ---- if (fdUseCount == -1) { if (fd != null) { try { ! socketClose(); } catch (IOException e) { } finally { fd = null; *************** *** 587,592 **** --- 599,619 ---- return timeout; } + /* + * "Pre-close" a socket by dup'ing the file descriptor - this enables + * the socket to be closed without releasing the file descriptor. + */ + private void socketPreClose() throws IOException { + socketClose0(true); + } + + /* + * Close the socket (and release the file descriptor). + */ + private void socketClose() throws IOException { + socketClose0(false); + } + private native void socketCreate(boolean isServer) throws IOException; private native void socketConnect(InetAddress address, int port, int timeout) throws IOException; ###@###.### 2002-12-20
20-12-2002

EVALUATION The bug arises under the following conditions :- - no thread blocked in an I/O - untransmitted data on the socket - network congestion or congestion at peer application - lengthy linger interval - socket is closed Under these conditions the close (on Linux only) releases the fd but doesn't return until the linger interval expires. In that time the fd can be used by another socket but I/O on that fd will be blocked by the preemptive close mechanism until the previous close completes. I've updated the bug with the fix. ###@###.### 2002-12-19
19-12-2002