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() {
