Package com.sun.jini.jeri.internal.runtime

Source Code of com.sun.jini.jeri.internal.runtime.ObjectTable$DgcServerImpl

/*
* 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.ImplRefManager.ImplRef;
import com.sun.jini.thread.NewThreadAction;
import java.rmi.Remote;
import java.rmi.server.ExportException;
import java.rmi.server.Unreferenced;
import java.security.AccessController;
import java.util.Arrays;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.id.Uuid;
import net.jini.jeri.RequestDispatcher;
import net.jini.security.Security;
import net.jini.security.SecurityContext;

/**
* A table of exported remote objects.
*
* @author Sun Microsystems, Inc.
**/
final class ObjectTable {

    private static final Logger logger =
  Logger.getLogger("net.jini.jeri.BasicJeriExporter");

    /**
     * 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;

    /** table of references to impls exported with DGC */
    private final ImplRefManager implRefManager;

    /** number of objects exported with keepAlive == true */
    private final JvmLifeSupport keepAliveCount;

    /** maps client ID to Lease (lock guards leaseChecker too) */
    private final ConcurrentMap<Uuid,Lease> leaseTable;

    /** thread to check for expired leases */
    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 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,
      boolean allowDGC,
      boolean keepAlive,
      Uuid id)
        throws ExportException
    {
  DgcRequestDispatcher[] rds = new DgcRequestDispatcher[requestDispatchers.length];
  for (int i = 0; i < requestDispatchers.length; i++) {
      rds[i] = getRD(requestDispatchers[i]);
  }
        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 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:
   */
  DgcRequestDispatcher rd = (DgcRequestDispatcher) requestDispatcher;
  if (!rd.forTable(this)) {
      throw new IllegalArgumentException(
    "request dispatcher for different object table");
  }
  return rd;
    }

    void registerTarget(Target target, Uuid clientID) {
        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) {
        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;
            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;              
                }
            }
  }
    }

    static class NoSuchObject extends Exception { }
}
TOP

Related Classes of com.sun.jini.jeri.internal.runtime.ObjectTable$DgcServerImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.