108 private static final int cleanFailureRetries = 5;
109
110 /** constant empty ObjID array for lease renewal optimization */
111 private static final ObjID[] emptyObjIDArray = new ObjID[0];
112
113 /** ObjID for server-side DGC object */
114 private static final ObjID dgcID = new ObjID(ObjID.DGC_ID);
115
116 /*
117 * Disallow anyone from creating one of these.
118 */
119 private DGCClient() {}
120
121 /**
122 * Register the LiveRef instances in the supplied list to participate
123 * in distributed garbage collection.
124 *
125 * All of the LiveRefs in the list must be for remote objects at the
126 * given endpoint.
127 */
128 static void registerRefs(Endpoint ep, List refs) {
129 /*
130 * Look up the given endpoint and register the refs with it.
131 * The retrieved entry may get removed from the global endpoint
132 * table before EndpointEntry.registerRefs() is able to acquire
133 * its lock; in this event, it returns false, and we loop and
134 * try again.
135 */
136 EndpointEntry epEntry;
137 do {
138 epEntry = EndpointEntry.lookup(ep);
139 } while (!epEntry.registerRefs(refs));
140 }
141
142 /**
143 * Get the next sequence number to be used for a dirty or clean
144 * operation from this VM. This method should only be called while
145 * synchronized on the EndpointEntry whose data structures the
146 * operation affects.
147 */
148 private static synchronized long getNextSequenceNum() {
159 * REMIND: This algorithm should be more sophisticated, waiting
160 * a longer fraction of the lease duration for longer leases.
161 */
162 return grantTime + (duration / 2);
163 }
164
165 /**
166 * EndpointEntry encapsulates the client-side DGC information specific
167 * to a particular Endpoint. Of most significance is the table that
168 * maps LiveRef value to RefEntry objects and the renew/clean thread
169 * that handles asynchronous client-side DGC operations.
170 */
171 private static class EndpointEntry {
172
173 /** the endpoint that this entry is for */
174 private Endpoint endpoint;
175 /** synthesized reference to the remote server-side DGC */
176 private DGC dgc;
177
178 /** table of refs held for endpoint: maps LiveRef to RefEntry */
179 private Map refTable = new HashMap(5);
180 /** set of RefEntry instances from last (failed) dirty call */
181 private Set invalidRefs = new HashSet(5);
182
183 /** true if this entry has been removed from the global table */
184 private boolean removed = false;
185
186 /** absolute time to renew current lease to this endpoint */
187 private long renewTime = Long.MAX_VALUE;
188 /** absolute time current lease to this endpoint will expire */
189 private long expirationTime = Long.MIN_VALUE;
190 /** count of recent dirty calls that have failed */
191 private int dirtyFailures = 0;
192 /** absolute time of first recent failed dirty call */
193 private long dirtyFailureStartTime;
194 /** (average) elapsed time for recent failed dirty calls */
195 private long dirtyFailureDuration;
196
197 /** renew/clean thread for handling lease renewals and clean calls */
198 private Thread renewCleanThread;
199 /** true if renew/clean thread may be interrupted */
200 private boolean interruptible = false;
201
202 /** reference queue for phantom references */
203 private ReferenceQueue refQueue = new ReferenceQueue();
204 /** set of clean calls that need to be made */
205 private Set pendingCleans = new HashSet(5);
206
207 /** global endpoint table: maps Endpoint to EndpointEntry */
208 private static Map endpointTable = new HashMap(5);
209 /** handle for GC latency request (for future cancellation) */
210 private static GC.LatencyRequest gcLatencyRequest = null;
211
212 /**
213 * Look up the EndpointEntry for the given Endpoint. An entry is
214 * created if one does not already exist.
215 */
216 public static EndpointEntry lookup(Endpoint ep) {
217 synchronized (endpointTable) {
218 EndpointEntry entry = (EndpointEntry) endpointTable.get(ep);
219 if (entry == null) {
220 entry = new EndpointEntry(ep);
221 endpointTable.put(ep, entry);
222 /*
223 * While we are tracking live remote references registered
224 * in this VM, request a maximum latency for inspecting the
225 * entire heap from the local garbage collector, to place
226 * an upper bound on the time to discover remote references
227 * that have become unreachable (see bugid 4171278).
228 */
229 if (gcLatencyRequest == null) {
230 gcLatencyRequest = GC.requestLatency(gcInterval);
231 }
232 }
233 return entry;
234 }
235 }
236
237 private EndpointEntry(final Endpoint endpoint) {
238 this.endpoint = endpoint;
243 } catch (RemoteException e) {
244 throw new Error("internal error creating DGC stub");
245 }
246 renewCleanThread = AccessController.doPrivileged(
247 new NewThreadAction(new RenewCleanThread(),
248 "RenewClean-" + endpoint, true));
249 renewCleanThread.start();
250 }
251
252 /**
253 * Register the LiveRef instances in the supplied list to participate
254 * in distributed garbage collection.
255 *
256 * This method returns false if this entry was removed from the
257 * global endpoint table (because it was empty) before these refs
258 * could be registered. In that case, a new EndpointEntry needs
259 * to be looked up.
260 *
261 * This method must NOT be called while synchronized on this entry.
262 */
263 public boolean registerRefs(List refs) {
264 assert !Thread.holdsLock(this);
265
266 Set refsToDirty = null; // entries for refs needing dirty
267 long sequenceNum; // sequence number for dirty call
268
269 synchronized (this) {
270 if (removed) {
271 return false;
272 }
273
274 Iterator iter = refs.iterator();
275 while (iter.hasNext()) {
276 LiveRef ref = (LiveRef) iter.next();
277 assert ref.getEndpoint().equals(endpoint);
278
279 RefEntry refEntry = (RefEntry) refTable.get(ref);
280 if (refEntry == null) {
281 LiveRef refClone = (LiveRef) ref.clone();
282 refEntry = new RefEntry(refClone);
283 refTable.put(refClone, refEntry);
284 if (refsToDirty == null) {
285 refsToDirty = new HashSet(5);
286 }
287 refsToDirty.add(refEntry);
288 }
289
290 refEntry.addInstanceToRefSet(ref);
291 }
292
293 if (refsToDirty == null) {
294 return true;
295 }
296
297 refsToDirty.addAll(invalidRefs);
298 invalidRefs.clear();
299
300 sequenceNum = getNextSequenceNum();
301 }
302
303 makeDirtyCall(refsToDirty, sequenceNum);
304 return true;
305 }
328 * registered, we are no longer concerned with the
329 * latency of local garbage collection here.
330 */
331 if (endpointTable.isEmpty()) {
332 assert gcLatencyRequest != null;
333 gcLatencyRequest.cancel();
334 gcLatencyRequest = null;
335 }
336 removed = true;
337 }
338 }
339 }
340
341 /**
342 * Make a DGC dirty call to this entry's endpoint, for the ObjIDs
343 * corresponding to the given set of refs and with the given
344 * sequence number.
345 *
346 * This method must NOT be called while synchronized on this entry.
347 */
348 private void makeDirtyCall(Set refEntries, long sequenceNum) {
349 assert !Thread.holdsLock(this);
350
351 ObjID[] ids;
352 if (refEntries != null) {
353 ids = createObjIDArray(refEntries);
354 } else {
355 ids = emptyObjIDArray;
356 }
357
358 long startTime = System.currentTimeMillis();
359 try {
360 Lease lease =
361 dgc.dirty(ids, sequenceNum, new Lease(vmid, leaseValue));
362 long duration = lease.getValue();
363
364 long newRenewTime = computeRenewTime(startTime, duration);
365 long newExpirationTime = startTime + duration;
366
367 synchronized (this) {
368 dirtyFailures = 0;
426 */
427 setRenewTime(Long.MAX_VALUE);
428 }
429 }
430
431 if (refEntries != null) {
432 /*
433 * Add all of these refs to the set of refs for this
434 * endpoint that may be invalid (this VM may not be in
435 * the server's referenced set), so that we will
436 * attempt to explicitly dirty them again in the
437 * future.
438 */
439 invalidRefs.addAll(refEntries);
440
441 /*
442 * Record that a dirty call has failed for all of these
443 * refs, so that clean calls for them in the future
444 * will be strong.
445 */
446 Iterator iter = refEntries.iterator();
447 while (iter.hasNext()) {
448 RefEntry refEntry = (RefEntry) iter.next();
449 refEntry.markDirtyFailed();
450 }
451 }
452
453 /*
454 * If the last known held lease will have expired before
455 * the next renewal, all refs might be invalid.
456 */
457 if (renewTime >= expirationTime) {
458 invalidRefs.addAll(refTable.values());
459 }
460 }
461 }
462 }
463
464 /**
465 * Set the absolute time at which the lease for this entry should
466 * be renewed.
467 *
468 * This method must ONLY be called while synchronized on this entry.
480 return null;
481 }
482 });
483 }
484 } else {
485 renewTime = newRenewTime;
486 }
487 }
488
489 /**
490 * RenewCleanThread handles the asynchronous client-side DGC activity
491 * for this entry: renewing the leases and making clean calls.
492 */
493 private class RenewCleanThread implements Runnable {
494
495 public void run() {
496 do {
497 long timeToWait;
498 RefEntry.PhantomLiveRef phantom = null;
499 boolean needRenewal = false;
500 Set refsToDirty = null;
501 long sequenceNum = Long.MIN_VALUE;
502
503 synchronized (EndpointEntry.this) {
504 /*
505 * Calculate time to block (waiting for phantom
506 * reference notifications). It is the time until the
507 * lease renewal should be done, bounded on the low
508 * end by 1 ms so that the reference queue will always
509 * get processed, and if there are pending clean
510 * requests (remaining because some clean calls
511 * failed), bounded on the high end by the maximum
512 * clean call retry interval.
513 */
514 long timeUntilRenew =
515 renewTime - System.currentTimeMillis();
516 timeToWait = Math.max(timeUntilRenew, 1);
517 if (!pendingCleans.isEmpty()) {
518 timeToWait = Math.min(timeToWait, cleanInterval);
519 }
520
547 interruptible = false;
548 Thread.interrupted(); // clear interrupted state
549
550 /*
551 * If there was a phantom reference enqueued, process
552 * it and all the rest on the queue, generating
553 * clean requests as necessary.
554 */
555 if (phantom != null) {
556 processPhantomRefs(phantom);
557 }
558
559 /*
560 * Check if it is time to renew this entry's lease.
561 */
562 long currentTime = System.currentTimeMillis();
563 if (currentTime > renewTime) {
564 needRenewal = true;
565 if (!invalidRefs.isEmpty()) {
566 refsToDirty = invalidRefs;
567 invalidRefs = new HashSet(5);
568 }
569 sequenceNum = getNextSequenceNum();
570 }
571 }
572
573 if (needRenewal) {
574 makeDirtyCall(refsToDirty, sequenceNum);
575 }
576
577 if (!pendingCleans.isEmpty()) {
578 makeCleanCalls();
579 }
580 } while (!removed || !pendingCleans.isEmpty());
581 }
582 }
583
584 /**
585 * Process the notification of the given phantom reference and any
586 * others that are on this entry's reference queue. Each phantom
587 * reference is removed from its RefEntry's ref set. All ref
588 * entries that have no more registered instances are collected
589 * into up to two batched clean call requests: one for refs
590 * requiring a "strong" clean call, and one for the rest.
591 *
592 * This method must ONLY be called while synchronized on this entry.
593 */
594 private void processPhantomRefs(RefEntry.PhantomLiveRef phantom) {
595 assert Thread.holdsLock(this);
596
597 Set strongCleans = null;
598 Set normalCleans = null;
599
600 do {
601 RefEntry refEntry = phantom.getRefEntry();
602 refEntry.removeInstanceFromRefSet(phantom);
603 if (refEntry.isRefSetEmpty()) {
604 if (refEntry.hasDirtyFailed()) {
605 if (strongCleans == null) {
606 strongCleans = new HashSet(5);
607 }
608 strongCleans.add(refEntry);
609 } else {
610 if (normalCleans == null) {
611 normalCleans = new HashSet(5);
612 }
613 normalCleans.add(refEntry);
614 }
615 removeRefEntry(refEntry);
616 }
617 } while ((phantom =
618 (RefEntry.PhantomLiveRef) refQueue.poll()) != null);
619
620 if (strongCleans != null) {
621 pendingCleans.add(
622 new CleanRequest(createObjIDArray(strongCleans),
623 getNextSequenceNum(), true));
624 }
625 if (normalCleans != null) {
626 pendingCleans.add(
627 new CleanRequest(createObjIDArray(normalCleans),
628 getNextSequenceNum(), false));
629 }
630 }
631
642 /** how many times this request has failed */
643 int failures = 0;
644
645 CleanRequest(ObjID[] objIDs, long sequenceNum, boolean strong) {
646 this.objIDs = objIDs;
647 this.sequenceNum = sequenceNum;
648 this.strong = strong;
649 }
650 }
651
652 /**
653 * Make all of the clean calls described by the clean requests in
654 * this entry's set of "pending cleans". Clean requests for clean
655 * calls that succeed are removed from the "pending cleans" set.
656 *
657 * This method must NOT be called while synchronized on this entry.
658 */
659 private void makeCleanCalls() {
660 assert !Thread.holdsLock(this);
661
662 Iterator iter = pendingCleans.iterator();
663 while (iter.hasNext()) {
664 CleanRequest request = (CleanRequest) iter.next();
665 try {
666 dgc.clean(request.objIDs, request.sequenceNum, vmid,
667 request.strong);
668 iter.remove();
669 } catch (Exception e) {
670 /*
671 * Many types of exceptions here could have been
672 * caused by a transient failure, so try again a
673 * few times, but not forever.
674 */
675 if (++request.failures >= cleanFailureRetries) {
676 iter.remove();
677 }
678 }
679 }
680 }
681
682 /**
683 * Create an array of ObjIDs (needed for the DGC remote calls)
684 * from the ids in the given set of refs.
685 */
686 private static ObjID[] createObjIDArray(Set refEntries) {
687 ObjID[] ids = new ObjID[refEntries.size()];
688 Iterator iter = refEntries.iterator();
689 for (int i = 0; i < ids.length; i++) {
690 ids[i] = ((RefEntry) iter.next()).getRef().getObjID();
691 }
692 return ids;
693 }
694
695 /**
696 * RefEntry encapsulates the client-side DGC information specific
697 * to a particular LiveRef value. In particular, it contains a
698 * set of phantom references to all of the instances of the LiveRef
699 * value registered in the system (but not garbage collected
700 * locally).
701 */
702 private class RefEntry {
703
704 /** LiveRef value for this entry (not a registered instance) */
705 private LiveRef ref;
706 /** set of phantom references to registered instances */
707 private Set refSet = new HashSet(5);
708 /** true if a dirty call containing this ref has failed */
709 private boolean dirtyFailed = false;
710
711 public RefEntry(LiveRef ref) {
712 this.ref = ref;
713 }
714
715 /**
716 * Return the LiveRef value for this entry (not a registered
717 * instance).
718 */
719 public LiveRef getRef() {
720 return ref;
721 }
722
723 /**
724 * Add a LiveRef to the set of registered instances for this entry.
725 *
726 * This method must ONLY be invoked while synchronized on this
727 * RefEntry's EndpointEntry.
775 }
776
777 /**
778 * Return true if a dirty call that explicitly contained this
779 * entry's ref has failed (and therefore a clean call for this
780 * ref needs to be marked "strong").
781 *
782 * This method must ONLY be invoked while synchronized on this
783 * RefEntry's EndpointEntry.
784 */
785 public boolean hasDirtyFailed() {
786 assert Thread.holdsLock(EndpointEntry.this);
787 return dirtyFailed;
788 }
789
790 /**
791 * PhantomLiveRef is a PhantomReference to a LiveRef instance,
792 * used to detect when the LiveRef becomes permanently
793 * unreachable in this VM.
794 */
795 private class PhantomLiveRef extends PhantomReference {
796
797 public PhantomLiveRef(LiveRef ref) {
798 super(ref, EndpointEntry.this.refQueue);
799 }
800
801 public RefEntry getRefEntry() {
802 return RefEntry.this;
803 }
804 }
805 }
806 }
807 }
|
108 private static final int cleanFailureRetries = 5;
109
110 /** constant empty ObjID array for lease renewal optimization */
111 private static final ObjID[] emptyObjIDArray = new ObjID[0];
112
113 /** ObjID for server-side DGC object */
114 private static final ObjID dgcID = new ObjID(ObjID.DGC_ID);
115
116 /*
117 * Disallow anyone from creating one of these.
118 */
119 private DGCClient() {}
120
121 /**
122 * Register the LiveRef instances in the supplied list to participate
123 * in distributed garbage collection.
124 *
125 * All of the LiveRefs in the list must be for remote objects at the
126 * given endpoint.
127 */
128 static void registerRefs(Endpoint ep, List<LiveRef> refs) {
129 /*
130 * Look up the given endpoint and register the refs with it.
131 * The retrieved entry may get removed from the global endpoint
132 * table before EndpointEntry.registerRefs() is able to acquire
133 * its lock; in this event, it returns false, and we loop and
134 * try again.
135 */
136 EndpointEntry epEntry;
137 do {
138 epEntry = EndpointEntry.lookup(ep);
139 } while (!epEntry.registerRefs(refs));
140 }
141
142 /**
143 * Get the next sequence number to be used for a dirty or clean
144 * operation from this VM. This method should only be called while
145 * synchronized on the EndpointEntry whose data structures the
146 * operation affects.
147 */
148 private static synchronized long getNextSequenceNum() {
159 * REMIND: This algorithm should be more sophisticated, waiting
160 * a longer fraction of the lease duration for longer leases.
161 */
162 return grantTime + (duration / 2);
163 }
164
165 /**
166 * EndpointEntry encapsulates the client-side DGC information specific
167 * to a particular Endpoint. Of most significance is the table that
168 * maps LiveRef value to RefEntry objects and the renew/clean thread
169 * that handles asynchronous client-side DGC operations.
170 */
171 private static class EndpointEntry {
172
173 /** the endpoint that this entry is for */
174 private Endpoint endpoint;
175 /** synthesized reference to the remote server-side DGC */
176 private DGC dgc;
177
178 /** table of refs held for endpoint: maps LiveRef to RefEntry */
179 private Map<LiveRef, RefEntry> refTable = new HashMap<>(5);
180 /** set of RefEntry instances from last (failed) dirty call */
181 private Set<RefEntry> invalidRefs = new HashSet<>(5);
182
183 /** true if this entry has been removed from the global table */
184 private boolean removed = false;
185
186 /** absolute time to renew current lease to this endpoint */
187 private long renewTime = Long.MAX_VALUE;
188 /** absolute time current lease to this endpoint will expire */
189 private long expirationTime = Long.MIN_VALUE;
190 /** count of recent dirty calls that have failed */
191 private int dirtyFailures = 0;
192 /** absolute time of first recent failed dirty call */
193 private long dirtyFailureStartTime;
194 /** (average) elapsed time for recent failed dirty calls */
195 private long dirtyFailureDuration;
196
197 /** renew/clean thread for handling lease renewals and clean calls */
198 private Thread renewCleanThread;
199 /** true if renew/clean thread may be interrupted */
200 private boolean interruptible = false;
201
202 /** reference queue for phantom references */
203 private ReferenceQueue<LiveRef> refQueue = new ReferenceQueue<>();
204 /** set of clean calls that need to be made */
205 private Set<CleanRequest> pendingCleans = new HashSet<>(5);
206
207 /** global endpoint table: maps Endpoint to EndpointEntry */
208 private static Map<Endpoint,EndpointEntry> endpointTable = new HashMap<>(5);
209 /** handle for GC latency request (for future cancellation) */
210 private static GC.LatencyRequest gcLatencyRequest = null;
211
212 /**
213 * Look up the EndpointEntry for the given Endpoint. An entry is
214 * created if one does not already exist.
215 */
216 public static EndpointEntry lookup(Endpoint ep) {
217 synchronized (endpointTable) {
218 EndpointEntry entry = endpointTable.get(ep);
219 if (entry == null) {
220 entry = new EndpointEntry(ep);
221 endpointTable.put(ep, entry);
222 /*
223 * While we are tracking live remote references registered
224 * in this VM, request a maximum latency for inspecting the
225 * entire heap from the local garbage collector, to place
226 * an upper bound on the time to discover remote references
227 * that have become unreachable (see bugid 4171278).
228 */
229 if (gcLatencyRequest == null) {
230 gcLatencyRequest = GC.requestLatency(gcInterval);
231 }
232 }
233 return entry;
234 }
235 }
236
237 private EndpointEntry(final Endpoint endpoint) {
238 this.endpoint = endpoint;
243 } catch (RemoteException e) {
244 throw new Error("internal error creating DGC stub");
245 }
246 renewCleanThread = AccessController.doPrivileged(
247 new NewThreadAction(new RenewCleanThread(),
248 "RenewClean-" + endpoint, true));
249 renewCleanThread.start();
250 }
251
252 /**
253 * Register the LiveRef instances in the supplied list to participate
254 * in distributed garbage collection.
255 *
256 * This method returns false if this entry was removed from the
257 * global endpoint table (because it was empty) before these refs
258 * could be registered. In that case, a new EndpointEntry needs
259 * to be looked up.
260 *
261 * This method must NOT be called while synchronized on this entry.
262 */
263 public boolean registerRefs(List<LiveRef> refs) {
264 assert !Thread.holdsLock(this);
265
266 Set<RefEntry> refsToDirty = null; // entries for refs needing dirty
267 long sequenceNum; // sequence number for dirty call
268
269 synchronized (this) {
270 if (removed) {
271 return false;
272 }
273
274 Iterator<LiveRef> iter = refs.iterator();
275 while (iter.hasNext()) {
276 LiveRef ref = iter.next();
277 assert ref.getEndpoint().equals(endpoint);
278
279 RefEntry refEntry = refTable.get(ref);
280 if (refEntry == null) {
281 LiveRef refClone = (LiveRef) ref.clone();
282 refEntry = new RefEntry(refClone);
283 refTable.put(refClone, refEntry);
284 if (refsToDirty == null) {
285 refsToDirty = new HashSet<RefEntry>(5);
286 }
287 refsToDirty.add(refEntry);
288 }
289
290 refEntry.addInstanceToRefSet(ref);
291 }
292
293 if (refsToDirty == null) {
294 return true;
295 }
296
297 refsToDirty.addAll(invalidRefs);
298 invalidRefs.clear();
299
300 sequenceNum = getNextSequenceNum();
301 }
302
303 makeDirtyCall(refsToDirty, sequenceNum);
304 return true;
305 }
328 * registered, we are no longer concerned with the
329 * latency of local garbage collection here.
330 */
331 if (endpointTable.isEmpty()) {
332 assert gcLatencyRequest != null;
333 gcLatencyRequest.cancel();
334 gcLatencyRequest = null;
335 }
336 removed = true;
337 }
338 }
339 }
340
341 /**
342 * Make a DGC dirty call to this entry's endpoint, for the ObjIDs
343 * corresponding to the given set of refs and with the given
344 * sequence number.
345 *
346 * This method must NOT be called while synchronized on this entry.
347 */
348 private void makeDirtyCall(Set<RefEntry> refEntries, long sequenceNum) {
349 assert !Thread.holdsLock(this);
350
351 ObjID[] ids;
352 if (refEntries != null) {
353 ids = createObjIDArray(refEntries);
354 } else {
355 ids = emptyObjIDArray;
356 }
357
358 long startTime = System.currentTimeMillis();
359 try {
360 Lease lease =
361 dgc.dirty(ids, sequenceNum, new Lease(vmid, leaseValue));
362 long duration = lease.getValue();
363
364 long newRenewTime = computeRenewTime(startTime, duration);
365 long newExpirationTime = startTime + duration;
366
367 synchronized (this) {
368 dirtyFailures = 0;
426 */
427 setRenewTime(Long.MAX_VALUE);
428 }
429 }
430
431 if (refEntries != null) {
432 /*
433 * Add all of these refs to the set of refs for this
434 * endpoint that may be invalid (this VM may not be in
435 * the server's referenced set), so that we will
436 * attempt to explicitly dirty them again in the
437 * future.
438 */
439 invalidRefs.addAll(refEntries);
440
441 /*
442 * Record that a dirty call has failed for all of these
443 * refs, so that clean calls for them in the future
444 * will be strong.
445 */
446 Iterator<RefEntry> iter = refEntries.iterator();
447 while (iter.hasNext()) {
448 RefEntry refEntry = iter.next();
449 refEntry.markDirtyFailed();
450 }
451 }
452
453 /*
454 * If the last known held lease will have expired before
455 * the next renewal, all refs might be invalid.
456 */
457 if (renewTime >= expirationTime) {
458 invalidRefs.addAll(refTable.values());
459 }
460 }
461 }
462 }
463
464 /**
465 * Set the absolute time at which the lease for this entry should
466 * be renewed.
467 *
468 * This method must ONLY be called while synchronized on this entry.
480 return null;
481 }
482 });
483 }
484 } else {
485 renewTime = newRenewTime;
486 }
487 }
488
489 /**
490 * RenewCleanThread handles the asynchronous client-side DGC activity
491 * for this entry: renewing the leases and making clean calls.
492 */
493 private class RenewCleanThread implements Runnable {
494
495 public void run() {
496 do {
497 long timeToWait;
498 RefEntry.PhantomLiveRef phantom = null;
499 boolean needRenewal = false;
500 Set<RefEntry> refsToDirty = null;
501 long sequenceNum = Long.MIN_VALUE;
502
503 synchronized (EndpointEntry.this) {
504 /*
505 * Calculate time to block (waiting for phantom
506 * reference notifications). It is the time until the
507 * lease renewal should be done, bounded on the low
508 * end by 1 ms so that the reference queue will always
509 * get processed, and if there are pending clean
510 * requests (remaining because some clean calls
511 * failed), bounded on the high end by the maximum
512 * clean call retry interval.
513 */
514 long timeUntilRenew =
515 renewTime - System.currentTimeMillis();
516 timeToWait = Math.max(timeUntilRenew, 1);
517 if (!pendingCleans.isEmpty()) {
518 timeToWait = Math.min(timeToWait, cleanInterval);
519 }
520
547 interruptible = false;
548 Thread.interrupted(); // clear interrupted state
549
550 /*
551 * If there was a phantom reference enqueued, process
552 * it and all the rest on the queue, generating
553 * clean requests as necessary.
554 */
555 if (phantom != null) {
556 processPhantomRefs(phantom);
557 }
558
559 /*
560 * Check if it is time to renew this entry's lease.
561 */
562 long currentTime = System.currentTimeMillis();
563 if (currentTime > renewTime) {
564 needRenewal = true;
565 if (!invalidRefs.isEmpty()) {
566 refsToDirty = invalidRefs;
567 invalidRefs = new HashSet<>(5);
568 }
569 sequenceNum = getNextSequenceNum();
570 }
571 }
572
573 if (needRenewal) {
574 makeDirtyCall(refsToDirty, sequenceNum);
575 }
576
577 if (!pendingCleans.isEmpty()) {
578 makeCleanCalls();
579 }
580 } while (!removed || !pendingCleans.isEmpty());
581 }
582 }
583
584 /**
585 * Process the notification of the given phantom reference and any
586 * others that are on this entry's reference queue. Each phantom
587 * reference is removed from its RefEntry's ref set. All ref
588 * entries that have no more registered instances are collected
589 * into up to two batched clean call requests: one for refs
590 * requiring a "strong" clean call, and one for the rest.
591 *
592 * This method must ONLY be called while synchronized on this entry.
593 */
594 private void processPhantomRefs(RefEntry.PhantomLiveRef phantom) {
595 assert Thread.holdsLock(this);
596
597 Set<RefEntry> strongCleans = null;
598 Set<RefEntry> normalCleans = null;
599
600 do {
601 RefEntry refEntry = phantom.getRefEntry();
602 refEntry.removeInstanceFromRefSet(phantom);
603 if (refEntry.isRefSetEmpty()) {
604 if (refEntry.hasDirtyFailed()) {
605 if (strongCleans == null) {
606 strongCleans = new HashSet<RefEntry>(5);
607 }
608 strongCleans.add(refEntry);
609 } else {
610 if (normalCleans == null) {
611 normalCleans = new HashSet<RefEntry>(5);
612 }
613 normalCleans.add(refEntry);
614 }
615 removeRefEntry(refEntry);
616 }
617 } while ((phantom =
618 (RefEntry.PhantomLiveRef) refQueue.poll()) != null);
619
620 if (strongCleans != null) {
621 pendingCleans.add(
622 new CleanRequest(createObjIDArray(strongCleans),
623 getNextSequenceNum(), true));
624 }
625 if (normalCleans != null) {
626 pendingCleans.add(
627 new CleanRequest(createObjIDArray(normalCleans),
628 getNextSequenceNum(), false));
629 }
630 }
631
642 /** how many times this request has failed */
643 int failures = 0;
644
645 CleanRequest(ObjID[] objIDs, long sequenceNum, boolean strong) {
646 this.objIDs = objIDs;
647 this.sequenceNum = sequenceNum;
648 this.strong = strong;
649 }
650 }
651
652 /**
653 * Make all of the clean calls described by the clean requests in
654 * this entry's set of "pending cleans". Clean requests for clean
655 * calls that succeed are removed from the "pending cleans" set.
656 *
657 * This method must NOT be called while synchronized on this entry.
658 */
659 private void makeCleanCalls() {
660 assert !Thread.holdsLock(this);
661
662 Iterator<CleanRequest> iter = pendingCleans.iterator();
663 while (iter.hasNext()) {
664 CleanRequest request = iter.next();
665 try {
666 dgc.clean(request.objIDs, request.sequenceNum, vmid,
667 request.strong);
668 iter.remove();
669 } catch (Exception e) {
670 /*
671 * Many types of exceptions here could have been
672 * caused by a transient failure, so try again a
673 * few times, but not forever.
674 */
675 if (++request.failures >= cleanFailureRetries) {
676 iter.remove();
677 }
678 }
679 }
680 }
681
682 /**
683 * Create an array of ObjIDs (needed for the DGC remote calls)
684 * from the ids in the given set of refs.
685 */
686 private static ObjID[] createObjIDArray(Set<RefEntry> refEntries) {
687 ObjID[] ids = new ObjID[refEntries.size()];
688 Iterator<RefEntry> iter = refEntries.iterator();
689 for (int i = 0; i < ids.length; i++) {
690 ids[i] = (iter.next()).getRef().getObjID();
691 }
692 return ids;
693 }
694
695 /**
696 * RefEntry encapsulates the client-side DGC information specific
697 * to a particular LiveRef value. In particular, it contains a
698 * set of phantom references to all of the instances of the LiveRef
699 * value registered in the system (but not garbage collected
700 * locally).
701 */
702 private class RefEntry {
703
704 /** LiveRef value for this entry (not a registered instance) */
705 private LiveRef ref;
706 /** set of phantom references to registered instances */
707 private Set<PhantomLiveRef> refSet = new HashSet<>(5);
708 /** true if a dirty call containing this ref has failed */
709 private boolean dirtyFailed = false;
710
711 public RefEntry(LiveRef ref) {
712 this.ref = ref;
713 }
714
715 /**
716 * Return the LiveRef value for this entry (not a registered
717 * instance).
718 */
719 public LiveRef getRef() {
720 return ref;
721 }
722
723 /**
724 * Add a LiveRef to the set of registered instances for this entry.
725 *
726 * This method must ONLY be invoked while synchronized on this
727 * RefEntry's EndpointEntry.
775 }
776
777 /**
778 * Return true if a dirty call that explicitly contained this
779 * entry's ref has failed (and therefore a clean call for this
780 * ref needs to be marked "strong").
781 *
782 * This method must ONLY be invoked while synchronized on this
783 * RefEntry's EndpointEntry.
784 */
785 public boolean hasDirtyFailed() {
786 assert Thread.holdsLock(EndpointEntry.this);
787 return dirtyFailed;
788 }
789
790 /**
791 * PhantomLiveRef is a PhantomReference to a LiveRef instance,
792 * used to detect when the LiveRef becomes permanently
793 * unreachable in this VM.
794 */
795 private class PhantomLiveRef extends PhantomReference<LiveRef> {
796
797 public PhantomLiveRef(LiveRef ref) {
798 super(ref, EndpointEntry.this.refQueue);
799 }
800
801 public RefEntry getRefEntry() {
802 return RefEntry.this;
803 }
804 }
805 }
806 }
807 }
|