/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sun.jini.jeri.internal.runtime;
import com.sun.jini.jeri.internal.runtime.Target;
import com.sun.jini.thread.NewThreadAction;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.rmi.Remote;
import java.rmi.server.Unreferenced;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.security.SecurityContext;
/**
* Manages references to remote object implementations (impls) used by
* ObjectTable targets. The managed reference for a particular impl
* is represented by an instance of the inner class ImplRef. All of
* the targets for current exports of an impl are registered with its
* ImplRef.
*
* This class supports pinning and unpinning of an impl reference by
* its registered targets (to control whether the reference is strong
* or weak); automatic, asynchronous invocation of an impl's
* Unreferenced.unreferenced() method, when appropriate; and
* asynchronous notifications of all registered targets when a (weakly
* referenced) impl has been garbage collected.
*
* @author Sun Microsystems, Inc.
**/
final class ImplRefManager {
private static final Logger logger =
Logger.getLogger("net.jini.jeri.BasicJeriExporter");
/** queue notified when weak refs to impls are cleared */
private final ReferenceQueue reapQueue = new ReferenceQueue();
/**
* lock guarding all mutable instance state (below).
*
* Note that if both this lock and the lock for an ImplRef
* instance need to be acquired together, then this lock must be
* acquired first.
**/
private final Object lock = new Object();
/** maps WeakKey(impl) to ImplRef(WeakKey(impl)) */
private final Map<Reference,ImplRef> weakImplTable = new HashMap<Reference,ImplRef>();
/** thread to process garbage collected impls */
private Thread reaper = null;
/** true if reaper thread may be interrupted */
private boolean interruptible = false;
ImplRefManager() {
}
/**
* Returns the ImplRef for the specified impl (creating it if
* necessary), registering the supplied target with it.
*
* If the target goes away on its own accord (i.e. without the
* impl getting garbage collected, such as by being unexported),
* it must invoke the returned ImplRef's release(Target) method
* passing itself (REMIND: perhaps this requirement could be
* avoided by using weak reference notifications).
**/
ImplRef getImplRef(Remote impl, Target target) {
/*
* Without an identity-based weak key hash table, we must look up
* with a key of a weak reference that matches on referent identity.
* If there is no matching entry, we reuse the same weak reference
* in the new ImplRef, so register it with our reference queue.
*/
Reference lookupKey = new WeakKey(impl, reapQueue);
synchronized (lock) {
ImplRef implRef = weakImplTable.get(lookupKey);
if (implRef == null) {
implRef = new ImplRef(lookupKey);
weakImplTable.put(lookupKey, implRef);
if (reaper == null) {
reaper = (Thread) AccessController.doPrivileged(
new NewThreadAction(new Reaper(), "Reaper", true));
reaper.start();
/*
* We are now interested in special assistance from the
* local garbage collector for aggressively collecting
* unreachable remote objects, so that they do not keep
* the VM alive indefinitely.
*
* Without guaranteed access to something like the
* sun.misc.GC API, however, we currently have no
* practical way of getting such special assistance.
*/
}
} else {
/*
* Clear the weak reference used for lookup, so that it will
* not generate spurious reference queue notifications later.
*/
lookupKey.clear();
}
implRef.addTarget(target);
return implRef;
}
}
/**
* A managed reference to a remote object implementation (impl).
*
* An ImplRef may be pinned and unpinned with respect to any
* target that is registered with it. While pinned for at least
* one target, an ImplRef refers to its impl with a strong
* reference; otherwise, it refers to the impl with only a weak
* reference, so that the impl may be locally garbage collected.
*
* The getImpl() method can be used to obtain a strong reference
* to the impl as long as it has not been garbage collected.
*
* An ImplRef maintains a set of all targets that are registered
* with it-- that is, targets passed to ImplRefManager.getImplRef
* but not passed to the release method. If the impl is detected
* to have been garbage collected, then all targets in the set
* will have their collect() method invoked asynchronously.
**/
final class ImplRef {
/** weak reference to impl */
private final Reference weakRef;
/** removed from table; guarded by "lock" */
private boolean removed = false;
/** targets for all exports of referenced impl; guarded by "this" */
private final Set targets = new HashSet(1);
/** targets that have pinned this reference; guarded by "this" */
private final Set pinningTargets = new HashSet(1);
/** strong reference to impl, when pinned; guarded by "this" */
private Remote strongRef = null;
private ImplRef(Reference weakRef) {
this.weakRef = weakRef;
}
private synchronized void addTarget(Target target) {
assert !targets.contains(target);
targets.add(target);
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "this={0}, target={1}",
new Object[] { this, target });
}
}
/**
* Returns the referenced impl, or null if the impl has been
* garbage collected.
**/
Remote getImpl() {
return (Remote) weakRef.get();
}
/**
* Pins this reference for the specified target, so that the
* impl will be held with a strong reference. This pin will
* remain in effect until unpin is invoked for the same
* target, or if the target releases this reference.
*
* This method must NOT be invoked if the referenced impl has
* been garbage collected (i.e. if getImpl() returns null).
**/
synchronized void pin(Target target) {
assert target.getEnableDGC();
assert targets.contains(target);
if (pinningTargets.isEmpty()) {
assert strongRef == null;
strongRef = (Remote) weakRef.get();
}
assert strongRef != null;
assert !pinningTargets.contains(target);
pinningTargets.add(target);
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST,
"this={0}, target={1}, pin count now {2}",
new Object[] {
this, target, new Integer(pinningTargets.size())
});
}
}
/**
* Unpins a previous pin of this reference. Each invocation
* of this method must correspond to a previous invocation of
* the pin method for the same target.
*
* If this operation causes the number of pinning targets to
* transition to zero, the impl's unreferenced() method will
* be asynchronously invoked (if it implements the
* Unreferenced interface).
**/
synchronized void unpin(Target target) {
assert target.getEnableDGC();
assert targets.contains(target);
assert pinningTargets.contains(target);
pinningTargets.remove(target);
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST,
"this={0}, target={1}, pin count now {2}",
new Object[] {
this, target,
new Integer(pinningTargets.size())
});
}
if (pinningTargets.isEmpty()) {
assert strongRef != null;
invokeUnreferenced(target);
strongRef = null;
}
}
/**
* Asynchronously invokes the Unreferenced.unreferenced method
* of the impl, if it is an instance of the Unreferenced
* interface. The specified target must be registered with
* this ImplRef and DGC-enabled; it is used to obtain a
* context class loader value for the invocation.
**/
private void invokeUnreferenced(final Target target) {
assert Thread.holdsLock(this);
assert strongRef != null;
assert target.getEnableDGC();
assert targets.contains(target);
if (strongRef instanceof Unreferenced) {
final Unreferenced obj = (Unreferenced) strongRef;
final Thread t = (Thread) AccessController.doPrivileged(
new NewThreadAction(new Runnable() {
public void run() {
SecurityContext securityContext =
target.getSecurityContext();
AccessController.doPrivileged(securityContext.wrap(
new PrivilegedAction() {
public Object run() {
obj.unreferenced();
return null;
}
}), securityContext.getAccessControlContext());
}
}, "Unreferenced", false, true));
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
t.setContextClassLoader(
target.getContextClassLoader());
return null;
}
});
t.start();
}
}
private boolean isPinned() {
assert Thread.holdsLock(this);
return !pinningTargets.isEmpty();
}
/**
* Removes the specified target from the set of targets that
* are using this ImplRef, allowing the target to be garbage
* collected (and this ImplRef too, if there are no other
* targets using it).
*
* If this operation causes the number of pinning targets to
* transition to zero AND there are still targets representing
* DGC-enabled exports to the referenced impl, then the impl's
* unreferenced() method will be asynchronously invoked (if it
* implements the Unreferenced interface).
**/
void release(Target target) {
synchronized (lock) {
if (removed) {
return; // may have been removed via garbage collection
}
synchronized (this) {
assert targets.contains(target);
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "this={0}, target={1}",
new Object[] { this, target });
}
targets.remove(target);
boolean moreTargetsLeft = !targets.isEmpty();
/*
* Effectively unpin this reference for the target
* if it had been pinned for it, but only invoke
* unreferenced if DGC-enabled targets remain.
*/
if (pinningTargets.remove(target) &&
pinningTargets.isEmpty())
{
assert strongRef != null;
if (moreTargetsLeft) { // one must be DGC-enabled, too
for (Iterator i = targets.iterator(); i.hasNext();)
{
Target t = (Target) i.next();
if (t.getEnableDGC()) {
invokeUnreferenced(t);
break;
}
}
}
strongRef = null;
}
/*
* If there are no targets left registered with
* this ImplRef, remove it from the table while
* synchronized on "lock", to prevent a race with
* future ImplRefManager.getImplRef invocations.
*/
if (!moreTargetsLeft) {
remove();
}
}
}
}
/**
* Removes this ImplRef from the table.
**/
private void remove() {
assert Thread.holdsLock(lock);
assert !removed;
assert weakImplTable.get(weakRef) == this;
weakImplTable.remove(weakRef);
removed = true;
if (weakImplTable.size() == 0) {
assert reaper != null;
if (interruptible) {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
reaper.interrupt();
return null;
}
});
}
reaper = null;
/*
* We are no longer interested in special assistance from the
* local garbage collector for aggressively collecting
* unreachable remote objects, if we had been getting such
* special assistance in the first place.
*/
}
}
public String toString() { // for logging
return "ImplRef@" + Integer.toHexString(hashCode()) +
"[" + getImpl() + "]";
}
}
/**
* Waits for notifications that weak references in the table have
* been cleared (and thus referenced impls have been garbage
* collected). When a notification is received, the targets using
* the containing ImplRef are notified, and the corresponding
* entry is removed from the table.
**/
private class Reaper implements Runnable {
public void run() {
do {
/*
* We must only block on the reference queue if we
* know that this thread will be interrupted if this
* reaper is to be terminated.
*/
synchronized (lock) {
if (reaper != Thread.currentThread()) {
break; // this reaper has been terminated
}
interruptible = true;
}
/*
* Wait for next cleared weak reference.
*/
Reference weakRef;
try {
weakRef = reapQueue.remove();
} catch (InterruptedException e) {
synchronized (lock) {
interruptible = false;
}
break; // pass away if interrupted
}
Set collectedTargets;
synchronized (lock) {
/*
* Prevent interrupts and clear interrupted state
* in order to avoid unpredictable behavior below.
* If an interrupt occurred after reapQueue.remove
* returned, this thread will terminate on the
* next iteration.
*/
interruptible = false;
Thread.interrupted(); // clear interrupted state
ImplRef implRef = (ImplRef) weakImplTable.get(weakRef);
if (implRef == null) {
continue; // may have been removed via unexport
}
assert !implRef.removed;
synchronized (implRef) {
assert !implRef.isPinned();
collectedTargets = implRef.targets;
implRef.remove();
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST,
"implRef={0}, targets={1}",
new Object[] {
implRef, collectedTargets
});
}
}
}
// notify targets without holding any locks
for (Iterator i = collectedTargets.iterator(); i.hasNext();) {
((Target) i.next()).collect();
}
} while (true);
}
}
}