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