Path: blob/master/src/java.rmi/share/classes/sun/rmi/transport/DGCImpl.java
41154 views
/*1* Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package sun.rmi.transport;2526import java.io.ObjectInputFilter;27import java.net.SocketPermission;28import java.rmi.Remote;29import java.rmi.RemoteException;30import java.rmi.dgc.DGC;31import java.rmi.dgc.Lease;32import java.rmi.dgc.VMID;33import java.rmi.server.LogStream;34import java.rmi.server.ObjID;35import java.rmi.server.RemoteServer;36import java.rmi.server.ServerNotActiveException;37import java.rmi.server.UID;38import java.security.AccessControlContext;39import java.security.AccessController;40import java.security.Permissions;41import java.security.PrivilegedAction;42import java.security.ProtectionDomain;43import java.security.Security;44import java.util.ArrayList;45import java.util.HashSet;46import java.util.HashMap;47import java.util.Iterator;48import java.util.List;49import java.util.Map;50import java.util.Set;51import java.util.concurrent.Future;52import java.util.concurrent.ScheduledExecutorService;53import java.util.concurrent.TimeUnit;54import sun.rmi.runtime.Log;55import sun.rmi.runtime.RuntimeUtil;56import sun.rmi.server.UnicastRef;57import sun.rmi.server.UnicastServerRef;58import sun.rmi.server.Util;5960/**61* This class implements the guts of the server-side distributed GC62* algorithm63*64* @author Ann Wollrath65*/66@SuppressWarnings({"removal","deprecation"})67final class DGCImpl implements DGC {6869/* dgc system log */70static final Log dgcLog = Log.getLog("sun.rmi.dgc", "dgc",71LogStream.parseLevel(AccessController.doPrivileged(72(PrivilegedAction<String>) () -> System.getProperty("sun.rmi.dgc.logLevel"))));7374/** lease duration to grant to clients */75private static final long leaseValue = // default 10 minutes76AccessController.doPrivileged(77(PrivilegedAction<Long>) () -> Long.getLong("java.rmi.dgc.leaseValue", 600000));7879/** lease check interval; default is half of lease grant duration */80private static final long leaseCheckInterval =81AccessController.doPrivileged(82(PrivilegedAction<Long>) () -> Long.getLong("sun.rmi.dgc.checkInterval", leaseValue / 2));8384/** thread pool for scheduling delayed tasks */85private static final ScheduledExecutorService scheduler =86AccessController.doPrivileged(87new RuntimeUtil.GetInstanceAction()).getScheduler();8889/** remote implementation of DGC interface for this VM */90private static DGCImpl dgc;91/** table that maps VMID to LeaseInfo */92private Map<VMID,LeaseInfo> leaseTable = new HashMap<>();93/** checks for lease expiration */94private Future<?> checker = null;9596/**97* Return the remote implementation of the DGC interface for98* this VM.99*/100static DGCImpl getDGCImpl() {101return dgc;102}103104/**105* Property name of the DGC serial filter to augment106* the built-in list of allowed types.107* Setting the property in the {@code conf/security/java.security} file108* or system property will enable the augmented filter.109*/110private static final String DGC_FILTER_PROPNAME = "sun.rmi.transport.dgcFilter";111112/** Registry max depth of remote invocations. **/113private static int DGC_MAX_DEPTH = 5;114115/** Registry maximum array size in remote invocations. **/116private static int DGC_MAX_ARRAY_SIZE = 10000;117118/**119* The dgcFilter created from the value of the {@code "sun.rmi.transport.dgcFilter"}120* property.121*/122private static final ObjectInputFilter dgcFilter =123AccessController.doPrivileged((PrivilegedAction<ObjectInputFilter>)DGCImpl::initDgcFilter);124125/**126* Initialize the dgcFilter from the security properties or system property; if any127* @return an ObjectInputFilter, or null128*/129private static ObjectInputFilter initDgcFilter() {130ObjectInputFilter filter = null;131String props = System.getProperty(DGC_FILTER_PROPNAME);132if (props == null) {133props = Security.getProperty(DGC_FILTER_PROPNAME);134}135if (props != null) {136filter = ObjectInputFilter.Config.createFilter(props);137if (dgcLog.isLoggable(Log.BRIEF)) {138dgcLog.log(Log.BRIEF, "dgcFilter = " + filter);139}140}141return filter;142}143144/**145* Construct a new server-side remote object collector at146* a particular port. Disallow construction from outside.147*/148private DGCImpl() {}149150/**151* The dirty call adds the VMID "vmid" to the set of clients152* that hold references to the object associated with the ObjID153* id. The long "sequenceNum" is used to detect late dirty calls. If154* the VMID "vmid" is null, a VMID will be generated on the155* server (for use by the client in subsequent calls) and156* returned.157*158* The client must call the "dirty" method to renew the lease159* before the "lease" time expires or all references to remote160* objects in this VM that the client holds are considered161* "unreferenced".162*/163public Lease dirty(ObjID[] ids, long sequenceNum, Lease lease) {164VMID vmid = lease.getVMID();165/*166* The server specifies the lease value; the client has167* no say in the matter.168*/169long duration = leaseValue;170171if (dgcLog.isLoggable(Log.VERBOSE)) {172dgcLog.log(Log.VERBOSE, "vmid = " + vmid);173}174175// create a VMID if one wasn't supplied176if (vmid == null) {177vmid = new VMID();178179if (dgcLog.isLoggable(Log.BRIEF)) {180String clientHost;181try {182clientHost = RemoteServer.getClientHost();183} catch (ServerNotActiveException e) {184clientHost = "<unknown host>";185}186dgcLog.log(Log.BRIEF, " assigning vmid " + vmid +187" to client " + clientHost);188}189}190191lease = new Lease(vmid, duration);192// record lease information193synchronized (leaseTable) {194LeaseInfo info = leaseTable.get(vmid);195if (info == null) {196leaseTable.put(vmid, new LeaseInfo(vmid, duration));197if (checker == null) {198checker = scheduler.scheduleWithFixedDelay(199new Runnable() {200public void run() {201checkLeases();202}203},204leaseCheckInterval,205leaseCheckInterval, TimeUnit.MILLISECONDS);206}207} else {208info.renew(duration);209}210}211212for (ObjID id : ids) {213if (dgcLog.isLoggable(Log.VERBOSE)) {214dgcLog.log(Log.VERBOSE, "id = " + id +215", vmid = " + vmid + ", duration = " + duration);216}217218ObjectTable.referenced(id, sequenceNum, vmid);219}220221// return the VMID used222return lease;223}224225/**226* The clean call removes the VMID from the set of clients227* that hold references to the object associated with the LiveRef228* ref. The sequence number is used to detect late clean calls. If the229* argument "strong" is true, then the clean call is a result of a230* failed "dirty" call, thus the sequence number for the VMID needs231* to be remembered until the client goes away.232*/233public void clean(ObjID[] ids, long sequenceNum, VMID vmid, boolean strong)234{235for (ObjID id : ids) {236if (dgcLog.isLoggable(Log.VERBOSE)) {237dgcLog.log(Log.VERBOSE, "id = " + id +238", vmid = " + vmid + ", strong = " + strong);239}240241ObjectTable.unreferenced(id, sequenceNum, vmid, strong);242}243}244245/**246* Register interest in receiving a callback when this VMID247* becomes inaccessible.248*/249void registerTarget(VMID vmid, Target target) {250synchronized (leaseTable) {251LeaseInfo info = leaseTable.get(vmid);252if (info == null) {253target.vmidDead(vmid);254} else {255info.notifySet.add(target);256}257}258}259260/**261* Remove notification request.262*/263void unregisterTarget(VMID vmid, Target target) {264synchronized (leaseTable) {265LeaseInfo info = leaseTable.get(vmid);266if (info != null) {267info.notifySet.remove(target);268}269}270}271272/**273* Check if leases have expired. If a lease has expired, remove274* it from the table and notify all interested parties that the275* VMID is essentially "dead".276*277* @return if true, there are leases outstanding; otherwise leases278* no longer need to be checked279*/280private void checkLeases() {281long time = System.currentTimeMillis();282283/* List of vmids that need to be removed from the leaseTable */284List<LeaseInfo> toUnregister = new ArrayList<>();285286/* Build a list of leaseInfo objects that need to have287* targets removed from their notifySet. Remove expired288* leases from leaseTable.289*/290synchronized (leaseTable) {291Iterator<LeaseInfo> iter = leaseTable.values().iterator();292while (iter.hasNext()) {293LeaseInfo info = iter.next();294if (info.expired(time)) {295toUnregister.add(info);296iter.remove();297}298}299300if (leaseTable.isEmpty()) {301checker.cancel(false);302checker = null;303}304}305306/* Notify and unegister targets without holding the lock on307* the leaseTable so we avoid deadlock.308*/309for (LeaseInfo info : toUnregister) {310for (Target target : info.notifySet) {311target.vmidDead(info.vmid);312}313}314}315316static {317/*318* "Export" the singleton DGCImpl in a context isolated from319* the arbitrary current thread context.320*/321AccessController.doPrivileged(new PrivilegedAction<Void>() {322public Void run() {323ClassLoader savedCcl =324Thread.currentThread().getContextClassLoader();325try {326Thread.currentThread().setContextClassLoader(327ClassLoader.getSystemClassLoader());328329/*330* Put remote collector object in table by hand to prevent331* listen on port. (UnicastServerRef.exportObject would332* cause transport to listen.)333*/334try {335dgc = new DGCImpl();336ObjID dgcID = new ObjID(ObjID.DGC_ID);337LiveRef ref = new LiveRef(dgcID, 0);338UnicastServerRef disp = new UnicastServerRef(ref,339DGCImpl::checkInput);340Remote stub =341Util.createProxy(DGCImpl.class,342new UnicastRef(ref), true);343disp.setSkeleton(dgc);344345Permissions perms = new Permissions();346perms.add(new SocketPermission("*", "accept,resolve"));347ProtectionDomain[] pd = { new ProtectionDomain(null, perms) };348AccessControlContext acceptAcc = new AccessControlContext(pd);349350Target target = AccessController.doPrivileged(351new PrivilegedAction<Target>() {352public Target run() {353return new Target(dgc, disp, stub, dgcID, true);354}355}, acceptAcc);356357ObjectTable.putTarget(target);358} catch (RemoteException e) {359throw new Error(360"exception initializing server-side DGC", e);361}362} finally {363Thread.currentThread().setContextClassLoader(savedCcl);364}365return null;366}367});368}369370/**371* ObjectInputFilter to filter DGC input objects.372* The list of acceptable classes is very short and explicit.373* The depth and array sizes are limited.374*375* @param filterInfo access to class, arrayLength, etc.376* @return {@link ObjectInputFilter.Status#ALLOWED} if allowed,377* {@link ObjectInputFilter.Status#REJECTED} if rejected,378* otherwise {@link ObjectInputFilter.Status#UNDECIDED}379*/380private static ObjectInputFilter.Status checkInput(ObjectInputFilter.FilterInfo filterInfo) {381if (dgcFilter != null) {382ObjectInputFilter.Status status = dgcFilter.checkInput(filterInfo);383if (status != ObjectInputFilter.Status.UNDECIDED) {384// The DGC filter can override the built-in allow-list385return status;386}387}388389if (filterInfo.depth() > DGC_MAX_DEPTH) {390return ObjectInputFilter.Status.REJECTED;391}392Class<?> clazz = filterInfo.serialClass();393if (clazz != null) {394while (clazz.isArray()) {395if (filterInfo.arrayLength() >= 0 && filterInfo.arrayLength() > DGC_MAX_ARRAY_SIZE) {396return ObjectInputFilter.Status.REJECTED;397}398// Arrays are allowed depending on the component type399clazz = clazz.getComponentType();400}401if (clazz.isPrimitive()) {402// Arrays of primitives are allowed403return ObjectInputFilter.Status.ALLOWED;404}405return (clazz == ObjID.class ||406clazz == UID.class ||407clazz == VMID.class ||408clazz == Lease.class)409? ObjectInputFilter.Status.ALLOWED410: ObjectInputFilter.Status.REJECTED;411}412// Not a class, not size limited413return ObjectInputFilter.Status.UNDECIDED;414}415416417private static class LeaseInfo {418VMID vmid;419long expiration;420Set<Target> notifySet = new HashSet<>();421422LeaseInfo(VMID vmid, long lease) {423this.vmid = vmid;424expiration = System.currentTimeMillis() + lease;425}426427synchronized void renew(long lease) {428long newExpiration = System.currentTimeMillis() + lease;429if (newExpiration > expiration)430expiration = newExpiration;431}432433boolean expired(long time) {434if (expiration < time) {435if (dgcLog.isLoggable(Log.BRIEF)) {436dgcLog.log(Log.BRIEF, vmid.toString());437}438return true;439} else {440return false;441}442}443}444}445446447