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