1 /*
   2  * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package sun.rmi.transport;
  26 
  27 import java.lang.ref.ReferenceQueue;
  28 import java.rmi.NoSuchObjectException;
  29 import java.rmi.Remote;
  30 import java.rmi.dgc.VMID;
  31 import java.rmi.server.ExportException;
  32 import java.rmi.server.ObjID;
  33 import java.security.AccessController;
  34 import java.security.PrivilegedAction;
  35 import java.util.HashMap;
  36 import java.util.Map;
  37 import sun.rmi.runtime.Log;
  38 import sun.rmi.runtime.NewThreadAction;
  39 
  40 /**
  41  * Object table shared by all implementors of the Transport interface.
  42  * This table maps object ids to remote object targets in this address
  43  * space.
  44  *
  45  * @author  Ann Wollrath
  46  * @author  Peter Jones
  47  */
  48 public final class ObjectTable {
  49 
  50     /** maximum interval between complete garbage collections of local heap */
  51     private final static long gcInterval =              // default 1 hour
  52         AccessController.doPrivileged((PrivilegedAction<Long>) () ->
  53             Long.getLong("sun.rmi.dgc.server.gcInterval", 3600000));
  54 
  55     /**
  56      * lock guarding objTable and implTable.
  57      * Holders MAY acquire a Target instance's lock or keepAliveLock.
  58      */
  59     private static final Object tableLock = new Object();
  60 
  61     /** tables mapping to Target, keyed from ObjectEndpoint and impl object */
  62     private static final Map<ObjectEndpoint,Target> objTable =
  63         new HashMap<>();
  64     private static final Map<WeakRef,Target> implTable =
  65         new HashMap<>();
  66 
  67     /**
  68      * lock guarding keepAliveCount, reaper, and gcLatencyRequest.
  69      * Holders may NOT acquire a Target instance's lock or tableLock.
  70      */
  71     private static final Object keepAliveLock = new Object();
  72 
  73     /** count of non-permanent objects in table or still processing calls */
  74     private static int keepAliveCount = 0;
  75 
  76     /** thread to collect unreferenced objects from table */
  77     private static Thread reaper = null;
  78 
  79     /** queue notified when weak refs in the table are cleared */
  80     static final ReferenceQueue<Object> reapQueue = new ReferenceQueue<>();
  81 
  82     /** handle for GC latency request (for future cancellation) */
  83     private static GC.LatencyRequest gcLatencyRequest = null;
  84 
  85     /*
  86      * Disallow anyone from creating one of these.
  87      */
  88     private ObjectTable() {}
  89 
  90     /**
  91      * Returns the target associated with the object id.
  92      */
  93     static Target getTarget(ObjectEndpoint oe) {
  94         synchronized (tableLock) {
  95             return objTable.get(oe);
  96         }
  97     }
  98 
  99     /**
 100      * Returns the target associated with the remote object
 101      */
 102     public static Target getTarget(Remote impl) {
 103         synchronized (tableLock) {
 104             return implTable.get(new WeakRef(impl));
 105         }
 106     }
 107 
 108     /**
 109      * Returns the stub for the remote object <b>obj</b> passed
 110      * as a parameter. This operation is only valid <i>after</i>
 111      * the object has been exported.
 112      *
 113      * @return the stub for the remote object, <b>obj</b>.
 114      * @exception NoSuchObjectException if the stub for the
 115      * remote object could not be found.
 116      */
 117     public static Remote getStub(Remote impl)
 118         throws NoSuchObjectException
 119     {
 120         Target target = getTarget(impl);
 121         if (target == null) {
 122             throw new NoSuchObjectException("object not exported");
 123         } else {
 124             return target.getStub();
 125         }
 126     }
 127 
 128    /**
 129     * Remove the remote object, obj, from the RMI runtime. If
 130     * successful, the object can no longer accept incoming RMI calls.
 131     * If the force parameter is true, the object is forcibly unexported
 132     * even if there are pending calls to the remote object or the
 133     * remote object still has calls in progress.  If the force
 134     * parameter is false, the object is only unexported if there are
 135     * no pending or in progress calls to the object.
 136     *
 137     * @param obj the remote object to be unexported
 138     * @param force if true, unexports the object even if there are
 139     * pending or in-progress calls; if false, only unexports the object
 140     * if there are no pending or in-progress calls
 141     * @return true if operation is successful, false otherwise
 142     * @exception NoSuchObjectException if the remote object is not
 143     * currently exported
 144     */
 145    public static boolean unexportObject(Remote obj, boolean force)
 146         throws java.rmi.NoSuchObjectException
 147     {
 148         synchronized (tableLock) {
 149             Target target = getTarget(obj);
 150             if (target == null) {
 151                 throw new NoSuchObjectException("object not exported");
 152             } else {
 153                 if (target.unexport(force)) {
 154                     removeTarget(target);
 155                     return true;
 156                 } else {
 157                     return false;
 158                 }
 159             }
 160         }
 161     }
 162 
 163     /**
 164      * Add target to object table.  If it is not a permanent entry, then
 165      * make sure that reaper thread is running to remove collected entries
 166      * and keep VM alive.
 167      */
 168     static void putTarget(Target target) throws ExportException {
 169         ObjectEndpoint oe = target.getObjectEndpoint();
 170         WeakRef weakImpl = target.getWeakImpl();
 171 
 172         if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) {
 173             DGCImpl.dgcLog.log(Log.VERBOSE, "add object " + oe);
 174         }
 175 
 176         synchronized (tableLock) {
 177             /**
 178              * Do nothing if impl has already been collected (see 6597112). Check while
 179              * holding tableLock to ensure that Reaper cannot process weakImpl in between
 180              * null check and put/increment effects.
 181              */
 182             if (target.getImpl() != null) {
 183                 if (objTable.containsKey(oe)) {
 184                     throw new ExportException(
 185                         "internal error: ObjID already in use");
 186                 } else if (implTable.containsKey(weakImpl)) {
 187                     throw new ExportException("object already exported");
 188                 }
 189 
 190                 objTable.put(oe, target);
 191                 implTable.put(weakImpl, target);
 192 
 193                 if (!target.isPermanent()) {
 194                     incrementKeepAliveCount();
 195                 }
 196             }
 197         }
 198     }
 199 
 200     /**
 201      * Remove target from object table.
 202      *
 203      * NOTE: This method must only be invoked while synchronized on
 204      * the "tableLock" object, because it does not do so itself.
 205      */
 206     private static void removeTarget(Target target) {
 207         // assert Thread.holdsLock(tableLock);
 208 
 209         ObjectEndpoint oe = target.getObjectEndpoint();
 210         WeakRef weakImpl = target.getWeakImpl();
 211 
 212         if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) {
 213             DGCImpl.dgcLog.log(Log.VERBOSE, "remove object " + oe);
 214         }
 215 
 216         objTable.remove(oe);
 217         implTable.remove(weakImpl);
 218 
 219         target.markRemoved();   // handles decrementing keep-alive count
 220     }
 221 
 222     /**
 223      * Process client VM signalling reference for given ObjID: forward to
 224      * corresponding Target entry.  If ObjID is not found in table,
 225      * no action is taken.
 226      */
 227     static void referenced(ObjID id, long sequenceNum, VMID vmid) {
 228         synchronized (tableLock) {
 229             ObjectEndpoint oe =
 230                 new ObjectEndpoint(id, Transport.currentTransport());
 231             Target target = objTable.get(oe);
 232             if (target != null) {
 233                 target.referenced(sequenceNum, vmid);
 234             }
 235         }
 236     }
 237 
 238     /**
 239      * Process client VM dropping reference for given ObjID: forward to
 240      * corresponding Target entry.  If ObjID is not found in table,
 241      * no action is taken.
 242      */
 243     static void unreferenced(ObjID id, long sequenceNum, VMID vmid,
 244                              boolean strong)
 245     {
 246         synchronized (tableLock) {
 247             ObjectEndpoint oe =
 248                 new ObjectEndpoint(id, Transport.currentTransport());
 249             Target target = objTable.get(oe);
 250             if (target != null)
 251                 target.unreferenced(sequenceNum, vmid, strong);
 252         }
 253     }
 254 
 255     /**
 256      * Increments the "keep-alive count".
 257      *
 258      * The "keep-alive count" is the number of non-permanent remote objects
 259      * that are either in the object table or still have calls in progress.
 260      * Therefore, this method should be invoked exactly once for every
 261      * non-permanent remote object exported (a remote object must be
 262      * exported before it can have any calls in progress).
 263      *
 264      * The VM is "kept alive" while the keep-alive count is greater than
 265      * zero; this is accomplished by keeping a non-daemon thread running.
 266      *
 267      * Because non-permanent objects are those that can be garbage
 268      * collected while exported, and thus those for which the "reaper"
 269      * thread operates, the reaper thread also serves as the non-daemon
 270      * VM keep-alive thread; a new reaper thread is created if necessary.
 271      */
 272     static void incrementKeepAliveCount() {
 273         synchronized (keepAliveLock) {
 274             keepAliveCount++;
 275 
 276             if (reaper == null) {
 277                 reaper = AccessController.doPrivileged(
 278                     new NewThreadAction(new Reaper(), "Reaper", false));
 279                 reaper.start();
 280             }
 281 
 282             /*
 283              * While there are non-"permanent" objects in the object table,
 284              * request a maximum latency for inspecting the entire heap
 285              * from the local garbage collector, to place an upper bound
 286              * on the time to discover remote objects that have become
 287              * unreachable (and thus can be removed from the table).
 288              */
 289             if (gcLatencyRequest == null) {
 290                 gcLatencyRequest = GC.requestLatency(gcInterval);
 291             }
 292         }
 293     }
 294 
 295     /**
 296      * Decrements the "keep-alive count".
 297      *
 298      * The "keep-alive count" is the number of non-permanent remote objects
 299      * that are either in the object table or still have calls in progress.
 300      * Therefore, this method should be invoked exactly once for every
 301      * previously-exported non-permanent remote object that both has been
 302      * removed from the object table and has no calls still in progress.
 303      *
 304      * If the keep-alive count is decremented to zero, then the current
 305      * reaper thread is terminated to cease keeping the VM alive (and
 306      * because there are no more non-permanent remote objects to reap).
 307      */
 308     static void decrementKeepAliveCount() {
 309         synchronized (keepAliveLock) {
 310             keepAliveCount--;
 311 
 312             if (keepAliveCount == 0) {
 313                 if (!(reaper != null)) { throw new AssertionError(); }
 314                 AccessController.doPrivileged(new PrivilegedAction<Void>() {
 315                     public Void run() {
 316                         reaper.interrupt();
 317                         return null;
 318                     }
 319                 });
 320                 reaper = null;
 321 
 322                 /*
 323                  * If there are no longer any non-permanent objects in the
 324                  * object table, we are no longer concerned with the latency
 325                  * of local garbage collection here.
 326                  */
 327                 gcLatencyRequest.cancel();
 328                 gcLatencyRequest = null;
 329             }
 330         }
 331     }
 332 
 333     /**
 334      * The Reaper thread waits for notifications that weak references in the
 335      * object table have been cleared.  When it receives a notification, it
 336      * removes the corresponding entry from the table.
 337      *
 338      * Since the Reaper is created as a non-daemon thread, it also serves
 339      * to keep the VM from exiting while there are objects in the table
 340      * (other than permanent entries that should neither be reaped nor
 341      * keep the VM alive).
 342      */
 343     private static class Reaper implements Runnable {
 344 
 345         public void run() {
 346             try {
 347                 do {
 348                     // wait for next cleared weak reference
 349                     WeakRef weakImpl = (WeakRef) reapQueue.remove();
 350 
 351                     synchronized (tableLock) {
 352                         Target target = implTable.get(weakImpl);
 353                         if (target != null) {
 354                             if (!target.isEmpty()) {
 355                                 throw new Error(
 356                                     "object with known references collected");
 357                             } else if (target.isPermanent()) {
 358                                 throw new Error("permanent object collected");
 359                             }
 360                             removeTarget(target);
 361                         }
 362                     }
 363                 } while (!Thread.interrupted());
 364             } catch (InterruptedException e) {
 365                 // pass away if interrupted
 366             }
 367         }
 368     }
 369 }