JDK-4197666 : Socket descriptors leak into processes spawned by Java programs on windows
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 1.1,1.1.7,1.2.0,1.2.2
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_nt
  • CPU: x86
  • Submitted: 1998-12-15
  • Updated: 1999-11-22
  • Resolved: 1999-05-24
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 Other Other
1.1.7 b04Fixed 1.1.8_001Fixed 1.2.1_002Fixed 1.3.0Fixed
Related Reports
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Relates :  
Description
On windows NT if you open a server socket and then spawn a sub-process
the child inherits the the socket, this will of course keep another
process from using the port associated with the socket.

This problem is compounded by a windows "feature" where if the parent
process goes away and a child who has inherited a socket descriptor is
still around clients who try to connect to the parent will succeeded,
(if they try to read they will block until the child dies and
any writes go to the big bit bucket in the sky)

I have included simple Java programs that demonstrate this bug. Run
TCPServer with something like
	java TCPServer open exec "notepad" exit

Then try connecting to it with TCPClient
	java TCPClient <host TCPServer was on> <port> <string of text>

If notepad is still around the client will run normally, if notepad is killed the client will generate an exception

You can use the sleep argument with TCPServer to get to hang around before
exiting, this allows you to make sure TCPClient can connect succefully
when TCPServer is still running.

Sample code:

import java.net.*;
import java.io.*;

public class TCPServer {
    static private String svrName;

    static private class AcceptThread extends Thread {
	private ServerSocket	sSock;
	
	AcceptThread(ServerSocket sSock) {
	    this.sSock = sSock;
	}

	public void run() {
	    try {
		while (true) {
		    final Socket sock = sSock.accept();
		    (new ReadThread(sock)).start();
		}
	    } catch (IOException e) {
		System.err.println(svrName + 
		    ":IOException opening server socket");
		e.printStackTrace();
	    }
	}
    }

    static private class ReadThread extends Thread {
	private BufferedReader rdr;
	
	ReadThread(Socket sock) throws IOException {
	    rdr = new BufferedReader(
	        new InputStreamReader(sock.getInputStream()));
	}

	public void run() {
	    try {
		while (true) {
		    final String str = rdr.readLine();
		    if (str == null) {
			System.out.println(svrName + 
					   ":EOF recived, thread exiting"); 
			return;
		    } else if (str.equals("bye")) {
			System.out.println(svrName + ":Recived bye, exiting");
			System.exit(0);
		    } else {
			System.out.println(svrName + ":" + str);
		    }
		}
	    } catch (IOException e) {
		System.err.println(svrName + ":IOException in read thread");
		e.printStackTrace();
	    }
	}
    }

    /** Utility class to forward bytes from one stream to another */
    static private class Dumper extends Thread {
	private final InputStream in;
	private final OutputStream out;
	private final boolean flush;

	/**
	 * Create a new <code>Dumper</code>
	 * @param in The stream to take bytes from
	 * @param out The stream to dump the bytes to
	 * @param flust If true flush out after each write.
	 */
	Dumper(InputStream in, OutputStream out, String name) {
	    super(name);
	    this.in = in;
	    this.out = out;
	    this.flush = true;
	}

	/**
	 * Read a bunch of bytes from the associated
 	 * <code>InputStream</code> and write them to the associated
	 * <code>OutputStream<code>, repeat until the read returns
	 * EOF, or until there is an <code>IOException</code>
	 */
	public void run() {
	    byte[] buf = new byte[4096];
	    try {
		while (true) {
		    final int bytesRead = in.read(buf);
		    if (bytesRead > 0) {
			out.write(buf, 0, bytesRead);
			if (flush) out.flush();
		    } else if (bytesRead < 0)
			break;
		}
	    } catch (IOException e) {
		System.err.println(svrName + ":IOException in dump thread");
		e.printStackTrace();
	    } finally {
		try {
		    in.close();
		} catch (IOException e) {
		    // Just trying to be nice
		}
	    }
	}
    }

    public static void main(String[] args) throws IOException {
	svrName = args[0];

	for (int i=1; i<args.length; i++) {
	    final String arg = args[i];
	    
	    if (arg.equals("open")) {
		final ServerSocket sSock = new ServerSocket(0);
		(new AcceptThread(sSock)).start();
		System.out.println(svrName + 
				   ":Opened Accept Socket on port " + 
				   sSock.getLocalPort());
	    } else if (arg.equals("exec")) {
		final String execStr = args[++i];
		final Runtime rt = Runtime.getRuntime();
		final Process proc = rt.exec(execStr);

		Thread outThread = 
		    new Dumper(proc.getInputStream(), System.out, 
			       "Out Forwarding Thread");

		Thread errThread = 
		    new Dumper(proc.getErrorStream(), System.err, 
			       "Err Forwarding Thread");
	
		outThread.start();
		errThread.start();

		System.out.println(svrName + ":Execed:" + execStr);
	    } else if (arg.equals("sleep")) {
		final String sleepTimeStr = args[++i];
		final long sleepTime = Long.parseLong(sleepTimeStr);
		try {
		    System.out.print(svrName + ":Sleeping for " + sleepTime + 
				     "milliseconds...");
		    System.out.flush();
		    Thread.sleep(sleepTime);
		    System.out.println("awake"); 
		} catch (InterruptedException e) {
		    break;
		}
	    } else if (arg.equals("exit")) {
		System.out.println(svrName + ":Exiting...");
		System.exit(0);
	    }
	}
    }
}



import java.net.*;
import java.io.*;

public class TCPClient {
    public static void main(String[] args) throws IOException {	
	final int port = Integer.parseInt(args[1]);
	final Socket sock = new Socket(args[0], port);

	final PrintWriter out = new PrintWriter(sock.getOutputStream(), true);

	out.println(args[2]);
    }
}

---------------------------------------------------------------------------------------------------
Another of our licensees has also reported this problem :

There are two aspects to this - inheritance for plain sockets, and for
datagram sockets.  There is a test case for each of these and the fix is
similar for both of them.
Both the fixes are similar, and are in the same file.  I have included the
diffs below for the 1.1.7 level code

>>Plain Sockets

Test Case

 TCPClient.java TCPServer.java

Two files TCPServer and TCPClient.  These come from Sunbug 4197666 which is
also for plain socket inheritance (not datagram socket inheritance).  To
run the test case - you can use one machine
In a server "window" - run
     java TCPServer localHost open exec "notepad" exit.
This will start the server, run the notepad and exit the server.

Run the client
     java TCPClient <machine ip address> <port of server> sometextgoeshere

The client will run and exit, even though the server has exited!  Try
running the whole server and client again, but BEFORE running the client,
close notepad.  The client will be unable to connect to the server.  The
fix below will cure the problem

(See TCPClient.java and TCPServer.java above)


>>Datagram Sockets

Test Case - DatagramClient Datagramserver

Start the server and then start the client.  No command line parameters
required - uses the local host machine.  About 10 communications will flow
between the client and sever.  On communication  5, notepad will be
started.  The problem will manifest it self in that a second server can not
be created if notepad is still active.  Ignore the fact that the client
hangs - as this is udp it is waiting for a response that will never come!
The important part is that the server can be restarted.  Closing notepad
will allow a new server to start - or implementing the fix below!

(See DatagramClient.java and DatagramServer.java below)


------------------------------------------------------------------------------

import java.net.*;

/**
 * This program sends a datagram to the server every 2 seconds and waits
 * for a reply. If the datagram gets lost, this program will hang since it
 * has no retry logic.
 */

public class DatagramClient extends Object
{
    public static void main(String[] args)
    {
        try
        {
// Create the socket for sending
            DatagramSocket mysock = new DatagramSocket();


// Create the send buffer
            byte[] buf = new byte[1024];

// Create a packet to send. Currently just tries to send to the local host.
// Change the inet address to make it send somewhere else.

            DatagramPacket p = new DatagramPacket(buf,
                                                  buf.length, InetAddress.getLocalHost(), 5432);
            while (true)
            {
// Send the datagram
                mysock.send(p);
                System.out.println("Client sent datagram!");
// Wait for a reply
                mysock.receive(p);
                System.out.println("Client received datagram!");
                Thread.sleep(2000);
            }
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

import java.net.*;

/**
 * This is a simple datagram echo server that receives datagrams
 * and echoes them back untouched.
 */

public class DatagramServer extends Object
{
    public static void main(String[] args)
    {
        int counter=1;

        try
        {
            System.out.println("Starting datagram server");
// Create the datagram socket with a specific port number
            DatagramSocket mysock = new DatagramSocket(5432);

// Allow packets up to 1024 bytes long
            byte[] buf = new byte[1024];

// Create the packet for receiving datagrams
            DatagramPacket p = new DatagramPacket(buf,
                                                  buf.length);
            while (true)
            {
// Read in the datagram
                mysock.receive(p);

                System.out.println("Received datagram :"+counter);
                counter++;




// A nice feature of datagram packets is that there is only one
// address field. The incoming address and outgoing address are
// really the same address.  This means that when you receive
// a datagram, if you want to send it back to the originating
// address, you can just invoke send again.

                mysock.send(p);

                // needs to be after the resend back to the client
                // otherwise the client will just wait for a message that is never
                // coming - and that is not hte point of this exercise
                if (counter==5)
                {
                   // start an external process
                   Runtime.getRuntime().exec("notepad");
                   System.out.println("Started external process");
                } else if (counter == 10)
                {
                    System.out.println("Server exiting");
                    System.exit(0);
                }

            }
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

mick.fleming@Ireland 1999-04-09

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: generic kestrel kestrel-beta FIXED IN: 1.1.7b_004 1.1.8_001 1.2.1-002 kestrel kestrel-beta INTEGRATED IN: 1.1.7b_004 1.1.8_001 1.2.1-002 kestrel kestrel-beta
14-06-2004

EVALUATION Current processes do not permit this change for 1.2.1. Committ to kestrel to move it out of the 1.2.1 radar. anand.palaniswamy@Eng 1999-03-15 Due to a current IBM escalation (Top 10 bugs) Java Platform CTE will release official patches with this fix on JDK1.1.7B, 1.1.8, 1.2.1 and 1.2.2 (since we missed the cricket code freeze). patrick.ong@Eng 1999-05-10 ----------------------------------------------------------------------- A licensee sent a simpler testcase : # # The Test case for the Socket Inheritence problem. We have a server # SimpleServer and a client SimpleClient. # # The server and client communicate 10 times, simple bytes being sent across # the network. After 5 communications the Server will start the child class # file - you could change this to the notepad. The child just waits for a # few minutes. The Client and Server exit properly after the communication. # To demonstrate the accept socket fix - (assuming the parital socket # inheritence fix is intact), Restart the server (with no fixes in this won't # start properly) it will restart. Without the accept socket fix, note that # the client cannot be succesfully restarted - a reconnection exception will # occur. # # To Start the server java SimpleServer # To start the client java SimpleClient mbwhite <---- where mbwhite is the # machine of the server # ================== SimpleServer.java ================= import java.io.*; import java.net.*; public class SimpleServer { ServerSocket serverSock; Socket clientConn; public static void main(String args[]) { new SimpleServer(); } public SimpleServer() { try{ serverSock = new ServerSocket(2222); System.out.println("<s>ServerSocket created -waiting!"); clientConn = serverSock.accept(); System.out.println("<s>Got connection"); DataInputStream input = new DataInputStream(new BufferedInputStream( clientConn.getInputStream())); DataOutputStream output = new DataOutputStream(new BufferedOutputStream(clientConn.getOutputStream())); // send hello to the client output.writeInt(1101); output.flush(); System.out.println("<s>Msg sent to client"); int i; System.out.println("<s>About to wait for client"); while ( (i=input.readInt() )!=-1 ) { System.out.println("<s>From Client >> "+i); //int i = Integer.parseInt( nextLine.substring(nextLine.lastIndexOf(':')+1) ) ; if (i==5) { System.out.println("<s> creating process"); //Runtime.getRuntime().exec("notepad"); Runtime.getRuntime().exec("cmd /c start java child 10"); } else if (i==10) { input.close(); output.close(); clientConn.close(); System.exit(1); } output.writeInt(1101); output.flush(); System.out.println("<s>About to wait for client"); } output.close(); input.close(); clientConn.close(); } catch (IOException e) { System.out.println(e.toString()); System.exit(1); } catch (Exception e) { System.out.println(e.toString()); System.exit(1); } } } ================= SimpleClient.java ================= import java.io.*; import java.net.*; public class SimpleClient { String serverName; public static void main(String args[]) { new SimpleClient(args[0]); } public SimpleClient(String name) { // set the server host name serverName = name; int counter = 0; int responce; try{ Socket chatSocket = new Socket(serverName,2222); DataInputStream input = new DataInputStream(new BufferedInputStream( chatSocket.getInputStream())); DataOutputStream output = new DataOutputStream(new BufferedOutputStream(chatSocket.getOutputStream())); System.out.println("<c>Socket created ok"); // get message from sevrer System.out.println("<c>About to wait for server"); while ( (responce = input.readInt()) != 0) { System.out.println("<c>From server>"+responce); output.writeInt(counter); output.flush(); System.out.println("<c>Sent to server"); Thread.sleep(2000); counter+=1; System.out.println("<c>About to wait for server"); } } catch (IOException e){ System.out.println("IOException "+e.toString()); System.exit(1); } catch (Exception e){ System.out.println(e.toString()); System.exit(1); } } } -------------------------------- mick.fleming@Ireland 1999-05-21
21-05-1999

SUGGESTED FIX update suggested fix in 1.1.7 with one more SetHandleInformation in java_net_PlainSocketImpl_socketAccept (src/win32/net/socket.c) ------- socket.c ------- *** /tmp/dhqPhv_ Fri May 21 13:32:37 1999 --- socket.c Fri May 21 13:30:28 1999 *************** *** 234,239 **** --- 234,240 ---- raiseSocketException(WSAGetLastError(), "create"); return; } + SetHandleInformation((HANDLE)fd, HANDLE_FLAG_INHERIT, FALSE); fdptr->fd = fd + 1; } *************** *** 419,424 **** --- 420,426 ---- } } fd = accept(infd, (struct sockaddr *)&him, &len); + SetHandleInformation((HANDLE)fd, HANDLE_FLAG_INHERIT, FALSE); if (fd < 0) { if (fdptr->fd < 0) { NET_ERROR(0, JAVANETPKG "SocketException", "socket was closed"); *************** *** 891,896 **** --- 893,899 ---- BOOL t = TRUE; setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char*)&t, sizeof(BOOL)); } + SetHandleInformation((HANDLE)fd, HANDLE_FLAG_INHERIT, FALSE); fdptr->fd = fd + 1; } tao.ma@Eng 1999-05-21
21-05-1999