1 /*
   2  * Copyright (c) 1996, 2005, 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.rmi.Remote;
  28 import java.rmi.RemoteException;
  29 import java.rmi.dgc.DGC;
  30 import java.rmi.dgc.Lease;
  31 import java.rmi.dgc.VMID;
  32 import java.rmi.server.LogStream;
  33 import java.rmi.server.ObjID;
  34 import java.rmi.server.RemoteServer;
  35 import java.rmi.server.ServerNotActiveException;
  36 import java.security.AccessController;
  37 import java.security.PrivilegedAction;
  38 import java.util.ArrayList;
  39 import java.util.HashSet;
  40 import java.util.HashMap;
  41 import java.util.Iterator;
  42 import java.util.List;
  43 import java.util.Map;
  44 import java.util.Set;
  45 import java.util.concurrent.Future;
  46 import java.util.concurrent.ScheduledExecutorService;
  47 import java.util.concurrent.TimeUnit;
  48 import sun.rmi.runtime.Log;
  49 import sun.rmi.runtime.RuntimeUtil;
  50 import sun.rmi.server.UnicastRef;
  51 import sun.rmi.server.UnicastServerRef;
  52 import sun.rmi.server.Util;
  53 import sun.security.action.GetLongAction;
  54 import sun.security.action.GetPropertyAction;
  55 
  56 /**
  57  * This class implements the guts of the server-side distributed GC
  58  * algorithm
  59  *
  60  * @author Ann Wollrath
  61  */
  62 final class DGCImpl implements DGC {
  63 
  64     /* dgc system log */
  65     static final Log dgcLog = Log.getLog("sun.rmi.dgc", "dgc",
  66         LogStream.parseLevel(AccessController.doPrivileged(
  67             new GetPropertyAction("sun.rmi.dgc.logLevel"))));
  68 
  69     /** lease duration to grant to clients */
  70     private static final long leaseValue =              // default 10 minutes
  71         AccessController.doPrivileged(
  72             new GetLongAction("java.rmi.dgc.leaseValue", 600000));
  73 
  74     /** lease check interval; default is half of lease grant duration */
  75     private static final long leaseCheckInterval =
  76         AccessController.doPrivileged(
  77             new GetLongAction("sun.rmi.dgc.checkInterval", leaseValue / 2));
  78 
  79     /** thread pool for scheduling delayed tasks */
  80     private static final ScheduledExecutorService scheduler =
  81         AccessController.doPrivileged(
  82             new RuntimeUtil.GetInstanceAction()).getScheduler();
  83 
  84     /** remote implementation of DGC interface for this VM */
  85     private static DGCImpl dgc;
  86     /** table that maps VMID to LeaseInfo */
  87     private Map<VMID,LeaseInfo> leaseTable = new HashMap<>();
  88     /** checks for lease expiration */
  89     private Future<?> checker = null;
  90 
  91     /**
  92      * Return the remote implementation of the DGC interface for
  93      * this VM.
  94      */
  95     static DGCImpl getDGCImpl() {
  96         return dgc;
  97     }
  98 
  99     /**
 100      * Construct a new server-side remote object collector at
 101      * a particular port. Disallow construction from outside.
 102      */
 103     private DGCImpl() {}
 104 
 105     /**
 106      * The dirty call adds the VMID "vmid" to the set of clients
 107      * that hold references to the object associated with the ObjID
 108      * id.  The long "sequenceNum" is used to detect late dirty calls.  If
 109      * the VMID "vmid" is null, a VMID will be generated on the
 110      * server (for use by the client in subsequent calls) and
 111      * returned.
 112      *
 113      * The client must call the "dirty" method to renew the lease
 114      * before the "lease" time expires or all references to remote
 115      * objects in this VM that the client holds are considered
 116      * "unreferenced".
 117      */
 118     public Lease dirty(ObjID[] ids, long sequenceNum, Lease lease) {
 119         VMID vmid = lease.getVMID();
 120         /*
 121          * The server specifies the lease value; the client has
 122          * no say in the matter.
 123          */
 124         long duration = leaseValue;
 125 
 126         if (dgcLog.isLoggable(Log.VERBOSE)) {
 127             dgcLog.log(Log.VERBOSE, "vmid = " + vmid);
 128         }
 129 
 130         // create a VMID if one wasn't supplied
 131         if (vmid == null) {
 132             vmid = new VMID();
 133 
 134             if (dgcLog.isLoggable(Log.BRIEF)) {
 135                 String clientHost;
 136                 try {
 137                     clientHost = RemoteServer.getClientHost();
 138                 } catch (ServerNotActiveException e) {
 139                     clientHost = "<unknown host>";
 140                 }
 141                 dgcLog.log(Log.BRIEF, " assigning vmid " + vmid +
 142                            " to client " + clientHost);
 143             }
 144         }
 145 
 146         lease = new Lease(vmid, duration);
 147         // record lease information
 148         synchronized (leaseTable) {
 149             LeaseInfo info = leaseTable.get(vmid);
 150             if (info == null) {
 151                 leaseTable.put(vmid, new LeaseInfo(vmid, duration));
 152                 if (checker == null) {
 153                     checker = scheduler.scheduleWithFixedDelay(
 154                         new Runnable() {
 155                             public void run() {
 156                                 checkLeases();
 157                             }
 158                         },
 159                         leaseCheckInterval,
 160                         leaseCheckInterval, TimeUnit.MILLISECONDS);
 161                 }
 162             } else {
 163                 info.renew(duration);
 164             }
 165         }
 166 
 167         for (ObjID id : ids) {
 168             if (dgcLog.isLoggable(Log.VERBOSE)) {
 169                 dgcLog.log(Log.VERBOSE, "id = " + id +
 170                            ", vmid = " + vmid + ", duration = " + duration);
 171             }
 172 
 173             ObjectTable.referenced(id, sequenceNum, vmid);
 174         }
 175 
 176         // return the VMID used
 177         return lease;
 178     }
 179 
 180     /**
 181      * The clean call removes the VMID from the set of clients
 182      * that hold references to the object associated with the LiveRef
 183      * ref.  The sequence number is used to detect late clean calls.  If the
 184      * argument "strong" is true, then the clean call is a result of a
 185      * failed "dirty" call, thus the sequence number for the VMID needs
 186      * to be remembered until the client goes away.
 187      */
 188     public void clean(ObjID[] ids, long sequenceNum, VMID vmid, boolean strong)
 189     {
 190         for (ObjID id : ids) {
 191             if (dgcLog.isLoggable(Log.VERBOSE)) {
 192                 dgcLog.log(Log.VERBOSE, "id = " + id +
 193                     ", vmid = " + vmid + ", strong = " + strong);
 194             }
 195 
 196             ObjectTable.unreferenced(id, sequenceNum, vmid, strong);
 197         }
 198     }
 199 
 200     /**
 201      * Register interest in receiving a callback when this VMID
 202      * becomes inaccessible.
 203      */
 204     void registerTarget(VMID vmid, Target target) {
 205         synchronized (leaseTable) {
 206             LeaseInfo info = leaseTable.get(vmid);
 207             if (info == null) {
 208                 target.vmidDead(vmid);
 209             } else {
 210                 info.notifySet.add(target);
 211             }
 212         }
 213     }
 214 
 215     /**
 216      * Remove notification request.
 217      */
 218     void unregisterTarget(VMID vmid, Target target) {
 219         synchronized (leaseTable) {
 220             LeaseInfo info = leaseTable.get(vmid);
 221             if (info != null) {
 222                 info.notifySet.remove(target);
 223             }
 224         }
 225     }
 226 
 227     /**
 228      * Check if leases have expired.  If a lease has expired, remove
 229      * it from the table and notify all interested parties that the
 230      * VMID is essentially "dead".
 231      *
 232      * @return if true, there are leases outstanding; otherwise leases
 233      * no longer need to be checked
 234      */
 235     private void checkLeases() {
 236         long time = System.currentTimeMillis();
 237 
 238         /* List of vmids that need to be removed from the leaseTable */
 239         List<LeaseInfo> toUnregister = new ArrayList<>();
 240 
 241         /* Build a list of leaseInfo objects that need to have
 242          * targets removed from their notifySet.  Remove expired
 243          * leases from leaseTable.
 244          */
 245         synchronized (leaseTable) {
 246             Iterator<LeaseInfo> iter = leaseTable.values().iterator();
 247             while (iter.hasNext()) {
 248                 LeaseInfo info = iter.next();
 249                 if (info.expired(time)) {
 250                     toUnregister.add(info);
 251                     iter.remove();
 252                 }
 253             }
 254 
 255             if (leaseTable.isEmpty()) {
 256                 checker.cancel(false);
 257                 checker = null;
 258             }
 259         }
 260 
 261         /* Notify and unegister targets without holding the lock on
 262          * the leaseTable so we avoid deadlock.
 263          */
 264         for (LeaseInfo info : toUnregister) {
 265             for (Target target : info.notifySet) {
 266                 target.vmidDead(info.vmid);
 267             }
 268         }
 269     }
 270 
 271     static {
 272         /*
 273          * "Export" the singleton DGCImpl in a context isolated from
 274          * the arbitrary current thread context.
 275          */
 276         AccessController.doPrivileged(new PrivilegedAction<Void>() {
 277             public Void run() {
 278                 ClassLoader savedCcl =
 279                     Thread.currentThread().getContextClassLoader();
 280                 try {
 281                     Thread.currentThread().setContextClassLoader(
 282                         ClassLoader.getSystemClassLoader());
 283 
 284                     /*
 285                      * Put remote collector object in table by hand to prevent
 286                      * listen on port.  (UnicastServerRef.exportObject would
 287                      * cause transport to listen.)
 288                      */
 289                     try {
 290                         dgc = new DGCImpl();
 291                         ObjID dgcID = new ObjID(ObjID.DGC_ID);
 292                         LiveRef ref = new LiveRef(dgcID, 0);
 293                         UnicastServerRef disp = new UnicastServerRef(ref);
 294                         Remote stub =
 295                             Util.createProxy(DGCImpl.class,
 296                                              new UnicastRef(ref), true);
 297                         disp.setSkeleton(dgc);
 298                         Target target =
 299                             new Target(dgc, disp, stub, dgcID, true);
 300                         ObjectTable.putTarget(target);
 301                     } catch (RemoteException e) {
 302                         throw new Error(
 303                             "exception initializing server-side DGC", e);
 304                     }
 305                 } finally {
 306                     Thread.currentThread().setContextClassLoader(savedCcl);
 307                 }
 308                 return null;
 309             }
 310         });
 311     }
 312 
 313     private static class LeaseInfo {
 314         VMID vmid;
 315         long expiration;
 316         Set<Target> notifySet = new HashSet<>();
 317 
 318         LeaseInfo(VMID vmid, long lease) {
 319             this.vmid = vmid;
 320             expiration = System.currentTimeMillis() + lease;
 321         }
 322 
 323         synchronized void renew(long lease) {
 324             long newExpiration = System.currentTimeMillis() + lease;
 325             if (newExpiration > expiration)
 326                 expiration = newExpiration;
 327         }
 328 
 329         boolean expired(long time) {
 330             if (expiration < time) {
 331                 if (dgcLog.isLoggable(Log.BRIEF)) {
 332                     dgcLog.log(Log.BRIEF, vmid.toString());
 333                 }
 334                 return true;
 335             } else {
 336                 return false;
 337             }
 338         }
 339     }
 340 }