You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@river.apache.org by th...@apache.org on 2011/06/20 15:00:44 UTC
svn commit: r1137619 [2/2] - in /river/jtsk/branches/2.2: ./ asm/ qa/
qa/doc/ src-doc/static/ src-doc/static/release-notes/
src/com/sun/jini/config/ src/com/sun/jini/jeri/internal/mux/
src/com/sun/jini/jeri/internal/runtime/ src/manifest/jsk-resources/...
Modified: river/jtsk/branches/2.2/src/com/sun/jini/jeri/internal/runtime/ObjectTable.java
URL: http://svn.apache.org/viewvc/river/jtsk/branches/2.2/src/com/sun/jini/jeri/internal/runtime/ObjectTable.java?rev=1137619&r1=1137618&r2=1137619&view=diff
==============================================================================
--- river/jtsk/branches/2.2/src/com/sun/jini/jeri/internal/runtime/ObjectTable.java (original)
+++ river/jtsk/branches/2.2/src/com/sun/jini/jeri/internal/runtime/ObjectTable.java Mon Jun 20 13:00:41 2011
@@ -19,41 +19,19 @@
package com.sun.jini.jeri.internal.runtime;
import com.sun.jini.jeri.internal.runtime.ImplRefManager.ImplRef;
-import com.sun.jini.logging.Levels;
import com.sun.jini.thread.NewThreadAction;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.ObjectInputStream;
-import java.io.OutputStream;
-import java.lang.reflect.Method;
import java.rmi.Remote;
import java.rmi.server.ExportException;
import java.rmi.server.Unreferenced;
import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.security.PrivilegedExceptionAction;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;
-import net.jini.export.ServerContext;
import net.jini.id.Uuid;
-import net.jini.id.UuidFactory;
-import net.jini.io.MarshalInputStream;
-import net.jini.io.UnsupportedConstraintException;
-import net.jini.jeri.BasicInvocationDispatcher;
-import net.jini.jeri.InvocationDispatcher;
-import net.jini.jeri.InboundRequest;
import net.jini.jeri.RequestDispatcher;
-import net.jini.jeri.ServerCapabilities;
-import net.jini.core.constraint.InvocationConstraints;
import net.jini.security.Security;
import net.jini.security.SecurityContext;
@@ -67,66 +45,48 @@ final class ObjectTable {
private static final Logger logger =
Logger.getLogger("net.jini.jeri.BasicJeriExporter");
- private static final Collection dgcDispatcherMethods = new ArrayList(2);
- static {
- Method[] methods = DgcServer.class.getMethods();
- for (int i = 0; i < methods.length; i++) {
- final Method m = methods[i];
- AccessController.doPrivileged(new PrivilegedAction() {
- public Object run() {
- m.setAccessible(true);
- return null;
- }
- });
- dgcDispatcherMethods.add(m);
- }
- }
-
- private static final ServerCapabilities dgcServerCapabilities =
- new ServerCapabilities() {
- public InvocationConstraints checkConstraints(
- InvocationConstraints constraints)
- throws UnsupportedConstraintException
- {
- assert constraints.equals(InvocationConstraints.EMPTY);
- return InvocationConstraints.EMPTY;
- }
- };
-
/**
* lock to serialize request dispatcher reservation per export, so
* that a partial export will not cause another export to fail
* unnecessarily
**/
- private final Object requestDispatchersLock = new Object();
+ private final Object requestDispatchersLock;
/** table of references to impls exported with DGC */
- private final ImplRefManager implRefManager = new ImplRefManager();
-
- /** lock guarding keepAliveCount and keeper */
- private final Object keepAliveLock = new Object();
+ private final ImplRefManager implRefManager;
/** number of objects exported with keepAlive == true */
- private int keepAliveCount = 0;
-
- /** thread to keep VM alive while keepAliveCount > 0 */
- private Thread keeper = null;
+ private final JvmLifeSupport keepAliveCount;
/** maps client ID to Lease (lock guards leaseChecker too) */
- private final Map leaseTable = new HashMap();
+ private final ConcurrentMap<Uuid,Lease> leaseTable;
/** thread to check for expired leases */
- private Thread leaseChecker = null;
-
- ObjectTable() { }
+ private Thread leaseChecker;
+
+ /** thread guard */
+ private Boolean running;
+
+ ObjectTable() {
+ requestDispatchersLock = new Object();
+ implRefManager = new ImplRefManager();
+ keepAliveCount = new JvmLifeSupport();
+ leaseTable = new ConcurrentHashMap<Uuid,Lease>(256);//Plenty of capacity to reduce resizing.
+ leaseChecker = null;
+ running = Boolean.FALSE;
+ }
RequestDispatcher createRequestDispatcher(Unreferenced unrefCallback) {
- return new RD(unrefCallback);
+ return new DgcRequestDispatcher(unrefCallback, this);
}
boolean isReferenced(RequestDispatcher requestDispatcher) {
return getRD(requestDispatcher).isReferenced();
}
+
+ DgcServer getDgcServer(DgcRequestDispatcher dgdRD){
+ return new DgcServerImpl(dgdRD);
+ }
Target export(Remote impl,
RequestDispatcher[] requestDispatchers,
@@ -135,21 +95,31 @@ final class ObjectTable {
Uuid id)
throws ExportException
{
- RD[] rds = new RD[requestDispatchers.length];
+ DgcRequestDispatcher[] rds = new DgcRequestDispatcher[requestDispatchers.length];
for (int i = 0; i < requestDispatchers.length; i++) {
rds[i] = getRD(requestDispatchers[i]);
}
-
- return new Target(impl, id, rds, allowDGC, keepAlive);
+ SecurityContext securityContext = Security.getContext();
+ ClassLoader ccl = Thread.currentThread().getContextClassLoader();
+ Target t = null;
+ t = new Target(id, rds, allowDGC, keepAlive,this,
+ securityContext, ccl, keepAliveCount);
+ synchronized (requestDispatchersLock){
+ t.procRequestDispatchers();
+ }
+ ImplRef implRef = implRefManager.getImplRef(impl, t);
+ t.setImplRef(implRef);
+ t.setExported();
+ return t;
}
- private RD getRD(RequestDispatcher requestDispatcher) {
+ private DgcRequestDispatcher getRD(RequestDispatcher requestDispatcher) {
/*
* The following cast will throw a ClassCastException if we were
* passed a RequestDispatcher that was not returned by this class's
* createRequestDispatcher method:
*/
- RD rd = (RD) requestDispatcher;
+ DgcRequestDispatcher rd = (DgcRequestDispatcher) requestDispatcher;
if (!rd.forTable(this)) {
throw new IllegalArgumentException(
"request dispatcher for different object table");
@@ -157,813 +127,245 @@ final class ObjectTable {
return rd;
}
- /**
- * Increments the count of objects exported with keepAlive true,
- * starting a non-daemon thread if necessary.
- **/
- private void incrementKeepAliveCount() {
- synchronized (keepAliveLock) {
- keepAliveCount++;
-
- if (keeper == null) {
- keeper = (Thread) AccessController.doPrivileged(
- new NewThreadAction(new Runnable() {
- public void run() {
- try {
- while (true) {
- Thread.sleep(Long.MAX_VALUE);
- }
- } catch (InterruptedException e) {
- // pass away if interrupted
- }
- }
- }, "KeepAlive", false));
- keeper.start();
- }
- }
- }
-
- /**
- * Decrements the count of objects exported with keepAlive true,
- * stopping the non-daemon thread if decremented to zero.
- **/
- private void decrementKeepAliveCount() {
- synchronized (keepAliveLock) {
- keepAliveCount--;
-
- if (keepAliveCount == 0) {
- assert keeper != null;
- AccessController.doPrivileged(new PrivilegedAction() {
- public Object run() {
- keeper.interrupt();
- return null;
- }
- });
- keeper = null;
- }
- }
- }
-
- /**
- * A Target is returned by the export method to represent the object
- * exported to this ObjectTable. It can be used to unexport the
- * exported object.
- */
- final class Target {
-
- private final ImplRef implRef;
- private final Uuid id;
- private final RD[] requestDispatchers;
- private final boolean allowDGC;
- private final boolean keepAlive;
- private final SecurityContext securityContext;
- private final ClassLoader ccl;
-
- /** lock guarding all mutable instance state (below) */
- private final Object lock = new Object();
- private InvocationDispatcher invocationDispatcher;
- private boolean exported = false;
- private int callsInProgress = 0;
- private final Set referencedSet;
- private final Map sequenceTable;
-
- Target(Remote impl,
- Uuid id,
- RD[] requestDispatchers,
- boolean allowDGC,
- boolean keepAlive)
- throws ExportException
- {
- this.id = id;
- this.requestDispatchers = requestDispatchers;
- this.allowDGC = allowDGC;
- this.keepAlive = keepAlive;
-
- securityContext = Security.getContext();
- ccl = Thread.currentThread().getContextClassLoader();
-
- synchronized (requestDispatchersLock) {
- boolean success = false;
- int i = 0;
- try {
- for (i = 0; i < requestDispatchers.length; i++) {
- requestDispatchers[i].put(this);
- }
- success = true;
- } finally {
- if (!success) {
- for (int j = 0; j < i; j++) {
- requestDispatchers[i].remove(this, false);
- }
- }
- }
- }
-
- implRef = implRefManager.getImplRef(impl, this);
-
- if (allowDGC) {
- referencedSet = new HashSet(3);
- sequenceTable = new HashMap(3);
- } else {
- referencedSet = null;
- sequenceTable = null;
- }
-
- if (keepAlive) {
- incrementKeepAliveCount();
- }
-
- synchronized (lock) {
- exported = true;
- }
- }
-
- void setInvocationDispatcher(InvocationDispatcher id) {
- assert id != null;
- synchronized (lock) {
- assert invocationDispatcher == null;
- invocationDispatcher = id;
- }
- }
-
- boolean unexport(boolean force) {
- synchronized (lock) {
- if (!exported) {
- return true;
- }
- if (!force && callsInProgress > 0) {
- return false;
- }
- exported = false;
-
- if (keepAlive && callsInProgress == 0) {
- decrementKeepAliveCount();
- }
-
- if (allowDGC) {
- if (!referencedSet.isEmpty()) {
- for (Iterator i = referencedSet.iterator();
- i.hasNext();)
- {
- Uuid clientID = (Uuid) i.next();
- unregisterTarget(this, clientID);
- }
- referencedSet.clear();
- }
- sequenceTable.clear();
- }
- }
-
- implRef.release(this);
-
- for (int i = 0; i < requestDispatchers.length; i++) {
- requestDispatchers[i].remove(this, false);
- }
- return true;
- }
-
- void collect() {
- synchronized (lock) {
- if (!exported) {
- return;
- }
-
- if (logger.isLoggable(Level.FINE)) {
- logger.log(Level.FINE,
- "garbage collection of object with id {0}", id);
- }
-
- exported = false;
-
- if (keepAlive && callsInProgress == 0) {
- decrementKeepAliveCount();
- }
-
- if (allowDGC) {
- assert referencedSet.isEmpty();
- sequenceTable.clear();
- }
- }
-
- for (int i = 0; i < requestDispatchers.length; i++) {
- requestDispatchers[i].remove(this, true);
- }
- }
-
- Uuid getObjectIdentifier() {
- return id;
- }
-
- // used by ImplRef for invoking Unreferenced.unreferenced
- boolean getEnableDGC() {
- return allowDGC;
- }
-
- // used by ImplRef for invoking Unreferenced.unreferenced
- SecurityContext getSecurityContext() {
- return securityContext;
- }
-
- // used by ImplRef for invoking Unreferenced.unreferenced
- ClassLoader getContextClassLoader() {
- return ccl;
- }
-
- void referenced(Uuid clientID, long sequenceNum) {
- if (!allowDGC) {
- return; // ignore if DGC not enabled for this object
- }
-
- synchronized (lock) {
- if (!exported) {
- return;
- }
-
- if (logger.isLoggable(Level.FINEST)) {
- logger.log(Level.FINEST,
- "this={0}, clientID={1}, sequenceNum={2}",
- new Object[] {
- this, clientID, new Long(sequenceNum)
- });
- }
-
- /*
- * Check current sequence number against the last
- * recorded sequence number for the client. If the
- * current value is lower, then this is a "late dirty
- * call", which should not be processed. Otherwise,
- * update the last recorded sequence number.
- */
- SequenceEntry entry =
- (SequenceEntry) sequenceTable.get(clientID);
- if (entry == null) {
- // no record: must assume this is not a late dirty call
- entry = new SequenceEntry(sequenceNum);
- sequenceTable.put(clientID, entry);
- } else if (sequenceNum < entry.sequenceNum) {
- return; // late dirty call: ignore
- } else {
- entry.sequenceNum = sequenceNum;
- }
-
- if (!referencedSet.contains(clientID)) {
- if (referencedSet.isEmpty()) {
- Remote impl = implRef.getImpl();
- if (impl == null) {
- return; // too late if impl was collected
- }
- implRef.pin(this);
- }
- referencedSet.add(clientID);
-
- registerTarget(this, clientID);
- }
- }
- }
-
- void unreferenced(Uuid clientID, long sequenceNum, boolean strong) {
- if (!allowDGC) {
- return; // ignore if DGC not enabled for this object
- }
-
- synchronized (lock) {
- if (!exported) {
- return;
- }
-
- if (logger.isLoggable(Level.FINEST)) {
- logger.log(Level.FINEST,
- "this={0}, clientID={1}, sequenceNum={2}, strong={3}",
- new Object[] {
- this, clientID, new Long(sequenceNum),
- Boolean.valueOf(strong)
- });
- }
-
- /*
- * Check current sequence number against the last
- * recorded sequence number for the client. If the
- * current value is lower, then this is a "late clean
- * call", which should not be processed. Otherwise:
- * if this is for a strong clean call, then update the
- * last recorded sequence number; if no strong clean
- * call has been processed for this client, discard
- * its sequence number record.
- */
- SequenceEntry entry =
- (SequenceEntry) sequenceTable.get(clientID);
- if (entry == null) {
- // no record: must assume this is not a late clean call
- if (strong) {
- entry = new SequenceEntry(sequenceNum);
- sequenceTable.put(clientID, entry);
- entry.keep = true;
- }
- } else if (sequenceNum < entry.sequenceNum) {
- return; // late clean call: ignore
- } else if (strong) {
- entry.sequenceNum = sequenceNum;
- entry.keep = true; // strong clean: retain sequence number
- } else if (!entry.keep) {
- sequenceTable.remove(clientID);
- }
-
- unregisterTarget(this, clientID);
-
- if (referencedSet.remove(clientID) &&
- referencedSet.isEmpty())
- {
- implRef.unpin(this);
- }
- }
- }
-
- void leaseExpired(Uuid clientID) {
- assert allowDGC;
-
- synchronized (lock) {
- if (!exported) {
- return;
- }
-
- if (logger.isLoggable(Level.FINEST)) {
- logger.log(Level.FINEST,
- "this={0}, clientID={1}",
- new Object[] { this, clientID });
- }
-
- SequenceEntry entry =
- (SequenceEntry) sequenceTable.get(clientID);
- if (entry != null && !entry.keep) {
- /*
- * REMIND: We could be removing the sequence number
- * for a more recent lease, thus allowing a "late
- * clean call" to be inappropriately processed?
- * (See 4848840 Comments.)
- */
- sequenceTable.remove(clientID);
- }
-
- if (referencedSet.remove(clientID) &&
- referencedSet.isEmpty())
- {
- implRef.unpin(this);
- }
- }
- }
-
- void dispatch(InboundRequest request)
- throws IOException, NoSuchObject
- {
- InvocationDispatcher id;
- synchronized (lock) {
- if (!exported || invocationDispatcher == null) {
- if (logger.isLoggable(Level.FINEST)) {
- logger.log(Level.FINEST,
- "this={0}, not exported", this);
- }
- throw new NoSuchObject();
- }
- id = invocationDispatcher; // save for reference outside lock
- callsInProgress++;
- }
- try {
- Remote impl = implRef.getImpl();
- if (impl == null) {
- if (logger.isLoggable(Level.FINEST)) {
- logger.log(Level.FINEST,
- "this={0}, garbage collected", this);
- }
- throw new NoSuchObject();
- }
-
- dispatch(request, id, impl);
-
- } finally {
- synchronized (lock) {
- assert callsInProgress > 0;
- callsInProgress--;
-
- if (keepAlive && !exported && callsInProgress == 0) {
- decrementKeepAliveCount();
- }
- }
- }
- }
-
- private void dispatch(final InboundRequest request,
- final InvocationDispatcher id,
- final Remote impl)
- throws IOException, NoSuchObject
- {
- Thread t = Thread.currentThread();
- ClassLoader savedCcl = t.getContextClassLoader();
- try {
- if (ccl != savedCcl) {
- t.setContextClassLoader(ccl);
- }
- AccessController.doPrivileged(securityContext.wrap(
- new PrivilegedExceptionAction() {
- public Object run() throws IOException {
- dispatch0(request, id, impl);
- return null;
- }
- }), securityContext.getAccessControlContext());
-
- } catch (java.security.PrivilegedActionException e) {
- throw (IOException) e.getException();
- } finally {
- if (ccl != savedCcl || savedCcl != t.getContextClassLoader()) {
- t.setContextClassLoader(savedCcl);
- }
- }
- }
-
- private void dispatch0(final InboundRequest request,
- final InvocationDispatcher id,
- final Remote impl)
- throws IOException
- {
- request.checkPermissions();
-
- OutputStream out = request.getResponseOutputStream();
- out.write(Jeri.OBJECT_HERE);
-
- final Collection context = new ArrayList(5);
- request.populateContext(context);
-
- ServerContext.doWithServerContext(new Runnable() {
- public void run() {
- id.dispatch(impl, request, context);
- }
- }, Collections.unmodifiableCollection(context));
- }
-
- public String toString() { // for logging
- return "Target@" + Integer.toHexString(hashCode()) +
- "[" + id + "]";
- }
- }
-
- private static final class SequenceEntry {
- long sequenceNum;
- boolean keep;
-
- SequenceEntry(long sequenceNum) {
- this.sequenceNum = sequenceNum;
- }
- }
-
void registerTarget(Target target, Uuid clientID) {
- synchronized (leaseTable) {
- Lease lease = (Lease) leaseTable.get(clientID);
- if (lease == null) {
- target.leaseExpired(clientID);
- } else {
- synchronized (lease.notifySet) {
- lease.notifySet.add(target);
- }
- }
- }
+ Lease lease = leaseTable.get(clientID);
+ if (lease == null) {
+ target.leaseExpired(clientID);
+ } else {
+ boolean added = lease.add(target);
+ if ( added == false){
+ // lease has been locked because it has expired
+ // prior to removal
+ target.leaseExpired(clientID);
+ }
+ }
}
void unregisterTarget(Target target, Uuid clientID) {
- synchronized (leaseTable) {
- Lease lease = (Lease) leaseTable.get(clientID);
- if (lease != null) {
- synchronized (lease.notifySet) {
- lease.notifySet.remove(target);
- }
- }
- }
- }
-
- /**
- * RequestDispatcher implementation.
- **/
- private class RD implements RequestDispatcher {
-
- private final Unreferenced unrefCallback;
-
- private final Map idTable = new HashMap();
- private int dgcEnabledCount = 0; // guarded by idTable lock
-
- private final InvocationDispatcher dgcDispatcher;
- private final DgcServerImpl dgcServerImpl;
-
- RD(Unreferenced unrefCallback) {
- this.unrefCallback = unrefCallback;
- try {
- dgcDispatcher =
- new BasicInvocationDispatcher(
- dgcDispatcherMethods, dgcServerCapabilities,
- null, null, this.getClass().getClassLoader())
- {
- protected ObjectInputStream createMarshalInputStream(
- Object impl,
- InboundRequest request,
- boolean integrity,
- Collection context)
- throws IOException
- {
- ClassLoader loader = getClassLoader();
- return new MarshalInputStream(
- request.getRequestInputStream(),
- loader, integrity, loader,
- Collections.unmodifiableCollection(context));
- // useStreamCodebases() not invoked
- }
- };
- } catch (ExportException e) {
- throw new AssertionError();
- }
- dgcServerImpl = new DgcServerImpl();
- }
-
- boolean forTable(ObjectTable table) {
- return ObjectTable.this == table;
- }
-
- boolean isReferenced() {
- synchronized (idTable) {
- return !idTable.isEmpty();
- }
- }
-
- Target get(Uuid id) {
- synchronized (idTable) {
- return (Target) idTable.get(id);
- }
- }
-
- void put(Target target) throws ExportException {
- synchronized (idTable) {
- Uuid id = target.getObjectIdentifier();
- if (id.equals(Jeri.DGC_ID)) {
- throw new ExportException(
- "object identifier reserved for DGC");
- }
- if (idTable.containsKey(id)) {
- throw new ExportException(
- "object identifier already in use");
- }
- idTable.put(id, target);
- if (target.getEnableDGC()) {
- dgcEnabledCount++;
- }
- }
- }
-
- void remove(Target target, boolean gc) {
- boolean empty = false;
- synchronized (idTable) {
- Uuid id = target.getObjectIdentifier();
- assert idTable.get(id) == target;
- idTable.remove(id);
- if (target.getEnableDGC()) {
- dgcEnabledCount--;
- assert dgcEnabledCount >= 0;
- }
-
- if (idTable.isEmpty()) {
- empty = true;
- }
- }
-
- if (gc && empty) {
- /*
- * We have to be careful to make this callback without holding
- * the lock for idTable, because the callback implementation
- * will likely be code that calls this object's isReferenced
- * method in its own synchronized block.
- */
- unrefCallback.unreferenced();
- }
- }
-
- private boolean hasDgcEnabledTargets() {
- synchronized (idTable) {
- return dgcEnabledCount > 0;
- }
- }
-
- public void dispatch(InboundRequest request) {
- try {
- InputStream in = request.getRequestInputStream();
- Uuid id = UuidFactory.read(in);
-
- if (logger.isLoggable(Level.FINEST)) {
- logger.log(Level.FINEST, "id={0}", id);
- }
-
- try {
- /*
- * The DGC object identifier is hardwired here,
- * rather than install it in idTable; this
- * eliminates the need to worry about not counting
- * the DGC server as an exported object in the
- * table, and it doesn't need all of the machinery
- * that Target provides.
- */
- if (id.equals(Jeri.DGC_ID)) {
- dispatchDgcRequest(request);
- return;
- }
-
- Target target = (Target) get(id);
- if (target == null) {
- logger.log(Level.FINEST, "id not in table");
- throw new NoSuchObject();
- }
- target.dispatch(request);
-
- } catch (NoSuchObject e) {
- in.close();
- OutputStream out = request.getResponseOutputStream();
- out.write(Jeri.NO_SUCH_OBJECT);
- out.close();
-
- if (logger.isLoggable(Levels.FAILED)) {
- logger.log(Levels.FAILED, "no such object: {0}", id);
- }
- }
- } catch (IOException e) {
- request.abort();
-
- if (logger.isLoggable(Levels.FAILED)) {
- logger.log(Levels.FAILED,
- "I/O exception dispatching request", e);
- }
- }
- }
-
- private void dispatchDgcRequest(final InboundRequest request)
- throws IOException, NoSuchObject
- {
- if (!hasDgcEnabledTargets()) {
- logger.log(Level.FINEST, "no DGC-enabled targets");
- throw new NoSuchObject();
- }
-
- OutputStream out = request.getResponseOutputStream();
- out.write(Jeri.OBJECT_HERE);
-
- final Collection context = new ArrayList(5);
- request.populateContext(context);
-
- ServerContext.doWithServerContext(new Runnable() {
- public void run() {
- dgcDispatcher.dispatch(dgcServerImpl, request, context);
- }
- }, Collections.unmodifiableCollection(context));
- }
-
- private class DgcServerImpl implements DgcServer {
-
- public long dirty(Uuid clientID,
- long sequenceNum,
- Uuid[] ids)
- {
- if (logger.isLoggable(Level.FINEST)) {
- logger.log(Level.FINEST,
- "clientID={0}, sequenceNum={1}, ids={2}",
- new Object[] {
- clientID, new Long(sequenceNum), Arrays.asList(ids)
- });
- }
-
- long duration = Jeri.leaseValue;
-
- synchronized (leaseTable) {
- Lease lease = (Lease) leaseTable.get(clientID);
- if (lease == null) {
- leaseTable.put(clientID,
- new Lease(clientID, duration));
- if (leaseChecker == null) {
- leaseChecker =
- (Thread) AccessController.doPrivileged(
- new NewThreadAction(new LeaseChecker(),
- "DGC Lease Checker", true));
- leaseChecker.start();
- }
- } else {
- lease.renew(duration);
- }
- }
-
- for (int i = 0; i < ids.length; i++) {
- Target target = get(ids[i]);
- if (target != null) {
- target.referenced(clientID, sequenceNum);
- }
- }
-
- return duration;
- }
-
- public void clean(Uuid clientID,
- long sequenceNum,
- Uuid[] ids,
- boolean strong)
- {
- if (logger.isLoggable(Level.FINEST)) {
- logger.log(Level.FINEST,
- "clientID={0}, sequenceNum={1}, ids={2}, strong={3}",
- new Object[] {
- clientID, new Long(sequenceNum),
- Arrays.asList(ids), Boolean.valueOf(strong)
- });
- }
-
- for (int i = 0; i < ids.length; i++) {
- Target target = get(ids[i]);
- if (target != null) {
- target.unreferenced(clientID, sequenceNum, strong);
- }
- }
- }
- }
+ Lease lease = leaseTable.get(clientID);
+ if (lease != null) {
+ lease.remove(target);
+ }
+ }
+
+
+
+ private class DgcServerImpl implements DgcServer {
+ private final DgcRequestDispatcher dgcRequestDispatcher;
+
+ DgcServerImpl(DgcRequestDispatcher dgcRequestDispatcher){
+ this.dgcRequestDispatcher = dgcRequestDispatcher;
+ }
+
+ public long dirty(Uuid clientID,
+ long sequenceNum,
+ Uuid[] ids)
+ {
+ if (logger.isLoggable(Level.FINEST)) {
+ logger.log(Level.FINEST,
+ "clientID={0}, sequenceNum={1}, ids={2}",
+ new Object[] {
+ clientID, new Long(sequenceNum), Arrays.asList(ids)
+ });
+ }
+
+ long duration = Jeri.leaseValue;
+
+ Lease lease = leaseTable.get(clientID);
+ if (lease == null) {
+ lease = new Lease(clientID, duration);
+ Lease existed = leaseTable.putIfAbsent(clientID,lease);
+ if (existed != null){
+ assert clientID.equals(existed.getClientID());
+ boolean renewed = existed.renew(duration);
+ if (!renewed){
+ /* The probability of getting here is low,
+ * it indicates a lease with an extremely short
+ * expiry and a very small lease table.
+ */
+ if (logger.isLoggable(Level.WARNING)) {
+ logger.log(Level.WARNING,
+ "Problem with lease table, try a longer " +
+ "lease duration clientID={0}, " +
+ "sequenceNum={1}, ids={2}",
+ new Object[] {
+ clientID, new Long(sequenceNum), Arrays.asList(ids)
+ });
+ }
+ }
+ }
+ } else {
+ assert clientID.equals(lease.getClientID());
+ boolean renewed = lease.renew(duration);
+ if (!renewed){
+ // Indicates an expired lease in the table. A lease
+ // always becomes expired prior to removal, it is
+ // never removed prior to expiry, in case it is
+ // renewed by another thread, which would risk a renewed
+ // lease being removed from the table.
+ // An expired lease must be replaced.
+ leaseTable.remove(clientID, lease); // Another thread could remove it first.
+ lease = new Lease(clientID, duration);
+ Lease existed = leaseTable.putIfAbsent(clientID, lease);
+ if (existed != null){
+ lease = existed;
+ assert clientID.equals(lease.getClientID());
+ renewed = lease.renew(duration);
+ if (!renewed){
+ /* The probability of getting here is low,
+ * it indicates a lease of extremely short
+ * duration and a very small lease table.
+ */
+ if (logger.isLoggable(Level.WARNING)) {
+ logger.log(Level.WARNING,
+ "Problem with lease table, try a longer " +
+ "lease duration clientID={0}, " +
+ "sequenceNum={1}, ids={2}",
+ new Object[] {
+ clientID, new Long(sequenceNum), Arrays.asList(ids)
+ });
+ }
+ }
+ }
+ }
+ }
+ /* FIXED: River-142:
+ * In the server-side DGC implementation's thread that check's for
+ * lease expirations
+ * (com.sun.jini.jeri.internal.runtime.ObjectTable.LeaseChecker.run),
+ * it checks for them while synchronized on the overall lease table,
+ * but it delays notifying the expired leases' individual registered
+ * Targets about the expirations until after it has released the
+ * lease table lock. This approach was taken from the
+ * JRMP implementation, which is that way because of the fix
+ * for 4118056 (a previous deadlock bug-- but now, I'm thinking
+ * that the JRMP implementation has this bug too).
+ *
+ * The problem seems to be that after releasing the lease table
+ * lock, it is possible for another lease renewal/request to
+ * come in (from the same DGC client and for the same remote object)
+ * that would then be invalidated by the subsequent Target
+ * notification made by the lease expiration check thread-- and
+ * thus the client's lease renewal (for that remote object) will
+ * be forgotten. It would appear that the synchronization approach
+ * here needs to be reconsidered.
+ *
+ * ( Comments note: )
+ * In addition to the basic problem of the expired-then-renewed
+ * client being removed from the referenced set, there is also
+ * the problem of the sequence table entry being forgotten-- which
+ * prevents detection of a "late clean call".
+ * Normally, late clean calls are not a problem because sequence
+ * numbers are retained while the client is in the referenced set
+ * (and there is no such thing as a "strong dirty").
+ * But in this case, with the following order of events on
+ * the server side:
+ *
+ * 1. dirty, seqNo=2
+ * 2. (lease expiration)
+ * 3. clean, seqNo=1
+ *
+ * The primary bug here is that the first two events will leave
+ * the client missing from the referenced set. But the secondary
+ * bug is that even if that's fixed, with the sequence number
+ * forgotten, the third event (the "late clean call") will still
+ * cause the client to be removed from the referenced set.
+ *
+ * FIX:
+ * This issue has been fixed by making the Lease responsible for
+ * it's own state, which is protected internally by synchronization.
+ * The lease checker passes the time to the Lease, which checks
+ * itself and notifies the Targets in the event of expiry.
+ *
+ * Because the notification is not delayed, the client id and
+ * sequence number will not be not be removed by the LeaseChecker
+ * thread after being updated by the second dirty call (when
+ * the lock was cleared as described).
+ *
+ * The client id and sequence number are added to the Target sequence
+ * table by the second dirty call, after the Lease removes
+ * them immediately upon expiry being invoked by the LeaseChecker.
+ *
+ * Then because the late clean call sequence number is less than the
+ * second dirty call and exists, it is correctly recognised.
+ */
+ synchronized (running){
+ if (!running) {
+ leaseChecker =
+ (Thread) AccessController.doPrivileged(
+ new NewThreadAction(new LeaseChecker(),
+ "DGC Lease Checker", true));
+ leaseChecker.start();
+ }
+ }
+ for (int i = 0; i < ids.length; i++) {
+ Target target = dgcRequestDispatcher.get(ids[i]);
+ if (target != null) {
+ target.referenced(clientID, sequenceNum);
+ }
+ }
+ return duration;
+ }
+
+ public void clean(Uuid clientID,
+ long sequenceNum,
+ Uuid[] ids,
+ boolean strong)
+ {
+ if (logger.isLoggable(Level.FINEST)) {
+ logger.log(Level.FINEST,
+ "clientID={0}, sequenceNum={1}, ids={2}, strong={3}",
+ new Object[] {
+ clientID, new Long(sequenceNum),
+ Arrays.asList(ids), Boolean.valueOf(strong)
+ });
+ }
+
+ for (int i = 0; i < ids.length; i++) {
+ Target target = dgcRequestDispatcher.get(ids[i]);
+ if (target != null) {
+ target.unreferenced(clientID, sequenceNum, strong);
+ }
+ }
+ }
}
private class LeaseChecker implements Runnable {
public void run() {
boolean done = false;
- do {
- try {
- Thread.sleep(Jeri.leaseCheckInterval);
- } catch (InterruptedException e) {
- // REMIND: shouldn't happen, OK to ignore?
- }
-
- long now = System.currentTimeMillis();
-
- Collection expiredLeases = new ArrayList();
-
- synchronized (leaseTable) {
- for (Iterator i = leaseTable.values().iterator();
- i.hasNext();)
- {
- Lease lease = (Lease) i.next();
- if (lease.hasExpired(now)) {
- expiredLeases.add(lease);
- i.remove();
- }
- }
-
- if (leaseTable.isEmpty()) {
- leaseChecker = null;
- done = true;
- }
- }
-
- if (expiredLeases.isEmpty()) {
- continue;
- }
-
- for (Iterator i = expiredLeases.iterator(); i.hasNext();) {
- Lease lease = (Lease) i.next();
- if (lease.notifySet.isEmpty()) {
- continue;
- }
-
- for (Iterator i2 = lease.notifySet.iterator();
- i2.hasNext();)
- {
- Target target = (Target) i2.next();
- target.leaseExpired(lease.getClientID());
- }
- }
- } while (!done);
- }
- }
-
- private static class Lease {
-
- private final Uuid clientID;
- final Set notifySet = new HashSet(3); // guarded?
- private long expiration; // guarded by leaseTable lock
-
- Lease(Uuid clientID, long duration) {
- this.clientID = clientID;
- expiration = System.currentTimeMillis() + duration;
- }
-
- Uuid getClientID() {
- return clientID;
- }
-
- void renew(long duration) {
- long newExpiration = System.currentTimeMillis() + duration;
- if (newExpiration > expiration) {
- expiration = newExpiration;
- }
- }
-
- boolean hasExpired(long now) {
- return expiration < now;
+ try {
+ do {
+ Thread.sleep(Jeri.leaseCheckInterval);
+ long now = System.currentTimeMillis();
+ for (Iterator i = leaseTable.values().iterator();
+ i.hasNext();)
+ {
+ Lease lease = (Lease) i.next();
+ boolean expired = lease.notifyIfExpired(now);
+ if (expired) {
+ i.remove();
+ }
+ }
+ if (leaseTable.isEmpty()) {
+ done = true;
+ }
+ } while (!done);
+ } catch (InterruptedException e) {
+ // REMIND: shouldn't happen, OK to ignore?
+ // No, restore the interrupted status
+ Thread.currentThread().interrupt();
+ } finally {
+ // This is always executed and returns the lease checker
+ // to the non running state, such that if the application
+ // has not exited, another thread will be started eventually.
+ synchronized (running){
+ leaseChecker = null;
+ running = Boolean.FALSE;
+ }
+ }
}
}
- private static class NoSuchObject extends Exception { }
+ static class NoSuchObject extends Exception { }
}
Modified: river/jtsk/branches/2.2/src/manifest/jsk-resources/META-INF/services/net.jini.config.Configuration
URL: http://svn.apache.org/viewvc/river/jtsk/branches/2.2/src/manifest/jsk-resources/META-INF/services/net.jini.config.Configuration?rev=1137619&r1=1137618&r2=1137619&view=diff
==============================================================================
--- river/jtsk/branches/2.2/src/manifest/jsk-resources/META-INF/services/net.jini.config.Configuration (original)
+++ river/jtsk/branches/2.2/src/manifest/jsk-resources/META-INF/services/net.jini.config.Configuration Mon Jun 20 13:00:41 2011
@@ -15,4 +15,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#*/
-com.sun.jini.config.GroovyConfig
+net.jini.config.GroovyConfig
Modified: river/jtsk/branches/2.2/src/net/jini/config/package.html
URL: http://svn.apache.org/viewvc/river/jtsk/branches/2.2/src/net/jini/config/package.html?rev=1137619&r1=1137618&r2=1137619&view=diff
==============================================================================
--- river/jtsk/branches/2.2/src/net/jini/config/package.html (original)
+++ river/jtsk/branches/2.2/src/net/jini/config/package.html Mon Jun 20 13:00:41 2011
@@ -59,6 +59,10 @@ The {@link net.jini.config.EmptyConfigur
instance of this class to simplify handling cases where no configuration
is specified rather than, for example, checking for a <code>null</code>
configuration. <p>
+
+The net.jini.config.GroovyConfig class is a new configuration provider,
+it uses Groovy configuration objects, this is potentially the most
+powerful configuration mechanism. <p>
<a name="Usage"><h2>Using Configuration</h2></a>
Modified: river/jtsk/branches/2.2/src/net/jini/jeri/connection/ConnectionManager.java
URL: http://svn.apache.org/viewvc/river/jtsk/branches/2.2/src/net/jini/jeri/connection/ConnectionManager.java?rev=1137619&r1=1137618&r2=1137619&view=diff
==============================================================================
--- river/jtsk/branches/2.2/src/net/jini/jeri/connection/ConnectionManager.java (original)
+++ river/jtsk/branches/2.2/src/net/jini/jeri/connection/ConnectionManager.java Mon Jun 20 13:00:41 2011
@@ -106,6 +106,10 @@ import net.jini.jeri.OutboundRequestIter
* milliseconds to leave idle client-side connections around before
* closing them. The default value is 15000 milliseconds (15 seconds).
*
+ * <li><code>com.sun.jini.jeri.handshakeTimeout</code> - Time in
+ * milliseconds for client-side connections to wait for the server to
+ * acknowledge an opening handshake. The default value is 15000 milliseconds (15 seconds).
+ *
* </ul>
**/
public final class ConnectionManager {
@@ -113,10 +117,17 @@ public final class ConnectionManager {
* How long to leave idle muxes around before closing them.
*/
private static final long TIMEOUT =
- ((Long) AccessController.doPrivileged(new GetLongAction(
+ ( (Long) AccessController.doPrivileged(new GetLongAction(
"com.sun.jini.jeri.connectionTimeout",
15000))).longValue();
/**
+ * How long to wait for a server to respond to an initial client message.
+ */
+ private static final long HANDSHAKE_TIMEOUT =
+ ((Long) AccessController.doPrivileged(new GetLongAction(
+ "com.sun.jini.jeri.handshakeTimeout",
+ 15000))).longValue();
+ /**
* ConnectionManager logger.
*/
private static final Logger logger =
@@ -278,6 +289,7 @@ public final class ConnectionManager {
try {
mux = (c.getChannel() == null) ?
new OutboundMux(c) : new OutboundMux(c, true);
+ mux.setStartTimeout(HANDSHAKE_TIMEOUT);
} finally {
if (mux == null) {
try {
Modified: river/jtsk/branches/2.2/src/net/jini/loader/pref/PreferredClassProvider.java
URL: http://svn.apache.org/viewvc/river/jtsk/branches/2.2/src/net/jini/loader/pref/PreferredClassProvider.java?rev=1137619&r1=1137618&r2=1137619&view=diff
==============================================================================
--- river/jtsk/branches/2.2/src/net/jini/loader/pref/PreferredClassProvider.java (original)
+++ river/jtsk/branches/2.2/src/net/jini/loader/pref/PreferredClassProvider.java Mon Jun 20 13:00:41 2011
@@ -43,6 +43,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.WeakHashMap;
@@ -274,7 +276,7 @@ public class PreferredClassProvider exte
* references, so this table does not prevent loaders from being
* garbage collected.
*/
- private final Map loaderTable = new HashMap();
+ private final Map<LoaderKey,LoaderEntryHolder> loaderTable = new HashMap<LoaderKey,LoaderEntryHolder>();
/** reference queue for cleared class loader entries */
private final ReferenceQueue refQueue = new ReferenceQueue();
@@ -1556,31 +1558,55 @@ public class PreferredClassProvider exte
* }
*/
+ /*
+ * Take this opportunity to remove from the table entries
+ * whose weak references have been cleared.
+ */
+ List<LoaderKey> toRemove = new LinkedList<LoaderKey>();
+ Object ref;
+ while ((ref = refQueue.poll()) != null) {
+ if (ref instanceof LoaderKey)
+ toRemove.add((LoaderKey) ref);
+ else if (ref instanceof LoaderEntry) {
+ LoaderEntry entry = (LoaderEntry) ref;
+ if (!entry.removed) // ignore entries removed below
+ toRemove.add(entry.key);
+ }
+ }
+
+ LoaderKey key = new LoaderKey(urls, parent);
+ LoaderEntryHolder holder;
synchronized (loaderTable) {
- /*
- * Take this opportunity to remove from the table entries
- * whose weak references have been cleared.
- */
- Object ref;
- while ((ref = refQueue.poll()) != null) {
- if (ref instanceof LoaderKey) {
- LoaderKey key = (LoaderKey) ref;
- loaderTable.remove(key);
- } else if (ref instanceof LoaderEntry) {
- LoaderEntry entry = (LoaderEntry) ref;
- if (!entry.removed) { // ignore entries removed below
- loaderTable.remove(entry.key);
- }
- }
+ if (!toRemove.isEmpty()) {
+ for (LoaderKey oldKey : toRemove)
+ loaderTable.remove(oldKey);
+ toRemove.clear();
}
/*
* Look up the codebase URL path and parent class loader pair
* in the table of RMI class loaders.
*/
- LoaderKey key = new LoaderKey(urls, parent);
- LoaderEntry entry = (LoaderEntry) loaderTable.get(key);
+ holder = loaderTable.get(key);
+ if (null == holder) {
+ holder = new LoaderEntryHolder();
+ loaderTable.put(key, holder);
+ }
+ }
+ /*
+ * Four possible cases:
+ * 1) this is our first time creating this classloader
+ * - holder.entry is null, need to make a new entry and a new loader
+ * 2) we made this classloader before, but it was garbage collected a long while ago
+ * - identical to case #1 and it was reaped by the toRemove code above
+ * 3) we made this classloader before, and it was garbage collected recently
+ * - holder.entry is non-null, but holder.entry.get() is null, very similar to case #1
+ * 4) we made this classloader before, and it's still alive (CACHE HIT)
+ * - just return it
+ */
+ synchronized (holder) {
+ LoaderEntry entry = holder.entry;
ClassLoader loader;
if (entry == null ||
(loader = (ClassLoader) entry.get()) == null)
@@ -1593,7 +1619,6 @@ public class PreferredClassProvider exte
* from the weak reference queue.
*/
if (entry != null) {
- loaderTable.remove(key);
entry.removed = true;
}
@@ -1623,7 +1648,7 @@ public class PreferredClassProvider exte
* weak reference and store it in the table with the key.
*/
entry = new LoaderEntry(key, loader);
- loaderTable.put(key, entry);
+ holder.entry = entry;
}
return loader;
}
@@ -1737,6 +1762,9 @@ public class PreferredClassProvider exte
this.key = key;
}
}
+ private class LoaderEntryHolder {
+ public LoaderEntry entry;
+ }
private static ClassLoader getClassLoader(final Class c) {
return (ClassLoader) AccessController.doPrivileged(
Modified: river/jtsk/branches/2.2/test/src/com/sun/jini/outrigger/FastListTest.java
URL: http://svn.apache.org/viewvc/river/jtsk/branches/2.2/test/src/com/sun/jini/outrigger/FastListTest.java?rev=1137619&r1=1137618&r2=1137619&view=diff
==============================================================================
--- river/jtsk/branches/2.2/test/src/com/sun/jini/outrigger/FastListTest.java (original)
+++ river/jtsk/branches/2.2/test/src/com/sun/jini/outrigger/FastListTest.java Mon Jun 20 13:00:41 2011
@@ -29,7 +29,6 @@ public class FastListTest {
public void initialize() {
testee = new FastList<TestNode>();
rawTestee = new Iterable<TestNode>() {
- @Override
public Iterator<TestNode> iterator() {
return testee.rawIterator();
}
@@ -181,7 +180,6 @@ public class FastListTest {
private List<Thread> getAddThreads(final List<List<TestNode>> elements,
final CyclicBarrier barrier) {
NodeListRunnableFactory factory = new NodeListRunnableFactory() {
- @Override
public Runnable getRunnable(final List<TestNode> nodes) {
return new Runnable() {
public void run() {
@@ -207,7 +205,6 @@ public class FastListTest {
private List<Thread> getRemoveThreads(final List<List<TestNode>> elements,
final CyclicBarrier barrier) {
NodeListRunnableFactory factory = new NodeListRunnableFactory() {
- @Override
public Runnable getRunnable(final List<TestNode> nodes) {
return new Runnable() {
public void run() {
@@ -233,7 +230,6 @@ public class FastListTest {
private List<Thread> getRemoveAllThreads(final List<List<TestNode>> elements,
final CyclicBarrier barrier, final int tries) {
NodeListRunnableFactory factory = new NodeListRunnableFactory() {
- @Override
public Runnable getRunnable(final List<TestNode> nodes) {
return new Runnable() {
public void run() {