807 lines · 32.2 KB · Java
RawDownload
/* * Copyright (c) 1996, 2008, 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; import java.lang.ref.PhantomReference;import java.lang.ref.ReferenceQueue;import java.security.AccessController;import java.security.PrivilegedAction;import java.util.HashMap;import java.util.HashSet;import java.util.Iterator;import java.util.List;import java.util.Map;import java.util.Set;import java.rmi.ConnectException;import java.rmi.RemoteException;import java.rmi.dgc.DGC;import java.rmi.dgc.Lease;import java.rmi.dgc.VMID;import java.rmi.server.ObjID;import sun.misc.GC;import sun.rmi.runtime.NewThreadAction;import sun.rmi.server.UnicastRef;import sun.rmi.server.Util;import sun.security.action.GetLongAction; /** * DGCClient implements the client-side of the RMI distributed garbage * collection system. * * The external interface to DGCClient is the "registerRefs" method. * When a LiveRef to a remote object enters the VM, it needs to be * registered with the DGCClient to participate in distributed garbage * collection. * * When the first LiveRef to a particular remote object is registered, * a "dirty" call is made to the server-side distributed garbage * collector for the remote object, which returns a lease guaranteeing * that the server-side DGC will not collect the remote object for a * certain period of time.  While LiveRef instances to remote objects * on a particular server exist, the DGCClient periodically sends more * "dirty" calls to renew its lease. * * The DGCClient tracks the local reachability of registered LiveRef * instances (using phantom references).  When the LiveRef instance * for a particular remote object becomes garbage collected locally, * a "clean" call is made to the server-side distributed garbage * collector, indicating that the server no longer needs to keep the * remote object alive for this client. * * @see java.rmi.dgc.DGC, sun.rmi.transport.DGCImpl * * @author  Ann Wollrath * @author  Peter Jones */final class DGCClient {     /** next sequence number for DGC calls (access synchronized on class) */    private static long nextSequenceNum = Long.MIN_VALUE;     /** unique identifier for this VM as a client of DGC */    private static VMID vmid = new VMID();     /** lease duration to request (usually ignored by server) */    private static final long leaseValue =              // default 10 minutes        AccessController.doPrivileged(            new GetLongAction("java.rmi.dgc.leaseValue",                              600000)).longValue();     /** maximum interval between retries of failed clean calls */    private static final long cleanInterval =           // default 3 minutes        AccessController.doPrivileged(            new GetLongAction("sun.rmi.dgc.cleanInterval",                              180000)).longValue();     /** maximum interval between complete garbage collections of local heap */    private static final long gcInterval =              // default 1 hour        AccessController.doPrivileged(            new GetLongAction("sun.rmi.dgc.client.gcInterval",                              3600000)).longValue();     /** minimum retry count for dirty calls that fail */    private static final int dirtyFailureRetries = 5;     /** retry count for clean calls that fail with ConnectException */    private static final int cleanFailureRetries = 5;     /** constant empty ObjID array for lease renewal optimization */    private static final ObjID[] emptyObjIDArray = new ObjID[0];     /** ObjID for server-side DGC object */    private static final ObjID dgcID = new ObjID(ObjID.DGC_ID);     /*     * Disallow anyone from creating one of these.     */    private DGCClient() {}     /**     * Register the LiveRef instances in the supplied list to participate     * in distributed garbage collection.     *     * All of the LiveRefs in the list must be for remote objects at the     * given endpoint.     */    static void registerRefs(Endpoint ep, List refs) {        /*         * Look up the given endpoint and register the refs with it.         * The retrieved entry may get removed from the global endpoint         * table before EndpointEntry.registerRefs() is able to acquire         * its lock; in this event, it returns false, and we loop and         * try again.         */        EndpointEntry epEntry;        do {            epEntry = EndpointEntry.lookup(ep);        } while (!epEntry.registerRefs(refs));    }     /**     * Get the next sequence number to be used for a dirty or clean     * operation from this VM.  This method should only be called while     * synchronized on the EndpointEntry whose data structures the     * operation affects.     */    private static synchronized long getNextSequenceNum() {        return nextSequenceNum++;    }     /**     * Given the length of a lease and the time that it was granted,     * compute the absolute time at which it should be renewed, giving     * room for reasonable computational and communication delays.     */    private static long computeRenewTime(long grantTime, long duration) {        /*         * REMIND: This algorithm should be more sophisticated, waiting         * a longer fraction of the lease duration for longer leases.         */        return grantTime + (duration / 2);    }     /**     * EndpointEntry encapsulates the client-side DGC information specific     * to a particular Endpoint.  Of most significance is the table that     * maps LiveRef value to RefEntry objects and the renew/clean thread     * that handles asynchronous client-side DGC operations.     */    private static class EndpointEntry {         /** the endpoint that this entry is for */        private Endpoint endpoint;        /** synthesized reference to the remote server-side DGC */        private DGC dgc;         /** table of refs held for endpoint: maps LiveRef to RefEntry */        private Map refTable = new HashMap(5);        /** set of RefEntry instances from last (failed) dirty call */        private Set invalidRefs = new HashSet(5);         /** true if this entry has been removed from the global table */        private boolean removed = false;         /** absolute time to renew current lease to this endpoint */        private long renewTime = Long.MAX_VALUE;        /** absolute time current lease to this endpoint will expire */        private long expirationTime = Long.MIN_VALUE;        /** count of recent dirty calls that have failed */        private int dirtyFailures = 0;        /** absolute time of first recent failed dirty call */        private long dirtyFailureStartTime;        /** (average) elapsed time for recent failed dirty calls */        private long dirtyFailureDuration;         /** renew/clean thread for handling lease renewals and clean calls */        private Thread renewCleanThread;        /** true if renew/clean thread may be interrupted */        private boolean interruptible = false;         /** reference queue for phantom references */        private ReferenceQueue refQueue = new ReferenceQueue();        /** set of clean calls that need to be made */        private Set pendingCleans = new HashSet(5);         /** global endpoint table: maps Endpoint to EndpointEntry */        private static Map endpointTable = new HashMap(5);        /** handle for GC latency request (for future cancellation) */        private static GC.LatencyRequest gcLatencyRequest = null;         /**         * Look up the EndpointEntry for the given Endpoint.  An entry is         * created if one does not already exist.         */        public static EndpointEntry lookup(Endpoint ep) {            synchronized (endpointTable) {                EndpointEntry entry = (EndpointEntry) endpointTable.get(ep);                if (entry == null) {                    entry = new EndpointEntry(ep);                    endpointTable.put(ep, entry);                    /*                     * While we are tracking live remote references registered                     * in this VM, request a maximum latency for inspecting the                     * entire heap from the local garbage collector, to place                     * an upper bound on the time to discover remote references                     * that have become unreachable (see bugid 4171278).                     */                    if (gcLatencyRequest == null) {                        gcLatencyRequest = GC.requestLatency(gcInterval);                    }                }                return entry;            }        }         private EndpointEntry(final Endpoint endpoint) {            this.endpoint = endpoint;            try {                LiveRef dgcRef = new LiveRef(dgcID, endpoint, false);                dgc = (DGC) Util.createProxy(DGCImpl.class,                                             new UnicastRef(dgcRef), true);            } catch (RemoteException e) {                throw new Error("internal error creating DGC stub");            }            renewCleanThread =  AccessController.doPrivileged(                new NewThreadAction(new RenewCleanThread(),                                    "RenewClean-" + endpoint, true));            renewCleanThread.start();        }         /**         * Register the LiveRef instances in the supplied list to participate         * in distributed garbage collection.         *         * This method returns false if this entry was removed from the         * global endpoint table (because it was empty) before these refs         * could be registered.  In that case, a new EndpointEntry needs         * to be looked up.         *         * This method must NOT be called while synchronized on this entry.         */        public boolean registerRefs(List refs) {            assert !Thread.holdsLock(this);             Set refsToDirty = null;     // entries for refs needing dirty            long sequenceNum;           // sequence number for dirty call             synchronized (this) {                if (removed) {                    return false;                }                 Iterator iter = refs.iterator();                while (iter.hasNext()) {                    LiveRef ref = (LiveRef) iter.next();                    assert ref.getEndpoint().equals(endpoint);                     RefEntry refEntry = (RefEntry) refTable.get(ref);                    if (refEntry == null) {                        LiveRef refClone = (LiveRef) ref.clone();                        refEntry = new RefEntry(refClone);                        refTable.put(refClone, refEntry);                        if (refsToDirty == null) {                            refsToDirty = new HashSet(5);                        }                        refsToDirty.add(refEntry);                    }                     refEntry.addInstanceToRefSet(ref);                }                 if (refsToDirty == null) {                    return true;                }                 refsToDirty.addAll(invalidRefs);                invalidRefs.clear();                 sequenceNum = getNextSequenceNum();            }             makeDirtyCall(refsToDirty, sequenceNum);            return true;        }         /**         * Remove the given RefEntry from the ref table.  If that makes         * the ref table empty, remove this entry from the global endpoint         * table.         *         * This method must ONLY be called while synchronized on this entry.         */        private void removeRefEntry(RefEntry refEntry) {            assert Thread.holdsLock(this);            assert !removed;            assert refTable.containsKey(refEntry.getRef());             refTable.remove(refEntry.getRef());            invalidRefs.remove(refEntry);            if (refTable.isEmpty()) {                synchronized (endpointTable) {                    endpointTable.remove(endpoint);                    Transport transport = endpoint.getOutboundTransport();                    transport.free(endpoint);                    /*                     * If there are no longer any live remote references                     * registered, we are no longer concerned with the                     * latency of local garbage collection here.                     */                    if (endpointTable.isEmpty()) {                        assert gcLatencyRequest != null;                        gcLatencyRequest.cancel();                        gcLatencyRequest = null;                    }                    removed = true;                }            }        }         /**         * Make a DGC dirty call to this entry's endpoint, for the ObjIDs         * corresponding to the given set of refs and with the given         * sequence number.         *         * This method must NOT be called while synchronized on this entry.         */        private void makeDirtyCall(Set refEntries, long sequenceNum) {            assert !Thread.holdsLock(this);             ObjID[] ids;            if (refEntries != null) {                ids = createObjIDArray(refEntries);            } else {                ids = emptyObjIDArray;            }             long startTime = System.currentTimeMillis();            try {                Lease lease =                    dgc.dirty(ids, sequenceNum, new Lease(vmid, leaseValue));                long duration = lease.getValue();                 long newRenewTime = computeRenewTime(startTime, duration);                long newExpirationTime = startTime + duration;                 synchronized (this) {                    dirtyFailures = 0;                    setRenewTime(newRenewTime);                    expirationTime = newExpirationTime;                }             } catch (Exception e) {                long endTime = System.currentTimeMillis();                 synchronized (this) {                    dirtyFailures++;                     if (dirtyFailures == 1) {                        /*                         * If this was the first recent failed dirty call,                         * reschedule another one immediately, in case there                         * was just a transient network problem, and remember                         * the start time and duration of this attempt for                         * future calculations of the delays between retries.                         */                        dirtyFailureStartTime = startTime;                        dirtyFailureDuration = endTime - startTime;                        setRenewTime(endTime);                    } else {                        /*                         * For each successive failed dirty call, wait for a                         * (binary) exponentially increasing delay before                         * retrying, to avoid network congestion.                         */                        int n = dirtyFailures - 2;                        if (n == 0) {                            /*                             * Calculate the initial retry delay from the                             * average time elapsed for each of the first                             * two failed dirty calls.  The result must be                             * at least 1000ms, to prevent a tight loop.                             */                            dirtyFailureDuration =                                Math.max((dirtyFailureDuration +                                          (endTime - startTime)) >> 1, 1000);                        }                        long newRenewTime =                            endTime + (dirtyFailureDuration << n);                         /*                         * Continue if the last known held lease has not                         * expired, or else at least a fixed number of times,                         * or at least until we've tried for a fixed amount                         * of time (the default lease value we request).                         */                        if (newRenewTime < expirationTime ||                            dirtyFailures < dirtyFailureRetries ||                            newRenewTime < dirtyFailureStartTime + leaseValue)                        {                            setRenewTime(newRenewTime);                        } else {                            /*                             * Give up: postpone lease renewals until next                             * ref is registered for this endpoint.                             */                            setRenewTime(Long.MAX_VALUE);                        }                    }                     if (refEntries != null) {                        /*                         * Add all of these refs to the set of refs for this                         * endpoint that may be invalid (this VM may not be in                         * the server's referenced set), so that we will                         * attempt to explicitly dirty them again in the                         * future.                         */                        invalidRefs.addAll(refEntries);                         /*                         * Record that a dirty call has failed for all of these                         * refs, so that clean calls for them in the future                         * will be strong.                         */                        Iterator iter = refEntries.iterator();                        while (iter.hasNext()) {                            RefEntry refEntry = (RefEntry) iter.next();                            refEntry.markDirtyFailed();                        }                    }                     /*                     * If the last known held lease will have expired before                     * the next renewal, all refs might be invalid.                     */                    if (renewTime >= expirationTime) {                        invalidRefs.addAll(refTable.values());                    }                }            }        }         /**         * Set the absolute time at which the lease for this entry should         * be renewed.         *         * This method must ONLY be called while synchronized on this entry.         */        private void setRenewTime(long newRenewTime) {            assert Thread.holdsLock(this);             if (newRenewTime < renewTime) {                renewTime = newRenewTime;                if (interruptible) {                    AccessController.doPrivileged(                        new PrivilegedAction<Void>() {                            public Void run() {                            renewCleanThread.interrupt();                            return null;                        }                    });                }            } else {                renewTime = newRenewTime;            }        }         /**         * RenewCleanThread handles the asynchronous client-side DGC activity         * for this entry: renewing the leases and making clean calls.         */        private class RenewCleanThread implements Runnable {             public void run() {                do {                    long timeToWait;                    RefEntry.PhantomLiveRef phantom = null;                    boolean needRenewal = false;                    Set refsToDirty = null;

Lines 1–500 of 807.