Re: RMI port release and LocateRegistry.createRegistry

Peter Jones - JavaSoft East <[email protected]>
Newsgroups gmane.comp.java.sun.rmi
Message-ID <20050720205609.GB25081@east>
Again regarding the topic of this old thread:

http://archives.java.sun.com/cgi-bin/wa?A2=ind0311&L=rmi-users&P=1026

>>> I'm afraid that you're just running into a bug (limitation) in
>>> Sun's current J2SE RMI implementations:
>>>
>>> http://developer.java.sun.com/developer/bugParade/bugs/4457683.html
>>> http://developer.java.sun.com/developer/bugParade/bugs/4508962.html

> Another potential workaround (alluded to in those bug reports) could
> be to create the registry with a custom RMIServerSocketFactory hack
> that returns a custom ServerSocket implementation that can be
> managed by your application to control whether or not it is actually
> listening on its TCP port.  When listening is enabled, it would keep
> a underlying normal ServerSocket open and delegate accept() through;
> when listening is disabled, it would close the underlying
> ServerSocket and let accept() invocations block (until listening is
> enabled again).

A few months ago I was advising someone on how to implement such a
workaround, and the result was the RMIServerSocketFactory class
appended below.  Note that it has only been minimally tested, so use
at your own risk.

Also, note that it seems to have at least one questionable behavior:
when the original listen port was zero (anonymous port) and the
listening on that port is switched off and then on again, it attempts
to listen on the same actual (non-zero) port that was used previously,
which may fail.  I don't remember exactly why this choice was made (I
guess that it dodges the issue of ServerSocket.getLocalPort returning
varying values).  Presumably, however, this workaround is primarily
desired for fixed ports anyway.

-- Peter


import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.BindException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.rmi.server.RMIServerSocketFactory;
import java.util.HashMap;
import java.util.Map;

/**
 * An RMIServerSocketFactory implementation that supports switching
 * off any server socket that it has created, so that its TCP port is
 * freed, and also switching it back on again, so that it can again
 * receive remote invocations, without disrupting the RMI runtime
 * implementation.
 *
 * This class is intended as a workaround for the following bug in
 * Sun's J2SE RMI implementation (through JDK 5.0) and J2ME RMI OP
 * implementation:
 *
 * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4457683
 *
 * IMPLEMENTATION NOTE: This version of this class is designed to work
 * with J2SE 1.3, so that it can also be used with J2ME CDC/FP 1.0.x
 * and RMI OP 1.0.  If it to be used with J2SE 1.4 or later, consider
 * changes such as using the language's built-in assertion facility,
 * uncommenting the invocations of Thread.holdsLock, and overriding
 * the additional public methods of the java.net.ServerSocket class.
 **/
public class SwitchedRMIServerSocketFactory
    implements RMIServerSocketFactory
{
    /** factory used to create underlying server sockets, or null */
    private final RMIServerSocketFactory ssf;

    /** guards "bindings" and "switches" */
    private final Object factoryLock = new Object();

    /** maps port number to Switch */
    private final Map switches = new HashMap();

    /** maps port number to SwitchedServerSocket */
    private final Map bindings = new HashMap();

    /**
     * Creates a new SwitchedRMIServerSocketFactory that uses the
     * specified RMIServerSocketFactory, if non-null, to create the
     * underlying server sockets.
     **/
    public SwitchedRMIServerSocketFactory(RMIServerSocketFactory ssf) {
        this.ssf = ssf;
    }

    // implements RMIServerSocketFactory.createServerSocket
    public ServerSocket createServerSocket(int port) throws IOException {
        synchronized (factoryLock) {
            Object key = new Integer(port);
            if (bindings.get(key) != null) {
                throw new BindException("port already bound: " + port);
            }
            Switch sw = getSwitch(port);
            ServerSocket sss = new SwitchedServerSocket(port, sw);
            bindings.put(key, sss);
            return sss;
        }
    }

    /**
     * Switches off server sockets created by this factory for the
     * specified port number.  While switched off, a server socket for
     * the port will block on an accept invocation until switched on
     * or closed.
     **/
    public void switchOff(int port) {
        getSwitch(port).setOff();
    }

    /**
     * Switches on server sockets created by this factory for the
     * specified port number.  While switched on, a server socket for
     * the port will delegate accept invocations to an underlying
     * server socket for the port.
     **/
    public void switchOn(int port) {
        getSwitch(port).setOn();
    }

    public int hashCode() {
        return getClass().hashCode() ^ (ssf == null ? 0 : ssf.hashCode());
    }

    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        } else if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        SwitchedRMIServerSocketFactory other =
            (SwitchedRMIServerSocketFactory) obj;
        return ssf == null ? other.ssf == null : ssf.equals(other.ssf);
    }

    private Switch getSwitch(int port) {
        synchronized (factoryLock) {
            Object key = new Integer(port);
            Switch sw = (Switch) switches.get(key);
            if (sw == null) {
                sw = new Switch();
                switches.put(key, sw);
            }
            return sw;
        }
    }

    /**
     * Object that represents the switched state of this factory for a
     * port number, independent of particular switched server sockets
     * (which may get created and closed over time).
     *
     * The lock for a Switch instance guards all mutable instance
     * state ("off", "offCount", and "serverSocket") as well as the
     * "closed" flag for any associated switched server socket, and
     * all waiters are notified when the switch transitions to the on
     * state or when an associated switched server socket is closed.
     **/
    private static class Switch {
        /** true if the associated port number is currently switch off */
        private boolean off = false;

        /**
         * counter incremented each time this switch transitions to
         * the off state; useful for diagnosing asynchronous server
         * socket closure
         **/
        private long offCount = Long.MIN_VALUE;

        /**
         * the current underlying server socket, which can only be set
         * when this switch is in the on state, to be asynchronously
         * closed when this switch next transitions to the off state
         **/
        private ServerSocket serverSocket = null;

        Switch() { }

        // methods invoked by SwitchedRMIServerSocketFactory.switchOff/On:

        synchronized void setOff() {
            if (!off) {
                if (serverSocket != null) {
                    try {
                        serverSocket.close();
                    } catch (IOException e) {
                    }
                    serverSocket = null;
                }
                off = true;
                offCount++;
            }
        }

        // invoked by SwitchedRMIServerSocketFactory.switchOff
        synchronized void setOn() {
            if (off) {
                _assert(serverSocket == null);
                off = false;
                notifyAll();
            }
        }

        // methods invoked by SwitchedServerSocket:

        boolean isOff() {
            // assert Thread.holdsLock(this);
            return off;
        }

        long getOffCount() {
            // assert Thread.holdsLock(this);
            return offCount;
        }

        ServerSocket getCurrentServerSocket() {
            // assert Thread.holdsLock(this);
            _assert(!off);
            return serverSocket;
        }

        void setCurrentServerSocket(ServerSocket serverSocket) {
            // assert Thread.holdsLock(this);
            _assert(!off);
            this.serverSocket = serverSocket;
        }
    }

    private class SwitchedServerSocket extends ServerSocket {
        /** port number listened on */
        private final int listenPort;

        /** switch that controls this server socket */
        private final Switch sw;

        /** local address initial underlying server socket bound to */
        private final InetAddress boundAddress;

        /**
         * local port initial underlying server socket bound to;
         * needed in case listenPort is zero, so that the same
         * anonymously-chosen port is reused when switched on
         **/
        private final int boundPort;

        /** true if this switched server socket has been closed */
        private boolean closed = false;

        SwitchedServerSocket(int listenPort, Switch sw)
            throws IOException
        {
            /*
             * NOTE: In J2SE 1.3, ServerSocket does not allow subcless
             * to construct an unbound instance, so an anonymous port
             * must be bound and released; in 1.4 or later, could just
             * use "super()" instead.
             */
            super(0);
            super.close();

            this.listenPort = listenPort;
            this.sw = sw;
            ServerSocket serverSocket = newServerSocket(listenPort);
            boundAddress = serverSocket.getInetAddress();
            boundPort = serverSocket.getLocalPort();
            synchronized (sw) {
                if (sw.isOff()) {
                    serverSocket.close();
                } else {
                    sw.setCurrentServerSocket(serverSocket);
                }
            }
        }

        private ServerSocket newServerSocket(int port) throws IOException {
            if (ssf == null) {
                return new ServerSocket(port);
            } else {
                return ssf.createServerSocket(port);
            }
        }

        public InetAddress getInetAddress() { return boundAddress; }
        public int getLocalPort() { return boundPort; }

        public Socket accept() throws IOException {
            while (true) {
                ServerSocket serverSocket;
                long switchOffCount;
                synchronized (sw) {
                    while (!closed && sw.isOff()) {
                        try {
                            sw.wait();
                        } catch (InterruptedException e) {
                            throw new InterruptedIOException();
                        }
                    }
                    if (closed) {
                        throw new IOException("switched server socket closed");
                    }
                    serverSocket = sw.getCurrentServerSocket();
                    if (serverSocket == null) {
                        // hopefully the same port can be rebound
                        serverSocket = newServerSocket(boundPort);
                        sw.setCurrentServerSocket(serverSocket);
                    }
                    switchOffCount = sw.getOffCount();
                }
                try {
                    return serverSocket.accept();
                } catch (IOException e) {
                    synchronized (sw) {
                        if (switchOffCount == sw.getOffCount()) {
                            /*
                             * If there was not an off transition
                             * since the last synchronization, then
                             * the IOException is not explainable by
                             * an asynchronous close by the switch, so
                             * let the exception be thrown.
                             */
                            throw e;
                        } else {
                            continue;
                        }
                    }
                }
            }
        }

        public void close() {
            synchronized (factoryLock) {
                synchronized (sw) {
                    closed = true;
                    if (sw.isOff()) {
                        // unblock accept invocations
                        sw.notifyAll();
                    } else {
                        ServerSocket serverSocket =
                            sw.getCurrentServerSocket();
                        if (serverSocket != null) {
                            try {
                                serverSocket.close();
                            } catch (IOException e) {
                            }
                        }
                        sw.setCurrentServerSocket(null);
                    }
                }
                bindings.remove(new Integer(listenPort));
            }
        }

        public void setSoTimeout(int timeout) {
            throw new UnsupportedOperationException();
        }

        public int getSoTimeout() {
            throw new UnsupportedOperationException();
        }

        public String toString() {
            return "SwitchedServerSocket[addr=" + getInetAddress() +
                ",localport=" + getLocalPort() + "]";
        }
    }

    private static void _assert(boolean assertion) {
        if (!assertion) { throw new Error("assertion error"); }
    }
}

===========================================================================
To unsubscribe, send email to [email protected] and include in the body
of the message "signoff RMI-USERS".  For general help, send email to
[email protected] and include in the body of the message "help".

For a list of frequently asked RMI questions please refer to:
http://java.sun.com/j2se/1.3/docs/guide/rmi/faq.html

To view past RMI-USERS postings, please see:
http://archives.java.sun.com/archives/rmi-users.html
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.