/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* DGCClient implements the client-side of the RMI distributed garbage
* collection system.
*
* The external interface to DGCClient is the "registerRefs" method.
* When a LiveRef to a remote object enters the VM, it needs to be
* registered with the DGCClient to participate in distributed garbage
* collection.
*
* When the first LiveRef to a particular remote object is registered,
* a "dirty" call is made to the server-side distributed garbage
* collector for the remote object, which returns a lease guaranteeing
* that the server-side DGC will not collect the remote object for a
* certain period of time. While LiveRef instances to remote objects
* on a particular server exist, the DGCClient periodically sends more
* "dirty" calls to renew its lease.
*
* The DGCClient tracks the local reachability of registered LiveRef
* instances (using phantom references). When the LiveRef instance
* for a particular remote object becomes garbage collected locally,
* a "clean" call is made to the server-side distributed garbage
* collector, indicating that the server no longer needs to keep the
* remote object alive for this client.
*
* @see java.rmi.dgc.DGC, sun.rmi.transport.DGCImpl
*
* @author Ann Wollrath
* @author Peter Jones
*/
final class DGCClient {
/** next sequence number for DGC calls (access synchronized on class) */
/** unique identifier for this VM as a client of DGC */
/** lease duration to request (usually ignored by server) */
new GetLongAction("java.rmi.dgc.leaseValue",
600000)).longValue();
/** maximum interval between retries of failed clean calls */
new GetLongAction("sun.rmi.dgc.cleanInterval",
180000)).longValue();
/** maximum interval between complete garbage collections of local heap */
new GetLongAction("sun.rmi.dgc.client.gcInterval",
3600000)).longValue();
/** minimum retry count for dirty calls that fail */
/** retry count for clean calls that fail with ConnectException */
/** constant empty ObjID array for lease renewal optimization */
/** ObjID for server-side DGC object */
/*
* Disallow anyone from creating one of these.
*/
private DGCClient() {}
/**
* Register the LiveRef instances in the supplied list to participate
* in distributed garbage collection.
*
* All of the LiveRefs in the list must be for remote objects at the
* given endpoint.
*/
/*
* Look up the given endpoint and register the refs with it.
* The retrieved entry may get removed from the global endpoint
* table before EndpointEntry.registerRefs() is able to acquire
* its lock; in this event, it returns false, and we loop and
* try again.
*/
do {
}
/**
* Get the next sequence number to be used for a dirty or clean
* operation from this VM. This method should only be called while
* synchronized on the EndpointEntry whose data structures the
* operation affects.
*/
private static synchronized long getNextSequenceNum() {
return nextSequenceNum++;
}
/**
* Given the length of a lease and the time that it was granted,
* compute the absolute time at which it should be renewed, giving
* room for reasonable computational and communication delays.
*/
/*
* REMIND: This algorithm should be more sophisticated, waiting
* a longer fraction of the lease duration for longer leases.
*/
}
/**
* EndpointEntry encapsulates the client-side DGC information specific
* to a particular Endpoint. Of most significance is the table that
* that handles asynchronous client-side DGC operations.
*/
private static class EndpointEntry {
/** the endpoint that this entry is for */
/** synthesized reference to the remote server-side DGC */
/** table of refs held for endpoint: maps LiveRef to RefEntry */
/** set of RefEntry instances from last (failed) dirty call */
/** true if this entry has been removed from the global table */
private boolean removed = false;
/** absolute time to renew current lease to this endpoint */
/** absolute time current lease to this endpoint will expire */
/** count of recent dirty calls that have failed */
/** absolute time of first recent failed dirty call */
private long dirtyFailureStartTime;
/** (average) elapsed time for recent failed dirty calls */
private long dirtyFailureDuration;
private boolean interruptible = false;
/** reference queue for phantom references */
/** set of clean calls that need to be made */
/** global endpoint table: maps Endpoint to EndpointEntry */
/** handle for GC latency request (for future cancellation) */
/**
* Look up the EndpointEntry for the given Endpoint. An entry is
* created if one does not already exist.
*/
synchronized (endpointTable) {
/*
* While we are tracking live remote references registered
* in this VM, request a maximum latency for inspecting the
* entire heap from the local garbage collector, to place
* an upper bound on the time to discover remote references
* that have become unreachable (see bugid 4171278).
*/
if (gcLatencyRequest == null) {
}
}
return entry;
}
}
try {
new UnicastRef(dgcRef), true);
} catch (RemoteException e) {
throw new Error("internal error creating DGC stub");
}
new NewThreadAction(new RenewCleanThread(),
"RenewClean-" + endpoint, true));
}
/**
* Register the LiveRef instances in the supplied list to participate
* in distributed garbage collection.
*
* This method returns false if this entry was removed from the
* global endpoint table (because it was empty) before these refs
* could be registered. In that case, a new EndpointEntry needs
* to be looked up.
*
* This method must NOT be called while synchronized on this entry.
*/
long sequenceNum; // sequence number for dirty call
synchronized (this) {
if (removed) {
return false;
}
if (refsToDirty == null) {
}
}
}
if (refsToDirty == null) {
return true;
}
invalidRefs.clear();
}
return true;
}
/**
* Remove the given RefEntry from the ref table. If that makes
* the ref table empty, remove this entry from the global endpoint
* table.
*
* This method must ONLY be called while synchronized on this entry.
*/
assert !removed;
synchronized (endpointTable) {
/*
* If there are no longer any live remote references
* registered, we are no longer concerned with the
* latency of local garbage collection here.
*/
if (endpointTable.isEmpty()) {
assert gcLatencyRequest != null;
}
removed = true;
}
}
}
/**
* Make a DGC dirty call to this entry's endpoint, for the ObjIDs
* corresponding to the given set of refs and with the given
* sequence number.
*
* This method must NOT be called while synchronized on this entry.
*/
if (refEntries != null) {
} else {
}
try {
synchronized (this) {
dirtyFailures = 0;
}
} catch (Exception e) {
synchronized (this) {
if (dirtyFailures == 1) {
/*
* If this was the first recent failed dirty call,
* reschedule another one immediately, in case there
* was just a transient network problem, and remember
* the start time and duration of this attempt for
* future calculations of the delays between retries.
*/
} else {
/*
* For each successive failed dirty call, wait for a
* (binary) exponentially increasing delay before
* retrying, to avoid network congestion.
*/
int n = dirtyFailures - 2;
if (n == 0) {
/*
* Calculate the initial retry delay from the
* average time elapsed for each of the first
* two failed dirty calls. The result must be
* at least 1000ms, to prevent a tight loop.
*/
}
long newRenewTime =
endTime + (dirtyFailureDuration << n);
/*
* Continue if the last known held lease has not
* expired, or else at least a fixed number of times,
* or at least until we've tried for a fixed amount
* of time (the default lease value we request).
*/
if (newRenewTime < expirationTime ||
{
} else {
/*
* Give up: postpone lease renewals until next
* ref is registered for this endpoint.
*/
}
}
if (refEntries != null) {
/*
* Add all of these refs to the set of refs for this
* endpoint that may be invalid (this VM may not be in
* the server's referenced set), so that we will
* attempt to explicitly dirty them again in the
* future.
*/
/*
* Record that a dirty call has failed for all of these
* refs, so that clean calls for them in the future
* will be strong.
*/
}
}
/*
* If the last known held lease will have expired before
* the next renewal, all refs might be invalid.
*/
if (renewTime >= expirationTime) {
}
}
}
}
/**
* Set the absolute time at which the lease for this entry should
* be renewed.
*
* This method must ONLY be called while synchronized on this entry.
*/
if (newRenewTime < renewTime) {
if (interruptible) {
new PrivilegedAction<Void>() {
return null;
}
});
}
} else {
}
}
/**
* RenewCleanThread handles the asynchronous client-side DGC activity
* for this entry: renewing the leases and making clean calls.
*/
public void run() {
do {
long timeToWait;
boolean needRenewal = false;
synchronized (EndpointEntry.this) {
/*
* Calculate time to block (waiting for phantom
* reference notifications). It is the time until the
* lease renewal should be done, bounded on the low
* end by 1 ms so that the reference queue will always
* get processed, and if there are pending clean
* requests (remaining because some clean calls
* failed), bounded on the high end by the maximum
* clean call retry interval.
*/
long timeUntilRenew =
if (!pendingCleans.isEmpty()) {
}
/*
* Set flag indicating that it is OK to interrupt this
* thread now, such as if a earlier lease renewal time
* is set, because we are only going to be blocking
* and can deal with interrupts.
*/
interruptible = true;
}
try {
/*
* Wait for the duration calculated above for any of
* our phantom references to be enqueued.
*/
} catch (InterruptedException e) {
}
synchronized (EndpointEntry.this) {
/*
* Set flag indicating that it is NOT OK to interrupt
* this thread now, because we may be undertaking I/O
* operations that should not be interrupted (and we
* will not be blocking arbitrarily).
*/
interruptible = false;
/*
* If there was a phantom reference enqueued, process
* it and all the rest on the queue, generating
* clean requests as necessary.
*/
}
/*
* Check if it is time to renew this entry's lease.
*/
if (currentTime > renewTime) {
needRenewal = true;
if (!invalidRefs.isEmpty()) {
}
}
}
if (needRenewal) {
}
if (!pendingCleans.isEmpty()) {
}
}
}
/**
* Process the notification of the given phantom reference and any
* others that are on this entry's reference queue. Each phantom
* reference is removed from its RefEntry's ref set. All ref
* entries that have no more registered instances are collected
* into up to two batched clean call requests: one for refs
* requiring a "strong" clean call, and one for the rest.
*
* This method must ONLY be called while synchronized on this entry.
*/
do {
if (refEntry.isRefSetEmpty()) {
if (refEntry.hasDirtyFailed()) {
if (strongCleans == null) {
}
} else {
if (normalCleans == null) {
}
}
}
} while ((phantom =
if (strongCleans != null) {
getNextSequenceNum(), true));
}
if (normalCleans != null) {
getNextSequenceNum(), false));
}
}
/**
* CleanRequest holds the data for the parameters of a clean call
* that needs to be made.
*/
private static class CleanRequest {
final long sequenceNum;
final boolean strong;
/** how many times this request has failed */
this.sequenceNum = sequenceNum;
}
}
/**
* Make all of the clean calls described by the clean requests in
* this entry's set of "pending cleans". Clean requests for clean
* calls that succeed are removed from the "pending cleans" set.
*
* This method must NOT be called while synchronized on this entry.
*/
private void makeCleanCalls() {
try {
} catch (Exception e) {
/*
* Many types of exceptions here could have been
* caused by a transient failure, so try again a
* few times, but not forever.
*/
}
}
}
}
/**
* Create an array of ObjIDs (needed for the DGC remote calls)
* from the ids in the given set of refs.
*/
}
return ids;
}
/**
* RefEntry encapsulates the client-side DGC information specific
* to a particular LiveRef value. In particular, it contains a
* set of phantom references to all of the instances of the LiveRef
* value registered in the system (but not garbage collected
* locally).
*/
private class RefEntry {
/** LiveRef value for this entry (not a registered instance) */
/** set of phantom references to registered instances */
/** true if a dirty call containing this ref has failed */
private boolean dirtyFailed = false;
}
/**
* Return the LiveRef value for this entry (not a registered
* instance).
*/
return ref;
}
/**
* Add a LiveRef to the set of registered instances for this entry.
*
* This method must ONLY be invoked while synchronized on this
* RefEntry's EndpointEntry.
*/
/*
* Only keep a phantom reference to the registered instance,
* so that it can be garbage collected normally (and we can be
* notified when that happens).
*/
}
/**
* Remove a PhantomLiveRef from the set of registered instances.
*
* This method must ONLY be invoked while synchronized on this
* RefEntry's EndpointEntry.
*/
}
/**
* Return true if there are no registered LiveRef instances for
* this entry still reachable in this VM.
*
* This method must ONLY be invoked while synchronized on this
* RefEntry's EndpointEntry.
*/
public boolean isRefSetEmpty() {
}
/**
* Record that a dirty call that explicitly contained this
* entry's ref has failed.
*
* This method must ONLY be invoked while synchronized on this
* RefEntry's EndpointEntry.
*/
public void markDirtyFailed() {
dirtyFailed = true;
}
/**
* Return true if a dirty call that explicitly contained this
* entry's ref has failed (and therefore a clean call for this
* ref needs to be marked "strong").
*
* This method must ONLY be invoked while synchronized on this
* RefEntry's EndpointEntry.
*/
public boolean hasDirtyFailed() {
return dirtyFailed;
}
/**
* PhantomLiveRef is a PhantomReference to a LiveRef instance,
* used to detect when the LiveRef becomes permanently
* unreachable in this VM.
*/
}
return RefEntry.this;
}
}
}
}
}