1 /*
   2  * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package sun.rmi.transport;
  26 
  27 import java.lang.ref.PhantomReference;
  28 import java.lang.ref.ReferenceQueue;
  29 import java.net.SocketPermission;
  30 import java.security.AccessController;
  31 import java.security.PrivilegedAction;
  32 import java.util.HashMap;
  33 import java.util.HashSet;
  34 import java.util.Iterator;
  35 import java.util.List;
  36 import java.util.Map;
  37 import java.util.Set;
  38 import java.rmi.ConnectException;
  39 import java.rmi.RemoteException;
  40 import java.rmi.dgc.DGC;
  41 import java.rmi.dgc.Lease;
  42 import java.rmi.dgc.VMID;
  43 import java.rmi.server.ObjID;
  44 import sun.misc.GC;
  45 import sun.rmi.runtime.NewThreadAction;
  46 import sun.rmi.server.UnicastRef;
  47 import sun.rmi.server.Util;
  48 
  49 import java.security.AccessControlContext;
  50 import java.security.Permissions;
  51 import java.security.ProtectionDomain;
  52 
  53 /**
  54  * DGCClient implements the client-side of the RMI distributed garbage
  55  * collection system.
  56  *
  57  * The external interface to DGCClient is the "registerRefs" method.
  58  * When a LiveRef to a remote object enters the VM, it needs to be
  59  * registered with the DGCClient to participate in distributed garbage
  60  * collection.
  61  *
  62  * When the first LiveRef to a particular remote object is registered,
  63  * a "dirty" call is made to the server-side distributed garbage
  64  * collector for the remote object, which returns a lease guaranteeing
  65  * that the server-side DGC will not collect the remote object for a
  66  * certain period of time.  While LiveRef instances to remote objects
  67  * on a particular server exist, the DGCClient periodically sends more
  68  * "dirty" calls to renew its lease.
  69  *
  70  * The DGCClient tracks the local reachability of registered LiveRef
  71  * instances (using phantom references).  When the LiveRef instance
  72  * for a particular remote object becomes garbage collected locally,
  73  * a "clean" call is made to the server-side distributed garbage
  74  * collector, indicating that the server no longer needs to keep the
  75  * remote object alive for this client.
  76  *
  77  * @see java.rmi.dgc.DGC, sun.rmi.transport.DGCImpl
  78  *
  79  * @author  Ann Wollrath
  80  * @author  Peter Jones
  81  */
  82 final class DGCClient {
  83 
  84     /** next sequence number for DGC calls (access synchronized on class) */
  85     private static long nextSequenceNum = Long.MIN_VALUE;
  86 
  87     /** unique identifier for this VM as a client of DGC */
  88     private static VMID vmid = new VMID();
  89 
  90     /** lease duration to request (usually ignored by server) */
  91     private static final long leaseValue =              // default 10 minutes
  92         AccessController.doPrivileged((PrivilegedAction<Long>) () ->
  93             Long.getLong("java.rmi.dgc.leaseValue", 600000));
  94 
  95     /** maximum interval between retries of failed clean calls */
  96     private static final long cleanInterval =           // default 3 minutes
  97         AccessController.doPrivileged((PrivilegedAction<Long>) () ->
  98             Long.getLong("sun.rmi.dgc.cleanInterval", 180000));
  99 
 100     /** maximum interval between complete garbage collections of local heap */
 101     private static final long gcInterval =              // default 1 hour
 102         AccessController.doPrivileged((PrivilegedAction<Long>) () ->
 103             Long.getLong("sun.rmi.dgc.client.gcInterval", 3600000));
 104 
 105     /** minimum retry count for dirty calls that fail */
 106     private static final int dirtyFailureRetries = 5;
 107 
 108     /** retry count for clean calls that fail with ConnectException */
 109     private static final int cleanFailureRetries = 5;
 110 
 111     /** constant empty ObjID array for lease renewal optimization */
 112     private static final ObjID[] emptyObjIDArray = new ObjID[0];
 113 
 114     /** ObjID for server-side DGC object */
 115     private static final ObjID dgcID = new ObjID(ObjID.DGC_ID);
 116 
 117     /**
 118      * An AccessControlContext with only socket permissions,
 119      * suitable for an RMIClientSocketFactory.
 120      */
 121     private static final AccessControlContext SOCKET_ACC;
 122     static {
 123         Permissions perms = new Permissions();
 124         perms.add(new SocketPermission("*", "connect,resolve"));
 125         ProtectionDomain[] pd = { new ProtectionDomain(null, perms) };
 126         SOCKET_ACC = new AccessControlContext(pd);
 127     }
 128 
 129     /*
 130      * Disallow anyone from creating one of these.
 131      */
 132     private DGCClient() {}
 133 
 134     /**
 135      * Register the LiveRef instances in the supplied list to participate
 136      * in distributed garbage collection.
 137      *
 138      * All of the LiveRefs in the list must be for remote objects at the
 139      * given endpoint.
 140      */
 141     static void registerRefs(Endpoint ep, List<LiveRef> refs) {
 142         /*
 143          * Look up the given endpoint and register the refs with it.
 144          * The retrieved entry may get removed from the global endpoint
 145          * table before EndpointEntry.registerRefs() is able to acquire
 146          * its lock; in this event, it returns false, and we loop and
 147          * try again.
 148          */
 149         EndpointEntry epEntry;
 150         do {
 151             epEntry = EndpointEntry.lookup(ep);
 152         } while (!epEntry.registerRefs(refs));
 153     }
 154 
 155     /**
 156      * Get the next sequence number to be used for a dirty or clean
 157      * operation from this VM.  This method should only be called while
 158      * synchronized on the EndpointEntry whose data structures the
 159      * operation affects.
 160      */
 161     private static synchronized long getNextSequenceNum() {
 162         return nextSequenceNum++;
 163     }
 164 
 165     /**
 166      * Given the length of a lease and the time that it was granted,
 167      * compute the absolute time at which it should be renewed, giving
 168      * room for reasonable computational and communication delays.
 169      */
 170     private static long computeRenewTime(long grantTime, long duration) {
 171         /*
 172          * REMIND: This algorithm should be more sophisticated, waiting
 173          * a longer fraction of the lease duration for longer leases.
 174          */
 175         return grantTime + (duration / 2);
 176     }
 177 
 178     /**
 179      * EndpointEntry encapsulates the client-side DGC information specific
 180      * to a particular Endpoint.  Of most significance is the table that
 181      * maps LiveRef value to RefEntry objects and the renew/clean thread
 182      * that handles asynchronous client-side DGC operations.
 183      */
 184     private static class EndpointEntry {
 185 
 186         /** the endpoint that this entry is for */
 187         private Endpoint endpoint;
 188         /** synthesized reference to the remote server-side DGC */
 189         private DGC dgc;
 190 
 191         /** table of refs held for endpoint: maps LiveRef to RefEntry */
 192         private Map<LiveRef, RefEntry> refTable = new HashMap<>(5);
 193         /** set of RefEntry instances from last (failed) dirty call */
 194         private Set<RefEntry> invalidRefs = new HashSet<>(5);
 195 
 196         /** true if this entry has been removed from the global table */
 197         private boolean removed = false;
 198 
 199         /** absolute time to renew current lease to this endpoint */
 200         private long renewTime = Long.MAX_VALUE;
 201         /** absolute time current lease to this endpoint will expire */
 202         private long expirationTime = Long.MIN_VALUE;
 203         /** count of recent dirty calls that have failed */
 204         private int dirtyFailures = 0;
 205         /** absolute time of first recent failed dirty call */
 206         private long dirtyFailureStartTime;
 207         /** (average) elapsed time for recent failed dirty calls */
 208         private long dirtyFailureDuration;
 209 
 210         /** renew/clean thread for handling lease renewals and clean calls */
 211         private Thread renewCleanThread;
 212         /** true if renew/clean thread may be interrupted */
 213         private boolean interruptible = false;
 214 
 215         /** reference queue for phantom references */
 216         private ReferenceQueue<LiveRef> refQueue = new ReferenceQueue<>();
 217         /** set of clean calls that need to be made */
 218         private Set<CleanRequest> pendingCleans = new HashSet<>(5);
 219 
 220         /** global endpoint table: maps Endpoint to EndpointEntry */
 221         private static Map<Endpoint,EndpointEntry> endpointTable = new HashMap<>(5);
 222         /** handle for GC latency request (for future cancellation) */
 223         private static GC.LatencyRequest gcLatencyRequest = null;
 224 
 225         /**
 226          * Look up the EndpointEntry for the given Endpoint.  An entry is
 227          * created if one does not already exist.
 228          */
 229         public static EndpointEntry lookup(Endpoint ep) {
 230             synchronized (endpointTable) {
 231                 EndpointEntry entry = endpointTable.get(ep);
 232                 if (entry == null) {
 233                     entry = new EndpointEntry(ep);
 234                     endpointTable.put(ep, entry);
 235                     /*
 236                      * While we are tracking live remote references registered
 237                      * in this VM, request a maximum latency for inspecting the
 238                      * entire heap from the local garbage collector, to place
 239                      * an upper bound on the time to discover remote references
 240                      * that have become unreachable (see bugid 4171278).
 241                      */
 242                     if (gcLatencyRequest == null) {
 243                         gcLatencyRequest = GC.requestLatency(gcInterval);
 244                     }
 245                 }
 246                 return entry;
 247             }
 248         }
 249 
 250         private EndpointEntry(final Endpoint endpoint) {
 251             this.endpoint = endpoint;
 252             try {
 253                 LiveRef dgcRef = new LiveRef(dgcID, endpoint, false);
 254                 dgc = (DGC) Util.createProxy(DGCImpl.class,
 255                                              new UnicastRef(dgcRef), true);
 256             } catch (RemoteException e) {
 257                 throw new Error("internal error creating DGC stub");
 258             }
 259             renewCleanThread =  AccessController.doPrivileged(
 260                 new NewThreadAction(new RenewCleanThread(),
 261                                     "RenewClean-" + endpoint, true));
 262             renewCleanThread.start();
 263         }
 264 
 265         /**
 266          * Register the LiveRef instances in the supplied list to participate
 267          * in distributed garbage collection.
 268          *
 269          * This method returns false if this entry was removed from the
 270          * global endpoint table (because it was empty) before these refs
 271          * could be registered.  In that case, a new EndpointEntry needs
 272          * to be looked up.
 273          *
 274          * This method must NOT be called while synchronized on this entry.
 275          */
 276         public boolean registerRefs(List<LiveRef> refs) {
 277             assert !Thread.holdsLock(this);
 278 
 279             Set<RefEntry> refsToDirty = null;     // entries for refs needing dirty
 280             long sequenceNum;           // sequence number for dirty call
 281 
 282             synchronized (this) {
 283                 if (removed) {
 284                     return false;
 285                 }
 286 
 287                 Iterator<LiveRef> iter = refs.iterator();
 288                 while (iter.hasNext()) {
 289                     LiveRef ref = iter.next();
 290                     assert ref.getEndpoint().equals(endpoint);
 291 
 292                     RefEntry refEntry = refTable.get(ref);
 293                     if (refEntry == null) {
 294                         LiveRef refClone = (LiveRef) ref.clone();
 295                         refEntry = new RefEntry(refClone);
 296                         refTable.put(refClone, refEntry);
 297                         if (refsToDirty == null) {
 298                             refsToDirty = new HashSet<>(5);
 299                         }
 300                         refsToDirty.add(refEntry);
 301                     }
 302 
 303                     refEntry.addInstanceToRefSet(ref);
 304                 }
 305 
 306                 if (refsToDirty == null) {
 307                     return true;
 308                 }
 309 
 310                 refsToDirty.addAll(invalidRefs);
 311                 invalidRefs.clear();
 312 
 313                 sequenceNum = getNextSequenceNum();
 314             }
 315 
 316             makeDirtyCall(refsToDirty, sequenceNum);
 317             return true;
 318         }
 319 
 320         /**
 321          * Remove the given RefEntry from the ref table.  If that makes
 322          * the ref table empty, remove this entry from the global endpoint
 323          * table.
 324          *
 325          * This method must ONLY be called while synchronized on this entry.
 326          */
 327         private void removeRefEntry(RefEntry refEntry) {
 328             assert Thread.holdsLock(this);
 329             assert !removed;
 330             assert refTable.containsKey(refEntry.getRef());
 331 
 332             refTable.remove(refEntry.getRef());
 333             invalidRefs.remove(refEntry);
 334             if (refTable.isEmpty()) {
 335                 synchronized (endpointTable) {
 336                     endpointTable.remove(endpoint);
 337                     Transport transport = endpoint.getOutboundTransport();
 338                     transport.free(endpoint);
 339                     /*
 340                      * If there are no longer any live remote references
 341                      * registered, we are no longer concerned with the
 342                      * latency of local garbage collection here.
 343                      */
 344                     if (endpointTable.isEmpty()) {
 345                         assert gcLatencyRequest != null;
 346                         gcLatencyRequest.cancel();
 347                         gcLatencyRequest = null;
 348                     }
 349                     removed = true;
 350                 }
 351             }
 352         }
 353 
 354         /**
 355          * Make a DGC dirty call to this entry's endpoint, for the ObjIDs
 356          * corresponding to the given set of refs and with the given
 357          * sequence number.
 358          *
 359          * This method must NOT be called while synchronized on this entry.
 360          */
 361         private void makeDirtyCall(Set<RefEntry> refEntries, long sequenceNum) {
 362             assert !Thread.holdsLock(this);
 363 
 364             ObjID[] ids;
 365             if (refEntries != null) {
 366                 ids = createObjIDArray(refEntries);
 367             } else {
 368                 ids = emptyObjIDArray;
 369             }
 370 
 371             long startTime = System.currentTimeMillis();
 372             try {
 373                 Lease lease =
 374                     dgc.dirty(ids, sequenceNum, new Lease(vmid, leaseValue));
 375                 long duration = lease.getValue();
 376 
 377                 long newRenewTime = computeRenewTime(startTime, duration);
 378                 long newExpirationTime = startTime + duration;
 379 
 380                 synchronized (this) {
 381                     dirtyFailures = 0;
 382                     setRenewTime(newRenewTime);
 383                     expirationTime = newExpirationTime;
 384                 }
 385 
 386             } catch (Exception e) {
 387                 long endTime = System.currentTimeMillis();
 388 
 389                 synchronized (this) {
 390                     dirtyFailures++;
 391 
 392                     if (dirtyFailures == 1) {
 393                         /*
 394                          * If this was the first recent failed dirty call,
 395                          * reschedule another one immediately, in case there
 396                          * was just a transient network problem, and remember
 397                          * the start time and duration of this attempt for
 398                          * future calculations of the delays between retries.
 399                          */
 400                         dirtyFailureStartTime = startTime;
 401                         dirtyFailureDuration = endTime - startTime;
 402                         setRenewTime(endTime);
 403                     } else {
 404                         /*
 405                          * For each successive failed dirty call, wait for a
 406                          * (binary) exponentially increasing delay before
 407                          * retrying, to avoid network congestion.
 408                          */
 409                         int n = dirtyFailures - 2;
 410                         if (n == 0) {
 411                             /*
 412                              * Calculate the initial retry delay from the
 413                              * average time elapsed for each of the first
 414                              * two failed dirty calls.  The result must be
 415                              * at least 1000ms, to prevent a tight loop.
 416                              */
 417                             dirtyFailureDuration =
 418                                 Math.max((dirtyFailureDuration +
 419                                           (endTime - startTime)) >> 1, 1000);
 420                         }
 421                         long newRenewTime =
 422                             endTime + (dirtyFailureDuration << n);
 423 
 424                         /*
 425                          * Continue if the last known held lease has not
 426                          * expired, or else at least a fixed number of times,
 427                          * or at least until we've tried for a fixed amount
 428                          * of time (the default lease value we request).
 429                          */
 430                         if (newRenewTime < expirationTime ||
 431                             dirtyFailures < dirtyFailureRetries ||
 432                             newRenewTime < dirtyFailureStartTime + leaseValue)
 433                         {
 434                             setRenewTime(newRenewTime);
 435                         } else {
 436                             /*
 437                              * Give up: postpone lease renewals until next
 438                              * ref is registered for this endpoint.
 439                              */
 440                             setRenewTime(Long.MAX_VALUE);
 441                         }
 442                     }
 443 
 444                     if (refEntries != null) {
 445                         /*
 446                          * Add all of these refs to the set of refs for this
 447                          * endpoint that may be invalid (this VM may not be in
 448                          * the server's referenced set), so that we will
 449                          * attempt to explicitly dirty them again in the
 450                          * future.
 451                          */
 452                         invalidRefs.addAll(refEntries);
 453 
 454                         /*
 455                          * Record that a dirty call has failed for all of these
 456                          * refs, so that clean calls for them in the future
 457                          * will be strong.
 458                          */
 459                         Iterator<RefEntry> iter = refEntries.iterator();
 460                         while (iter.hasNext()) {
 461                             RefEntry refEntry = iter.next();
 462                             refEntry.markDirtyFailed();
 463                         }
 464                     }
 465 
 466                     /*
 467                      * If the last known held lease will have expired before
 468                      * the next renewal, all refs might be invalid.
 469                      */
 470                     if (renewTime >= expirationTime) {
 471                         invalidRefs.addAll(refTable.values());
 472                     }
 473                 }
 474             }
 475         }
 476 
 477         /**
 478          * Set the absolute time at which the lease for this entry should
 479          * be renewed.
 480          *
 481          * This method must ONLY be called while synchronized on this entry.
 482          */
 483         private void setRenewTime(long newRenewTime) {
 484             assert Thread.holdsLock(this);
 485 
 486             if (newRenewTime < renewTime) {
 487                 renewTime = newRenewTime;
 488                 if (interruptible) {
 489                     AccessController.doPrivileged(
 490                         new PrivilegedAction<Void>() {
 491                             public Void run() {
 492                             renewCleanThread.interrupt();
 493                             return null;
 494                         }
 495                     });
 496                 }
 497             } else {
 498                 renewTime = newRenewTime;
 499             }
 500         }
 501 
 502         /**
 503          * RenewCleanThread handles the asynchronous client-side DGC activity
 504          * for this entry: renewing the leases and making clean calls.
 505          */
 506         private class RenewCleanThread implements Runnable {
 507 
 508             public void run() {
 509                 do {
 510                     long timeToWait;
 511                     RefEntry.PhantomLiveRef phantom = null;
 512                     boolean needRenewal = false;
 513                     Set<RefEntry> refsToDirty = null;
 514                     long sequenceNum = Long.MIN_VALUE;
 515 
 516                     synchronized (EndpointEntry.this) {
 517                         /*
 518                          * Calculate time to block (waiting for phantom
 519                          * reference notifications).  It is the time until the
 520                          * lease renewal should be done, bounded on the low
 521                          * end by 1 ms so that the reference queue will always
 522                          * get processed, and if there are pending clean
 523                          * requests (remaining because some clean calls
 524                          * failed), bounded on the high end by the maximum
 525                          * clean call retry interval.
 526                          */
 527                         long timeUntilRenew =
 528                             renewTime - System.currentTimeMillis();
 529                         timeToWait = Math.max(timeUntilRenew, 1);
 530                         if (!pendingCleans.isEmpty()) {
 531                             timeToWait = Math.min(timeToWait, cleanInterval);
 532                         }
 533 
 534                         /*
 535                          * Set flag indicating that it is OK to interrupt this
 536                          * thread now, such as if a earlier lease renewal time
 537                          * is set, because we are only going to be blocking
 538                          * and can deal with interrupts.
 539                          */
 540                         interruptible = true;
 541                     }
 542 
 543                     try {
 544                         /*
 545                          * Wait for the duration calculated above for any of
 546                          * our phantom references to be enqueued.
 547                          */
 548                         phantom = (RefEntry.PhantomLiveRef)
 549                             refQueue.remove(timeToWait);
 550                     } catch (InterruptedException e) {
 551                     }
 552 
 553                     synchronized (EndpointEntry.this) {
 554                         /*
 555                          * Set flag indicating that it is NOT OK to interrupt
 556                          * this thread now, because we may be undertaking I/O
 557                          * operations that should not be interrupted (and we
 558                          * will not be blocking arbitrarily).
 559                          */
 560                         interruptible = false;
 561                         Thread.interrupted();   // clear interrupted state
 562 
 563                         /*
 564                          * If there was a phantom reference enqueued, process
 565                          * it and all the rest on the queue, generating
 566                          * clean requests as necessary.
 567                          */
 568                         if (phantom != null) {
 569                             processPhantomRefs(phantom);
 570                         }
 571 
 572                         /*
 573                          * Check if it is time to renew this entry's lease.
 574                          */
 575                         long currentTime = System.currentTimeMillis();
 576                         if (currentTime > renewTime) {
 577                             needRenewal = true;
 578                             if (!invalidRefs.isEmpty()) {
 579                                 refsToDirty = invalidRefs;
 580                                 invalidRefs = new HashSet<>(5);
 581                             }
 582                             sequenceNum = getNextSequenceNum();
 583                         }
 584                     }
 585 
 586                     boolean needRenewal_ = needRenewal;
 587                     Set<RefEntry> refsToDirty_ = refsToDirty;
 588                     long sequenceNum_ = sequenceNum;
 589                     AccessController.doPrivileged((PrivilegedAction<Void>)() -> {
 590                         if (needRenewal_) {
 591                             makeDirtyCall(refsToDirty_, sequenceNum_);
 592                         }
 593 
 594                         if (!pendingCleans.isEmpty()) {
 595                             makeCleanCalls();
 596                         }
 597                         return null;
 598                     }, SOCKET_ACC);
 599                 } while (!removed || !pendingCleans.isEmpty());
 600             }
 601         }
 602 
 603         /**
 604          * Process the notification of the given phantom reference and any
 605          * others that are on this entry's reference queue.  Each phantom
 606          * reference is removed from its RefEntry's ref set.  All ref
 607          * entries that have no more registered instances are collected
 608          * into up to two batched clean call requests: one for refs
 609          * requiring a "strong" clean call, and one for the rest.
 610          *
 611          * This method must ONLY be called while synchronized on this entry.
 612          */
 613         private void processPhantomRefs(RefEntry.PhantomLiveRef phantom) {
 614             assert Thread.holdsLock(this);
 615 
 616             Set<RefEntry> strongCleans = null;
 617             Set<RefEntry> normalCleans = null;
 618 
 619             do {
 620                 RefEntry refEntry = phantom.getRefEntry();
 621                 refEntry.removeInstanceFromRefSet(phantom);
 622                 if (refEntry.isRefSetEmpty()) {
 623                     if (refEntry.hasDirtyFailed()) {
 624                         if (strongCleans == null) {
 625                             strongCleans = new HashSet<>(5);
 626                         }
 627                         strongCleans.add(refEntry);
 628                     } else {
 629                         if (normalCleans == null) {
 630                             normalCleans = new HashSet<>(5);
 631                         }
 632                         normalCleans.add(refEntry);
 633                     }
 634                     removeRefEntry(refEntry);
 635                 }
 636             } while ((phantom =
 637                 (RefEntry.PhantomLiveRef) refQueue.poll()) != null);
 638 
 639             if (strongCleans != null) {
 640                 pendingCleans.add(
 641                     new CleanRequest(createObjIDArray(strongCleans),
 642                                      getNextSequenceNum(), true));
 643             }
 644             if (normalCleans != null) {
 645                 pendingCleans.add(
 646                     new CleanRequest(createObjIDArray(normalCleans),
 647                                      getNextSequenceNum(), false));
 648             }
 649         }
 650 
 651         /**
 652          * CleanRequest holds the data for the parameters of a clean call
 653          * that needs to be made.
 654          */
 655         private static class CleanRequest {
 656 
 657             final ObjID[] objIDs;
 658             final long sequenceNum;
 659             final boolean strong;
 660 
 661             /** how many times this request has failed */
 662             int failures = 0;
 663 
 664             CleanRequest(ObjID[] objIDs, long sequenceNum, boolean strong) {
 665                 this.objIDs = objIDs;
 666                 this.sequenceNum = sequenceNum;
 667                 this.strong = strong;
 668             }
 669         }
 670 
 671         /**
 672          * Make all of the clean calls described by the clean requests in
 673          * this entry's set of "pending cleans".  Clean requests for clean
 674          * calls that succeed are removed from the "pending cleans" set.
 675          *
 676          * This method must NOT be called while synchronized on this entry.
 677          */
 678         private void makeCleanCalls() {
 679             assert !Thread.holdsLock(this);
 680 
 681             Iterator<CleanRequest> iter = pendingCleans.iterator();
 682             while (iter.hasNext()) {
 683                 CleanRequest request = iter.next();
 684                 try {
 685                     dgc.clean(request.objIDs, request.sequenceNum, vmid,
 686                               request.strong);
 687                     iter.remove();
 688                 } catch (Exception e) {
 689                     /*
 690                      * Many types of exceptions here could have been
 691                      * caused by a transient failure, so try again a
 692                      * few times, but not forever.
 693                      */
 694                     if (++request.failures >= cleanFailureRetries) {
 695                         iter.remove();
 696                     }
 697                 }
 698             }
 699         }
 700 
 701         /**
 702          * Create an array of ObjIDs (needed for the DGC remote calls)
 703          * from the ids in the given set of refs.
 704          */
 705         private static ObjID[] createObjIDArray(Set<RefEntry> refEntries) {
 706             ObjID[] ids = new ObjID[refEntries.size()];
 707             Iterator<RefEntry> iter = refEntries.iterator();
 708             for (int i = 0; i < ids.length; i++) {
 709                 ids[i] = iter.next().getRef().getObjID();
 710             }
 711             return ids;
 712         }
 713 
 714         /**
 715          * RefEntry encapsulates the client-side DGC information specific
 716          * to a particular LiveRef value.  In particular, it contains a
 717          * set of phantom references to all of the instances of the LiveRef
 718          * value registered in the system (but not garbage collected
 719          * locally).
 720          */
 721         private class RefEntry {
 722 
 723             /** LiveRef value for this entry (not a registered instance) */
 724             private LiveRef ref;
 725             /** set of phantom references to registered instances */
 726             private Set<PhantomLiveRef> refSet = new HashSet<>(5);
 727             /** true if a dirty call containing this ref has failed */
 728             private boolean dirtyFailed = false;
 729 
 730             public RefEntry(LiveRef ref) {
 731                 this.ref = ref;
 732             }
 733 
 734             /**
 735              * Return the LiveRef value for this entry (not a registered
 736              * instance).
 737              */
 738             public LiveRef getRef() {
 739                 return ref;
 740             }
 741 
 742             /**
 743              * Add a LiveRef to the set of registered instances for this entry.
 744              *
 745              * This method must ONLY be invoked while synchronized on this
 746              * RefEntry's EndpointEntry.
 747              */
 748             public void addInstanceToRefSet(LiveRef ref) {
 749                 assert Thread.holdsLock(EndpointEntry.this);
 750                 assert ref.equals(this.ref);
 751 
 752                 /*
 753                  * Only keep a phantom reference to the registered instance,
 754                  * so that it can be garbage collected normally (and we can be
 755                  * notified when that happens).
 756                  */
 757                 refSet.add(new PhantomLiveRef(ref));
 758             }
 759 
 760             /**
 761              * Remove a PhantomLiveRef from the set of registered instances.
 762              *
 763              * This method must ONLY be invoked while synchronized on this
 764              * RefEntry's EndpointEntry.
 765              */
 766             public void removeInstanceFromRefSet(PhantomLiveRef phantom) {
 767                 assert Thread.holdsLock(EndpointEntry.this);
 768                 assert refSet.contains(phantom);
 769                 refSet.remove(phantom);
 770             }
 771 
 772             /**
 773              * Return true if there are no registered LiveRef instances for
 774              * this entry still reachable in this VM.
 775              *
 776              * This method must ONLY be invoked while synchronized on this
 777              * RefEntry's EndpointEntry.
 778              */
 779             public boolean isRefSetEmpty() {
 780                 assert Thread.holdsLock(EndpointEntry.this);
 781                 return refSet.size() == 0;
 782             }
 783 
 784             /**
 785              * Record that a dirty call that explicitly contained this
 786              * entry's ref has failed.
 787              *
 788              * This method must ONLY be invoked while synchronized on this
 789              * RefEntry's EndpointEntry.
 790              */
 791             public void markDirtyFailed() {
 792                 assert Thread.holdsLock(EndpointEntry.this);
 793                 dirtyFailed = true;
 794             }
 795 
 796             /**
 797              * Return true if a dirty call that explicitly contained this
 798              * entry's ref has failed (and therefore a clean call for this
 799              * ref needs to be marked "strong").
 800              *
 801              * This method must ONLY be invoked while synchronized on this
 802              * RefEntry's EndpointEntry.
 803              */
 804             public boolean hasDirtyFailed() {
 805                 assert Thread.holdsLock(EndpointEntry.this);
 806                 return dirtyFailed;
 807             }
 808 
 809             /**
 810              * PhantomLiveRef is a PhantomReference to a LiveRef instance,
 811              * used to detect when the LiveRef becomes permanently
 812              * unreachable in this VM.
 813              */
 814             private class PhantomLiveRef extends PhantomReference<LiveRef> {
 815 
 816                 public PhantomLiveRef(LiveRef ref) {
 817                     super(ref, EndpointEntry.this.refQueue);
 818                 }
 819 
 820                 public RefEntry getRefEntry() {
 821                     return RefEntry.this;
 822                 }
 823             }
 824         }
 825     }
 826 }