Path: blob/master/src/java.rmi/share/classes/sun/rmi/transport/DGCClient.java
41154 views
/*1* Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package sun.rmi.transport;2526import java.io.InvalidClassException;27import java.lang.ref.PhantomReference;28import java.lang.ref.ReferenceQueue;29import java.net.SocketPermission;30import java.rmi.UnmarshalException;31import java.security.AccessController;32import java.security.PrivilegedAction;33import java.util.HashMap;34import java.util.HashSet;35import java.util.Iterator;36import java.util.List;37import java.util.Map;38import java.util.Set;39import java.rmi.ConnectException;40import java.rmi.RemoteException;41import java.rmi.dgc.DGC;42import java.rmi.dgc.Lease;43import java.rmi.dgc.VMID;44import java.rmi.server.ObjID;4546import sun.rmi.runtime.Log;47import sun.rmi.runtime.NewThreadAction;48import sun.rmi.server.UnicastRef;49import sun.rmi.server.Util;5051import java.security.AccessControlContext;52import java.security.Permissions;53import java.security.ProtectionDomain;5455/**56* DGCClient implements the client-side of the RMI distributed garbage57* collection system.58*59* The external interface to DGCClient is the "registerRefs" method.60* When a LiveRef to a remote object enters the VM, it needs to be61* registered with the DGCClient to participate in distributed garbage62* collection.63*64* When the first LiveRef to a particular remote object is registered,65* a "dirty" call is made to the server-side distributed garbage66* collector for the remote object, which returns a lease guaranteeing67* that the server-side DGC will not collect the remote object for a68* certain period of time. While LiveRef instances to remote objects69* on a particular server exist, the DGCClient periodically sends more70* "dirty" calls to renew its lease.71*72* The DGCClient tracks the local reachability of registered LiveRef73* instances (using phantom references). When the LiveRef instance74* for a particular remote object becomes garbage collected locally,75* a "clean" call is made to the server-side distributed garbage76* collector, indicating that the server no longer needs to keep the77* remote object alive for this client.78*79* @see java.rmi.dgc.DGC, sun.rmi.transport.DGCImpl80*81* @author Ann Wollrath82* @author Peter Jones83*/84@SuppressWarnings("removal")85final class DGCClient {8687/** next sequence number for DGC calls (access synchronized on class) */88private static long nextSequenceNum = Long.MIN_VALUE;8990/** unique identifier for this VM as a client of DGC */91private static VMID vmid = new VMID();9293/** lease duration to request (usually ignored by server) */94private static final long leaseValue = // default 10 minutes95AccessController.doPrivileged((PrivilegedAction<Long>) () ->96Long.getLong("java.rmi.dgc.leaseValue", 600000));9798/** maximum interval between retries of failed clean calls */99private static final long cleanInterval = // default 3 minutes100AccessController.doPrivileged((PrivilegedAction<Long>) () ->101Long.getLong("sun.rmi.dgc.cleanInterval", 180000));102103/** maximum interval between complete garbage collections of local heap */104private static final long gcInterval = // default 1 hour105AccessController.doPrivileged((PrivilegedAction<Long>) () ->106Long.getLong("sun.rmi.dgc.client.gcInterval", 3600000));107108/** minimum retry count for dirty calls that fail */109private static final int dirtyFailureRetries = 5;110111/** retry count for clean calls that fail with ConnectException */112private static final int cleanFailureRetries = 5;113114/** constant empty ObjID array for lease renewal optimization */115private static final ObjID[] emptyObjIDArray = new ObjID[0];116117/** ObjID for server-side DGC object */118private static final ObjID dgcID = new ObjID(ObjID.DGC_ID);119120/**121* An AccessControlContext with only socket permissions,122* suitable for an RMIClientSocketFactory.123*/124private static final AccessControlContext SOCKET_ACC;125static {126Permissions perms = new Permissions();127perms.add(new SocketPermission("*", "connect,resolve"));128ProtectionDomain[] pd = { new ProtectionDomain(null, perms) };129SOCKET_ACC = new AccessControlContext(pd);130}131132/*133* Disallow anyone from creating one of these.134*/135private DGCClient() {}136137/**138* Register the LiveRef instances in the supplied list to participate139* in distributed garbage collection.140*141* All of the LiveRefs in the list must be for remote objects at the142* given endpoint.143*/144static void registerRefs(Endpoint ep, List<LiveRef> refs) {145/*146* Look up the given endpoint and register the refs with it.147* The retrieved entry may get removed from the global endpoint148* table before EndpointEntry.registerRefs() is able to acquire149* its lock; in this event, it returns false, and we loop and150* try again.151*/152EndpointEntry epEntry;153do {154epEntry = EndpointEntry.lookup(ep);155} while (!epEntry.registerRefs(refs));156}157158/**159* Get the next sequence number to be used for a dirty or clean160* operation from this VM. This method should only be called while161* synchronized on the EndpointEntry whose data structures the162* operation affects.163*/164private static synchronized long getNextSequenceNum() {165return nextSequenceNum++;166}167168/**169* Given the length of a lease and the time that it was granted,170* compute the absolute time at which it should be renewed, giving171* room for reasonable computational and communication delays.172*/173private static long computeRenewTime(long grantTime, long duration) {174/*175* REMIND: This algorithm should be more sophisticated, waiting176* a longer fraction of the lease duration for longer leases.177*/178return grantTime + (duration / 2);179}180181/**182* EndpointEntry encapsulates the client-side DGC information specific183* to a particular Endpoint. Of most significance is the table that184* maps LiveRef value to RefEntry objects and the renew/clean thread185* that handles asynchronous client-side DGC operations.186*/187private static class EndpointEntry {188189/** the endpoint that this entry is for */190private Endpoint endpoint;191/** synthesized reference to the remote server-side DGC */192private DGC dgc;193194/** table of refs held for endpoint: maps LiveRef to RefEntry */195private Map<LiveRef, RefEntry> refTable = new HashMap<>(5);196/** set of RefEntry instances from last (failed) dirty call */197private Set<RefEntry> invalidRefs = new HashSet<>(5);198199/** true if this entry has been removed from the global table */200private boolean removed = false;201202/** absolute time to renew current lease to this endpoint */203private long renewTime = Long.MAX_VALUE;204/** absolute time current lease to this endpoint will expire */205private long expirationTime = Long.MIN_VALUE;206/** count of recent dirty calls that have failed */207private int dirtyFailures = 0;208/** absolute time of first recent failed dirty call */209private long dirtyFailureStartTime;210/** (average) elapsed time for recent failed dirty calls */211private long dirtyFailureDuration;212213/** renew/clean thread for handling lease renewals and clean calls */214private Thread renewCleanThread;215/** true if renew/clean thread may be interrupted */216private boolean interruptible = false;217218/** reference queue for phantom references */219private ReferenceQueue<LiveRef> refQueue = new ReferenceQueue<>();220/** set of clean calls that need to be made */221private Set<CleanRequest> pendingCleans = new HashSet<>(5);222223/** global endpoint table: maps Endpoint to EndpointEntry */224private static Map<Endpoint,EndpointEntry> endpointTable = new HashMap<>(5);225/** handle for GC latency request (for future cancellation) */226private static GC.LatencyRequest gcLatencyRequest = null;227228/**229* Look up the EndpointEntry for the given Endpoint. An entry is230* created if one does not already exist.231*/232public static EndpointEntry lookup(Endpoint ep) {233synchronized (endpointTable) {234EndpointEntry entry = endpointTable.get(ep);235if (entry == null) {236entry = new EndpointEntry(ep);237endpointTable.put(ep, entry);238/*239* While we are tracking live remote references registered240* in this VM, request a maximum latency for inspecting the241* entire heap from the local garbage collector, to place242* an upper bound on the time to discover remote references243* that have become unreachable (see bugid 4171278).244*/245if (gcLatencyRequest == null) {246gcLatencyRequest = GC.requestLatency(gcInterval);247}248}249return entry;250}251}252253private EndpointEntry(final Endpoint endpoint) {254this.endpoint = endpoint;255try {256LiveRef dgcRef = new LiveRef(dgcID, endpoint, false);257dgc = (DGC) Util.createProxy(DGCImpl.class,258new UnicastRef(dgcRef), true);259} catch (RemoteException e) {260throw new Error("internal error creating DGC stub");261}262renewCleanThread = AccessController.doPrivileged(263new NewThreadAction(new RenewCleanThread(),264"RenewClean-" + endpoint, true));265renewCleanThread.start();266}267268/**269* Register the LiveRef instances in the supplied list to participate270* in distributed garbage collection.271*272* This method returns false if this entry was removed from the273* global endpoint table (because it was empty) before these refs274* could be registered. In that case, a new EndpointEntry needs275* to be looked up.276*277* This method must NOT be called while synchronized on this entry.278*/279public boolean registerRefs(List<LiveRef> refs) {280assert !Thread.holdsLock(this);281282Set<RefEntry> refsToDirty = null; // entries for refs needing dirty283long sequenceNum; // sequence number for dirty call284285synchronized (this) {286if (removed) {287return false;288}289290Iterator<LiveRef> iter = refs.iterator();291while (iter.hasNext()) {292LiveRef ref = iter.next();293assert ref.getEndpoint().equals(endpoint);294295RefEntry refEntry = refTable.get(ref);296if (refEntry == null) {297LiveRef refClone = (LiveRef) ref.clone();298refEntry = new RefEntry(refClone);299refTable.put(refClone, refEntry);300if (refsToDirty == null) {301refsToDirty = new HashSet<>(5);302}303refsToDirty.add(refEntry);304}305306refEntry.addInstanceToRefSet(ref);307}308309if (refsToDirty == null) {310return true;311}312313refsToDirty.addAll(invalidRefs);314invalidRefs.clear();315316sequenceNum = getNextSequenceNum();317}318319makeDirtyCall(refsToDirty, sequenceNum);320return true;321}322323/**324* Remove the given RefEntry from the ref table. If that makes325* the ref table empty, remove this entry from the global endpoint326* table.327*328* This method must ONLY be called while synchronized on this entry.329*/330private void removeRefEntry(RefEntry refEntry) {331assert Thread.holdsLock(this);332assert !removed;333assert refTable.containsKey(refEntry.getRef());334335refTable.remove(refEntry.getRef());336invalidRefs.remove(refEntry);337if (refTable.isEmpty()) {338synchronized (endpointTable) {339endpointTable.remove(endpoint);340Transport transport = endpoint.getOutboundTransport();341transport.free(endpoint);342/*343* If there are no longer any live remote references344* registered, we are no longer concerned with the345* latency of local garbage collection here.346*/347if (endpointTable.isEmpty()) {348assert gcLatencyRequest != null;349gcLatencyRequest.cancel();350gcLatencyRequest = null;351}352removed = true;353}354}355}356357/**358* Make a DGC dirty call to this entry's endpoint, for the ObjIDs359* corresponding to the given set of refs and with the given360* sequence number.361*362* This method must NOT be called while synchronized on this entry.363*/364private void makeDirtyCall(Set<RefEntry> refEntries, long sequenceNum) {365assert !Thread.holdsLock(this);366367ObjID[] ids;368if (refEntries != null) {369ids = createObjIDArray(refEntries);370} else {371ids = emptyObjIDArray;372}373374long startTime = System.currentTimeMillis();375try {376Lease lease =377dgc.dirty(ids, sequenceNum, new Lease(vmid, leaseValue));378long duration = lease.getValue();379380long newRenewTime = computeRenewTime(startTime, duration);381long newExpirationTime = startTime + duration;382383synchronized (this) {384dirtyFailures = 0;385setRenewTime(newRenewTime);386expirationTime = newExpirationTime;387}388389} catch (Exception e) {390long endTime = System.currentTimeMillis();391392synchronized (this) {393dirtyFailures++;394395if (e instanceof UnmarshalException396&& e.getCause() instanceof InvalidClassException) {397DGCImpl.dgcLog.log(Log.BRIEF, "InvalidClassException exception in DGC dirty call", e);398return; // protocol error, do not register these refs399}400401if (dirtyFailures == 1) {402/*403* If this was the first recent failed dirty call,404* reschedule another one immediately, in case there405* was just a transient network problem, and remember406* the start time and duration of this attempt for407* future calculations of the delays between retries.408*/409dirtyFailureStartTime = startTime;410dirtyFailureDuration = endTime - startTime;411setRenewTime(endTime);412} else {413/*414* For each successive failed dirty call, wait for a415* (binary) exponentially increasing delay before416* retrying, to avoid network congestion.417*/418int n = dirtyFailures - 2;419if (n == 0) {420/*421* Calculate the initial retry delay from the422* average time elapsed for each of the first423* two failed dirty calls. The result must be424* at least 1000ms, to prevent a tight loop.425*/426dirtyFailureDuration =427Math.max((dirtyFailureDuration +428(endTime - startTime)) >> 1, 1000);429}430long newRenewTime =431endTime + (dirtyFailureDuration << n);432433/*434* Continue if the last known held lease has not435* expired, or else at least a fixed number of times,436* or at least until we've tried for a fixed amount437* of time (the default lease value we request).438*/439if (newRenewTime < expirationTime ||440dirtyFailures < dirtyFailureRetries ||441newRenewTime < dirtyFailureStartTime + leaseValue)442{443setRenewTime(newRenewTime);444} else {445/*446* Give up: postpone lease renewals until next447* ref is registered for this endpoint.448*/449setRenewTime(Long.MAX_VALUE);450}451}452453if (refEntries != null) {454/*455* Add all of these refs to the set of refs for this456* endpoint that may be invalid (this VM may not be in457* the server's referenced set), so that we will458* attempt to explicitly dirty them again in the459* future.460*/461invalidRefs.addAll(refEntries);462463/*464* Record that a dirty call has failed for all of these465* refs, so that clean calls for them in the future466* will be strong.467*/468Iterator<RefEntry> iter = refEntries.iterator();469while (iter.hasNext()) {470RefEntry refEntry = iter.next();471refEntry.markDirtyFailed();472}473}474475/*476* If the last known held lease will have expired before477* the next renewal, all refs might be invalid.478*/479if (renewTime >= expirationTime) {480invalidRefs.addAll(refTable.values());481}482}483}484}485486/**487* Set the absolute time at which the lease for this entry should488* be renewed.489*490* This method must ONLY be called while synchronized on this entry.491*/492private void setRenewTime(long newRenewTime) {493assert Thread.holdsLock(this);494495if (newRenewTime < renewTime) {496renewTime = newRenewTime;497if (interruptible) {498AccessController.doPrivileged(499new PrivilegedAction<Void>() {500public Void run() {501renewCleanThread.interrupt();502return null;503}504});505}506} else {507renewTime = newRenewTime;508}509}510511/**512* RenewCleanThread handles the asynchronous client-side DGC activity513* for this entry: renewing the leases and making clean calls.514*/515private class RenewCleanThread implements Runnable {516517public void run() {518do {519long timeToWait;520RefEntry.PhantomLiveRef phantom = null;521boolean needRenewal = false;522Set<RefEntry> refsToDirty = null;523long sequenceNum = Long.MIN_VALUE;524525synchronized (EndpointEntry.this) {526/*527* Calculate time to block (waiting for phantom528* reference notifications). It is the time until the529* lease renewal should be done, bounded on the low530* end by 1 ms so that the reference queue will always531* get processed, and if there are pending clean532* requests (remaining because some clean calls533* failed), bounded on the high end by the maximum534* clean call retry interval.535*/536long timeUntilRenew =537renewTime - System.currentTimeMillis();538timeToWait = Math.max(timeUntilRenew, 1);539if (!pendingCleans.isEmpty()) {540timeToWait = Math.min(timeToWait, cleanInterval);541}542543/*544* Set flag indicating that it is OK to interrupt this545* thread now, such as if a earlier lease renewal time546* is set, because we are only going to be blocking547* and can deal with interrupts.548*/549interruptible = true;550}551552try {553/*554* Wait for the duration calculated above for any of555* our phantom references to be enqueued.556*/557phantom = (RefEntry.PhantomLiveRef)558refQueue.remove(timeToWait);559} catch (InterruptedException e) {560}561562synchronized (EndpointEntry.this) {563/*564* Set flag indicating that it is NOT OK to interrupt565* this thread now, because we may be undertaking I/O566* operations that should not be interrupted (and we567* will not be blocking arbitrarily).568*/569interruptible = false;570Thread.interrupted(); // clear interrupted state571572/*573* If there was a phantom reference enqueued, process574* it and all the rest on the queue, generating575* clean requests as necessary.576*/577if (phantom != null) {578processPhantomRefs(phantom);579}580581/*582* Check if it is time to renew this entry's lease.583*/584long currentTime = System.currentTimeMillis();585if (currentTime > renewTime) {586needRenewal = true;587if (!invalidRefs.isEmpty()) {588refsToDirty = invalidRefs;589invalidRefs = new HashSet<>(5);590}591sequenceNum = getNextSequenceNum();592}593}594595boolean needRenewal_ = needRenewal;596Set<RefEntry> refsToDirty_ = refsToDirty;597long sequenceNum_ = sequenceNum;598AccessController.doPrivileged((PrivilegedAction<Void>)() -> {599if (needRenewal_) {600makeDirtyCall(refsToDirty_, sequenceNum_);601}602603if (!pendingCleans.isEmpty()) {604makeCleanCalls();605}606return null;607}, SOCKET_ACC);608} while (!removed || !pendingCleans.isEmpty());609}610}611612/**613* Process the notification of the given phantom reference and any614* others that are on this entry's reference queue. Each phantom615* reference is removed from its RefEntry's ref set. All ref616* entries that have no more registered instances are collected617* into up to two batched clean call requests: one for refs618* requiring a "strong" clean call, and one for the rest.619*620* This method must ONLY be called while synchronized on this entry.621*/622private void processPhantomRefs(RefEntry.PhantomLiveRef phantom) {623assert Thread.holdsLock(this);624625Set<RefEntry> strongCleans = null;626Set<RefEntry> normalCleans = null;627628do {629RefEntry refEntry = phantom.getRefEntry();630refEntry.removeInstanceFromRefSet(phantom);631if (refEntry.isRefSetEmpty()) {632if (refEntry.hasDirtyFailed()) {633if (strongCleans == null) {634strongCleans = new HashSet<>(5);635}636strongCleans.add(refEntry);637} else {638if (normalCleans == null) {639normalCleans = new HashSet<>(5);640}641normalCleans.add(refEntry);642}643removeRefEntry(refEntry);644}645} while ((phantom =646(RefEntry.PhantomLiveRef) refQueue.poll()) != null);647648if (strongCleans != null) {649pendingCleans.add(650new CleanRequest(createObjIDArray(strongCleans),651getNextSequenceNum(), true));652}653if (normalCleans != null) {654pendingCleans.add(655new CleanRequest(createObjIDArray(normalCleans),656getNextSequenceNum(), false));657}658}659660/**661* CleanRequest holds the data for the parameters of a clean call662* that needs to be made.663*/664private static class CleanRequest {665666final ObjID[] objIDs;667final long sequenceNum;668final boolean strong;669670/** how many times this request has failed */671int failures = 0;672673CleanRequest(ObjID[] objIDs, long sequenceNum, boolean strong) {674this.objIDs = objIDs;675this.sequenceNum = sequenceNum;676this.strong = strong;677}678}679680/**681* Make all of the clean calls described by the clean requests in682* this entry's set of "pending cleans". Clean requests for clean683* calls that succeed are removed from the "pending cleans" set.684*685* This method must NOT be called while synchronized on this entry.686*/687private void makeCleanCalls() {688assert !Thread.holdsLock(this);689690Iterator<CleanRequest> iter = pendingCleans.iterator();691while (iter.hasNext()) {692CleanRequest request = iter.next();693try {694dgc.clean(request.objIDs, request.sequenceNum, vmid,695request.strong);696iter.remove();697} catch (Exception e) {698/*699* Many types of exceptions here could have been700* caused by a transient failure, so try again a701* few times, but not forever.702*/703if (++request.failures >= cleanFailureRetries) {704iter.remove();705}706}707}708}709710/**711* Create an array of ObjIDs (needed for the DGC remote calls)712* from the ids in the given set of refs.713*/714private static ObjID[] createObjIDArray(Set<RefEntry> refEntries) {715ObjID[] ids = new ObjID[refEntries.size()];716Iterator<RefEntry> iter = refEntries.iterator();717for (int i = 0; i < ids.length; i++) {718ids[i] = iter.next().getRef().getObjID();719}720return ids;721}722723/**724* RefEntry encapsulates the client-side DGC information specific725* to a particular LiveRef value. In particular, it contains a726* set of phantom references to all of the instances of the LiveRef727* value registered in the system (but not garbage collected728* locally).729*/730private class RefEntry {731732/** LiveRef value for this entry (not a registered instance) */733private LiveRef ref;734/** set of phantom references to registered instances */735private Set<PhantomLiveRef> refSet = new HashSet<>(5);736/** true if a dirty call containing this ref has failed */737private boolean dirtyFailed = false;738739public RefEntry(LiveRef ref) {740this.ref = ref;741}742743/**744* Return the LiveRef value for this entry (not a registered745* instance).746*/747public LiveRef getRef() {748return ref;749}750751/**752* Add a LiveRef to the set of registered instances for this entry.753*754* This method must ONLY be invoked while synchronized on this755* RefEntry's EndpointEntry.756*/757public void addInstanceToRefSet(LiveRef ref) {758assert Thread.holdsLock(EndpointEntry.this);759assert ref.equals(this.ref);760761/*762* Only keep a phantom reference to the registered instance,763* so that it can be garbage collected normally (and we can be764* notified when that happens).765*/766refSet.add(new PhantomLiveRef(ref));767}768769/**770* Remove a PhantomLiveRef from the set of registered instances.771*772* This method must ONLY be invoked while synchronized on this773* RefEntry's EndpointEntry.774*/775public void removeInstanceFromRefSet(PhantomLiveRef phantom) {776assert Thread.holdsLock(EndpointEntry.this);777assert refSet.contains(phantom);778refSet.remove(phantom);779}780781/**782* Return true if there are no registered LiveRef instances for783* this entry still reachable in this VM.784*785* This method must ONLY be invoked while synchronized on this786* RefEntry's EndpointEntry.787*/788public boolean isRefSetEmpty() {789assert Thread.holdsLock(EndpointEntry.this);790return refSet.size() == 0;791}792793/**794* Record that a dirty call that explicitly contained this795* entry's ref has failed.796*797* This method must ONLY be invoked while synchronized on this798* RefEntry's EndpointEntry.799*/800public void markDirtyFailed() {801assert Thread.holdsLock(EndpointEntry.this);802dirtyFailed = true;803}804805/**806* Return true if a dirty call that explicitly contained this807* entry's ref has failed (and therefore a clean call for this808* ref needs to be marked "strong").809*810* This method must ONLY be invoked while synchronized on this811* RefEntry's EndpointEntry.812*/813public boolean hasDirtyFailed() {814assert Thread.holdsLock(EndpointEntry.this);815return dirtyFailed;816}817818/**819* PhantomLiveRef is a PhantomReference to a LiveRef instance,820* used to detect when the LiveRef becomes permanently821* unreachable in this VM.822*/823private class PhantomLiveRef extends PhantomReference<LiveRef> {824825public PhantomLiveRef(LiveRef ref) {826super(ref, EndpointEntry.this.refQueue);827}828829public RefEntry getRefEntry() {830return RefEntry.this;831}832}833}834}835}836837838