jdk/src/java.rmi/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java
changeset 25859 3317bb8137f4
parent 23333 b0af2c7c8c91
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.rmi/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java	Sun Aug 17 15:54:13 2014 +0100
@@ -0,0 +1,786 @@
+/*
+ * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.rmi.transport.tcp;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.rmi.ConnectIOException;
+import java.rmi.RemoteException;
+import java.rmi.server.RMIClientSocketFactory;
+import java.rmi.server.RMIServerSocketFactory;
+import java.rmi.server.RMISocketFactory;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import sun.rmi.runtime.Log;
+import sun.rmi.runtime.NewThreadAction;
+import sun.rmi.transport.Channel;
+import sun.rmi.transport.Endpoint;
+import sun.rmi.transport.Target;
+import sun.rmi.transport.Transport;
+
+/**
+ * TCPEndpoint represents some communication endpoint for an address
+ * space (VM).
+ *
+ * @author Ann Wollrath
+ */
+public class TCPEndpoint implements Endpoint {
+    /** IP address or host name */
+    private String host;
+    /** port number */
+    private int port;
+    /** custom client socket factory (null if not custom factory) */
+    private final RMIClientSocketFactory csf;
+    /** custom server socket factory (null if not custom factory) */
+    private final RMIServerSocketFactory ssf;
+
+    /** if local, the port number to listen on */
+    private int listenPort = -1;
+    /** if local, the transport object associated with this endpoint */
+    private TCPTransport transport = null;
+
+    /** the local host name */
+    private static String localHost;
+    /** true if real local host name is known yet */
+    private static boolean localHostKnown;
+
+    // this should be a *private* method since it is privileged
+    private static int getInt(String name, int def) {
+        return AccessController.doPrivileged(
+                (PrivilegedAction<Integer>) () -> Integer.getInteger(name, def));
+    }
+
+    // this should be a *private* method since it is privileged
+    private static boolean getBoolean(String name) {
+        return AccessController.doPrivileged(
+                (PrivilegedAction<Boolean>) () -> Boolean.getBoolean(name));
+    }
+
+    /**
+     * Returns the value of the java.rmi.server.hostname property.
+     */
+    private static String getHostnameProperty() {
+        return AccessController.doPrivileged(
+            (PrivilegedAction<String>) () -> System.getProperty("java.rmi.server.hostname"));
+    }
+
+    /**
+     * Find host name of local machine.  Property "java.rmi.server.hostname"
+     * is used if set, so server administrator can compensate for the possible
+     * inablility to get fully qualified host name from VM.
+     */
+    static {
+        localHostKnown = true;
+        localHost = getHostnameProperty();
+
+        // could try querying CGI program here?
+        if (localHost == null) {
+            try {
+                InetAddress localAddr = InetAddress.getLocalHost();
+                byte[] raw = localAddr.getAddress();
+                if ((raw[0] == 127) &&
+                    (raw[1] ==   0) &&
+                    (raw[2] ==   0) &&
+                    (raw[3] ==   1)) {
+                    localHostKnown = false;
+                }
+
+                /* if the user wishes to use a fully qualified domain
+                 * name then attempt to find one.
+                 */
+                if (getBoolean("java.rmi.server.useLocalHostName")) {
+                    localHost = FQDN.attemptFQDN(localAddr);
+                } else {
+                    /* default to using ip addresses, names will
+                     * work across seperate domains.
+                     */
+                    localHost = localAddr.getHostAddress();
+                }
+            } catch (Exception e) {
+                localHostKnown = false;
+                localHost = null;
+            }
+        }
+
+        if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
+            TCPTransport.tcpLog.log(Log.BRIEF,
+                "localHostKnown = " + localHostKnown +
+                ", localHost = " + localHost);
+        }
+    }
+
+    /** maps an endpoint key containing custom socket factories to
+     * their own unique endpoint */
+    // TBD: should this be a weak hash table?
+    private static final
+        Map<TCPEndpoint,LinkedList<TCPEndpoint>> localEndpoints =
+        new HashMap<>();
+
+    /**
+     * Create an endpoint for a specified host and port.
+     * This should not be used by external classes to create endpoints
+     * for servers in this VM; use getLocalEndpoint instead.
+     */
+    public TCPEndpoint(String host, int port) {
+        this(host, port, null, null);
+    }
+
+    /**
+     * Create a custom socket factory endpoint for a specified host and port.
+     * This should not be used by external classes to create endpoints
+     * for servers in this VM; use getLocalEndpoint instead.
+     */
+    public TCPEndpoint(String host, int port, RMIClientSocketFactory csf,
+                       RMIServerSocketFactory ssf)
+    {
+        if (host == null)
+            host = "";
+        this.host = host;
+        this.port = port;
+        this.csf = csf;
+        this.ssf = ssf;
+    }
+
+    /**
+     * Get an endpoint for the local address space on specified port.
+     * If port number is 0, it returns shared default endpoint object
+     * whose host name and port may or may not have been determined.
+     */
+    public static TCPEndpoint getLocalEndpoint(int port) {
+        return getLocalEndpoint(port, null, null);
+    }
+
+    public static TCPEndpoint getLocalEndpoint(int port,
+                                               RMIClientSocketFactory csf,
+                                               RMIServerSocketFactory ssf)
+    {
+        /*
+         * Find mapping for an endpoint key to the list of local unique
+         * endpoints for this client/server socket factory pair (perhaps
+         * null) for the specific port.
+         */
+        TCPEndpoint ep = null;
+
+        synchronized (localEndpoints) {
+            TCPEndpoint endpointKey = new TCPEndpoint(null, port, csf, ssf);
+            LinkedList<TCPEndpoint> epList = localEndpoints.get(endpointKey);
+            String localHost = resampleLocalHost();
+
+            if (epList == null) {
+                /*
+                 * Create new endpoint list.
+                 */
+                ep = new TCPEndpoint(localHost, port, csf, ssf);
+                epList = new LinkedList<TCPEndpoint>();
+                epList.add(ep);
+                ep.listenPort = port;
+                ep.transport = new TCPTransport(epList);
+                localEndpoints.put(endpointKey, epList);
+
+                if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
+                    TCPTransport.tcpLog.log(Log.BRIEF,
+                        "created local endpoint for socket factory " + ssf +
+                        " on port " + port);
+                }
+            } else {
+                synchronized (epList) {
+                    ep = epList.getLast();
+                    String lastHost = ep.host;
+                    int lastPort =  ep.port;
+                    TCPTransport lastTransport = ep.transport;
+                    // assert (localHost == null ^ lastHost != null)
+                    if (localHost != null && !localHost.equals(lastHost)) {
+                        /*
+                         * Hostname has been updated; add updated endpoint
+                         * to list.
+                         */
+                        if (lastPort != 0) {
+                            /*
+                             * Remove outdated endpoints only if the
+                             * port has already been set on those endpoints.
+                             */
+                            epList.clear();
+                        }
+                        ep = new TCPEndpoint(localHost, lastPort, csf, ssf);
+                        ep.listenPort = port;
+                        ep.transport = lastTransport;
+                        epList.add(ep);
+                    }
+                }
+            }
+        }
+
+        return ep;
+    }
+
+    /**
+     * Resamples the local hostname and returns the possibly-updated
+     * local hostname.
+     */
+    private static String resampleLocalHost() {
+
+        String hostnameProperty = getHostnameProperty();
+
+        synchronized (localEndpoints) {
+            // assert(localHostKnown ^ (localHost == null))
+
+            if (hostnameProperty != null) {
+                if (!localHostKnown) {
+                    /*
+                     * If the local hostname is unknown, update ALL
+                     * existing endpoints with the new hostname.
+                     */
+                    setLocalHost(hostnameProperty);
+                } else if (!hostnameProperty.equals(localHost)) {
+                    /*
+                     * Only update the localHost field for reference
+                     * in future endpoint creation.
+                     */
+                    localHost = hostnameProperty;
+
+                    if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
+                        TCPTransport.tcpLog.log(Log.BRIEF,
+                            "updated local hostname to: " + localHost);
+                    }
+                }
+            }
+            return localHost;
+        }
+    }
+
+    /**
+     * Set the local host name, if currently unknown.
+     */
+    static void setLocalHost(String host) {
+        // assert (host != null)
+
+        synchronized (localEndpoints) {
+            /*
+             * If host is not known, change the host field of ALL
+             * the local endpoints.
+             */
+            if (!localHostKnown) {
+                localHost = host;
+                localHostKnown = true;
+
+                if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
+                    TCPTransport.tcpLog.log(Log.BRIEF,
+                        "local host set to " + host);
+                }
+                for (LinkedList<TCPEndpoint> epList : localEndpoints.values())
+                {
+                    synchronized (epList) {
+                        for (TCPEndpoint ep : epList) {
+                            ep.host = host;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Set the port of the (shared) default endpoint object.
+     * When first created, it contains port 0 because the transport
+     * hasn't tried to listen to get assigned a port, or if listening
+     * failed, a port hasn't been assigned from the server.
+     */
+    static void setDefaultPort(int port, RMIClientSocketFactory csf,
+                               RMIServerSocketFactory ssf)
+    {
+        TCPEndpoint endpointKey = new TCPEndpoint(null, 0, csf, ssf);
+
+        synchronized (localEndpoints) {
+            LinkedList<TCPEndpoint> epList = localEndpoints.get(endpointKey);
+
+            synchronized (epList) {
+                int size = epList.size();
+                TCPEndpoint lastEp = epList.getLast();
+
+                for (TCPEndpoint ep : epList) {
+                    ep.port = port;
+                }
+                if (size > 1) {
+                    /*
+                     * Remove all but the last element of the list
+                     * (which contains the most recent hostname).
+                     */
+                    epList.clear();
+                    epList.add(lastEp);
+                }
+            }
+
+            /*
+             * Allow future exports to use the actual bound port
+             * explicitly (see 6269166).
+             */
+            TCPEndpoint newEndpointKey = new TCPEndpoint(null, port, csf, ssf);
+            localEndpoints.put(newEndpointKey, epList);
+
+            if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
+                TCPTransport.tcpLog.log(Log.BRIEF,
+                    "default port for server socket factory " + ssf +
+                    " and client socket factory " + csf +
+                    " set to " + port);
+            }
+        }
+    }
+
+    /**
+     * Returns transport for making connections to remote endpoints;
+     * (here, the default transport at port 0 is used).
+     */
+    public Transport getOutboundTransport() {
+        TCPEndpoint localEndpoint = getLocalEndpoint(0, null, null);
+        return localEndpoint.transport;
+    }
+
+    /**
+     * Returns the current list of known transports.
+     * The returned list is an unshared collection of Transports,
+     * including all transports which may have channels to remote
+     * endpoints.
+     */
+    private static Collection<TCPTransport> allKnownTransports() {
+        // Loop through local endpoints, getting the transport of each one.
+        Set<TCPTransport> s;
+        synchronized (localEndpoints) {
+            // presize s to number of localEndpoints
+            s = new HashSet<TCPTransport>(localEndpoints.size());
+            for (LinkedList<TCPEndpoint> epList : localEndpoints.values()) {
+                /*
+                 * Each local endpoint has its transport added to s.
+                 * Note: the transport is the same for all endpoints
+                 * in the list, so it is okay to pick any one of them.
+                 */
+                TCPEndpoint ep = epList.getFirst();
+                s.add(ep.transport);
+            }
+        }
+        return s;
+    }
+
+    /**
+     * Release idle outbound connections to reduce demand on I/O resources.
+     * All transports are asked to release excess connections.
+     */
+    public static void shedConnectionCaches() {
+        for (TCPTransport transport : allKnownTransports()) {
+            transport.shedConnectionCaches();
+        }
+    }
+
+    /**
+     * Export the object to accept incoming calls.
+     */
+    public void exportObject(Target target) throws RemoteException {
+        transport.exportObject(target);
+    }
+
+    /**
+     * Returns a channel for this (remote) endpoint.
+     */
+    public Channel getChannel() {
+        return getOutboundTransport().getChannel(this);
+    }
+
+    /**
+     * Returns address for endpoint
+     */
+    public String getHost() {
+        return host;
+    }
+
+    /**
+     * Returns the port for this endpoint.  If this endpoint was
+     * created as a server endpoint (using getLocalEndpoint) for a
+     * default/anonymous port and its inbound transport has started
+     * listening, this method returns (instead of zero) the actual
+     * bound port suitable for passing to clients.
+     **/
+    public int getPort() {
+        return port;
+    }
+
+    /**
+     * Returns the port that this endpoint's inbound transport listens
+     * on, if this endpoint was created as a server endpoint (using
+     * getLocalEndpoint).  If this endpoint was created for the
+     * default/anonymous port, then this method returns zero even if
+     * the transport has started listening.
+     **/
+    public int getListenPort() {
+        return listenPort;
+    }
+
+    /**
+     * Returns the transport for incoming connections to this
+     * endpoint, if this endpoint was created as a server endpoint
+     * (using getLocalEndpoint).
+     **/
+    public Transport getInboundTransport() {
+        return transport;
+    }
+
+    /**
+     * Get the client socket factory associated with this endpoint.
+     */
+    public RMIClientSocketFactory getClientSocketFactory() {
+        return csf;
+    }
+
+    /**
+     * Get the server socket factory associated with this endpoint.
+     */
+    public RMIServerSocketFactory getServerSocketFactory() {
+        return ssf;
+    }
+
+    /**
+     * Return string representation for endpoint.
+     */
+    public String toString() {
+        return "[" + host + ":" + port +
+            (ssf != null ? "," + ssf : "") +
+            (csf != null ? "," + csf : "") +
+            "]";
+    }
+
+    public int hashCode() {
+        return port;
+    }
+
+    public boolean equals(Object obj) {
+        if ((obj != null) && (obj instanceof TCPEndpoint)) {
+            TCPEndpoint ep = (TCPEndpoint) obj;
+            if (port != ep.port || !host.equals(ep.host))
+                return false;
+            if (((csf == null) ^ (ep.csf == null)) ||
+                ((ssf == null) ^ (ep.ssf == null)))
+                return false;
+            /*
+             * Fix for 4254510: perform socket factory *class* equality check
+             * before socket factory equality check to avoid passing
+             * a potentially naughty socket factory to this endpoint's
+             * {client,server} socket factory equals method.
+             */
+            if ((csf != null) &&
+                !(csf.getClass() == ep.csf.getClass() && csf.equals(ep.csf)))
+                return false;
+            if ((ssf != null) &&
+                !(ssf.getClass() == ep.ssf.getClass() && ssf.equals(ep.ssf)))
+                return false;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /* codes for the self-describing formats of wire representation */
+    private static final int FORMAT_HOST_PORT           = 0;
+    private static final int FORMAT_HOST_PORT_FACTORY   = 1;
+
+    /**
+     * Write endpoint to output stream.
+     */
+    public void write(ObjectOutput out) throws IOException {
+        if (csf == null) {
+            out.writeByte(FORMAT_HOST_PORT);
+            out.writeUTF(host);
+            out.writeInt(port);
+        } else {
+            out.writeByte(FORMAT_HOST_PORT_FACTORY);
+            out.writeUTF(host);
+            out.writeInt(port);
+            out.writeObject(csf);
+        }
+    }
+
+    /**
+     * Get the endpoint from the input stream.
+     * @param in the input stream
+     * @exception IOException If id could not be read (due to stream failure)
+     */
+    public static TCPEndpoint read(ObjectInput in)
+        throws IOException, ClassNotFoundException
+    {
+        String host;
+        int port;
+        RMIClientSocketFactory csf = null;
+
+        byte format = in.readByte();
+        switch (format) {
+          case FORMAT_HOST_PORT:
+            host = in.readUTF();
+            port = in.readInt();
+            break;
+
+          case FORMAT_HOST_PORT_FACTORY:
+            host = in.readUTF();
+            port = in.readInt();
+            csf = (RMIClientSocketFactory) in.readObject();
+          break;
+
+          default:
+            throw new IOException("invalid endpoint format");
+        }
+        return new TCPEndpoint(host, port, csf, null);
+    }
+
+    /**
+     * Write endpoint to output stream in older format used by
+     * UnicastRef for JDK1.1 compatibility.
+     */
+    public void writeHostPortFormat(DataOutput out) throws IOException {
+        if (csf != null) {
+            throw new InternalError("TCPEndpoint.writeHostPortFormat: " +
+                "called for endpoint with non-null socket factory");
+        }
+        out.writeUTF(host);
+        out.writeInt(port);
+    }
+
+    /**
+     * Create a new endpoint from input stream data.
+     * @param in the input stream
+     */
+    public static TCPEndpoint readHostPortFormat(DataInput in)
+        throws IOException
+    {
+        String host = in.readUTF();
+        int port = in.readInt();
+        return new TCPEndpoint(host, port);
+    }
+
+    private static RMISocketFactory chooseFactory() {
+        RMISocketFactory sf = RMISocketFactory.getSocketFactory();
+        if (sf == null) {
+            sf = TCPTransport.defaultSocketFactory;
+        }
+        return sf;
+    }
+
+    /**
+     * Open and return new client socket connection to endpoint.
+     */
+    Socket newSocket() throws RemoteException {
+        if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) {
+            TCPTransport.tcpLog.log(Log.VERBOSE,
+                "opening socket to " + this);
+        }
+
+        Socket socket;
+
+        try {
+            RMIClientSocketFactory clientFactory = csf;
+            if (clientFactory == null) {
+                clientFactory = chooseFactory();
+            }
+            socket = clientFactory.createSocket(host, port);
+
+        } catch (java.net.UnknownHostException e) {
+            throw new java.rmi.UnknownHostException(
+                "Unknown host: " + host, e);
+        } catch (java.net.ConnectException e) {
+            throw new java.rmi.ConnectException(
+                "Connection refused to host: " + host, e);
+        } catch (IOException e) {
+            // We might have simply run out of file descriptors
+            try {
+                TCPEndpoint.shedConnectionCaches();
+                // REMIND: should we retry createSocket?
+            } catch (OutOfMemoryError | Exception mem) {
+                // don't quit if out of memory
+                // or shed fails non-catastrophically
+            }
+
+            throw new ConnectIOException("Exception creating connection to: " +
+                host, e);
+        }
+
+        // set socket to disable Nagle's algorithm (always send immediately)
+        // TBD: should this be left up to socket factory instead?
+        try {
+            socket.setTcpNoDelay(true);
+        } catch (Exception e) {
+            // if we fail to set this, ignore and proceed anyway
+        }
+
+        // fix 4187495: explicitly set SO_KEEPALIVE to prevent client hangs
+        try {
+            socket.setKeepAlive(true);
+        } catch (Exception e) {
+            // ignore and proceed
+        }
+
+        return socket;
+    }
+
+    /**
+     * Return new server socket to listen for connections on this endpoint.
+     */
+    ServerSocket newServerSocket() throws IOException {
+        if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) {
+            TCPTransport.tcpLog.log(Log.VERBOSE,
+                "creating server socket on " + this);
+        }
+
+        RMIServerSocketFactory serverFactory = ssf;
+        if (serverFactory == null) {
+            serverFactory = chooseFactory();
+        }
+        ServerSocket server = serverFactory.createServerSocket(listenPort);
+
+        // if we listened on an anonymous port, set the default port
+        // (for this socket factory)
+        if (listenPort == 0)
+            setDefaultPort(server.getLocalPort(), csf, ssf);
+
+        return server;
+    }
+
+    /**
+     * The class FQDN encapsulates a routine that makes a best effort
+     * attempt to retrieve the fully qualified domain name of the local
+     * host.
+     *
+     * @author  Laird Dornin
+     */
+    private static class FQDN implements Runnable {
+
+        /**
+         * strings in which we can store discovered fqdn
+         */
+        private String reverseLookup;
+
+        private String hostAddress;
+
+        private FQDN(String hostAddress) {
+            this.hostAddress = hostAddress;
+        }
+
+        /**
+         * Do our best to obtain a fully qualified hostname for the local
+         * host.  Perform the following steps to get a localhostname:
+         *
+         * 1. InetAddress.getLocalHost().getHostName() - if contains
+         *    '.' use as FQDN
+         * 2. if no '.' query name service for FQDN in a thread
+         *    Note: We query the name service for an FQDN by creating
+         *    an InetAddress via a stringified copy of the local ip
+         *    address; this creates an InetAddress with a null hostname.
+         *    Asking for the hostname of this InetAddress causes a name
+         *    service lookup.
+         *
+         * 3. if name service takes too long to return, use ip address
+         * 4. if name service returns but response contains no '.'
+         *    default to ipaddress.
+         */
+        static String attemptFQDN(InetAddress localAddr)
+            throws java.net.UnknownHostException
+        {
+
+            String hostName = localAddr.getHostName();
+
+            if (hostName.indexOf('.') < 0 ) {
+
+                String hostAddress = localAddr.getHostAddress();
+                FQDN f = new FQDN(hostAddress);
+
+                int nameServiceTimeOut =
+                    TCPEndpoint.getInt("sun.rmi.transport.tcp.localHostNameTimeOut",
+                                       10000);
+
+                try {
+                    synchronized(f) {
+                        f.getFQDN();
+
+                        /* wait to obtain an FQDN */
+                        f.wait(nameServiceTimeOut);
+                    }
+                } catch (InterruptedException e) {
+                    /* propagate the exception to the caller */
+                    Thread.currentThread().interrupt();
+                }
+                hostName = f.getHost();
+
+                if ((hostName == null) || (hostName.equals(""))
+                    || (hostName.indexOf('.') < 0 )) {
+
+                    hostName = hostAddress;
+                }
+            }
+            return hostName;
+        }
+
+        /**
+         * Method that that will start a thread to wait to retrieve a
+         * fully qualified domain name from a name service.  The spawned
+         * thread may never return but we have marked it as a daemon so the vm
+         * will terminate appropriately.
+         */
+        private void getFQDN() {
+
+            /* FQDN finder will run in RMI threadgroup. */
+            Thread t = AccessController.doPrivileged(
+                new NewThreadAction(FQDN.this, "FQDN Finder", true));
+            t.start();
+        }
+
+        private synchronized String getHost() {
+            return reverseLookup;
+        }
+
+        /**
+         * thread to query a name service for the fqdn of this host.
+         */
+        public void run()  {
+
+            String name = null;
+
+            try {
+                name = InetAddress.getByName(hostAddress).getHostName();
+            } catch (java.net.UnknownHostException e) {
+            } finally {
+                synchronized(this) {
+                    reverseLookup = name;
+                    this.notify();
+                }
+            }
+        }
+    }
+}