提交 dee38dc3 编写于 作者: C Chuansheng Lu 提交者: Jonathan Lu

[MultiTenant] Added TenantDataIsolation feature

Summary: ported TenantDataIsolation to Dragonwell

Test Plan: jdk/test/multi-tenant

Reviewed-by: yuleil, superajun-wsj

Issue: https://github.com/alibaba/dragonwell8/issues/84
上级 2acf165b
package com.alibaba.tenant;
public class FieldReference<T> {
private T referent;
/**
* Constructs a new FieldReference with r as the referent
* @param r The object to which this FieldReference points
*/
public FieldReference(T r) {
this.referent = r;
}
/**
* Returns this FieldReference referent
* @return The referent
*/
public T get() {
return referent;
}
/**
* Sets {@code referent}
* @param t The new referent value
*/
public void set(Object t) {
referent = (T)t;
}
}
\ No newline at end of file
......@@ -36,6 +36,8 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.alibaba.rcm.ResourceContainer;
import com.alibaba.rcm.Constraint;
import com.alibaba.rcm.internal.AbstractResourceContainer;
import sun.misc.SharedSecrets;
import sun.misc.TenantAccess;
import static com.alibaba.tenant.TenantState.*;
......@@ -136,11 +138,24 @@ public class TenantContainer {
return totalThreadsAllocatedMemory + accumulatedMemory;
}
/**
* Data repository used to store the data isolated per tenant.
*/
private TenantData tenantData = new TenantData();
/**
* Used to track and run tenant shutdown hooks
*/
private TenantShutdownHooks tenantShutdownHooks = new TenantShutdownHooks();
/**
* Retrieves the data repository used by this tenant.
* @return the data repository associated with this tenant.
*/
public TenantData getTenantData() {
return tenantData;
}
/**
* Sets the tenant properties to the one specified by argument.
* @param props the properties to be set, CoW the system properties if it is null.
......@@ -266,6 +281,7 @@ public class TenantContainer {
// clear references
spawnedThreads.clear();
attachedThreads.clear();
tenantData.clear();
tenantShutdownHooks = null;
}
......@@ -584,6 +600,18 @@ public class TenantContainer {
//Initialize this field after the system is booted.
tenantContainerMap = Collections.synchronizedMap(new HashMap());
// initialize TenantAccess
if (SharedSecrets.getTenantAccess() == null) {
SharedSecrets.setTenantAccess(new TenantAccess() {
@Override
public void registerServiceThread(TenantContainer tenant, Thread thread) {
if (tenant != null && thread != null) {
tenant.addServiceThread(thread);
}
}
});
}
try {
// force initialization of TenantConfiguration
Class.forName("com.alibaba.tenant.TenantConfiguration");
......@@ -605,6 +633,31 @@ public class TenantContainer {
return obj != null ? nd.containerOf(obj) : null;
}
/**
* Gets the field value stored in the data repository of this tenant, which is same to call the
* {@code TenantData.getFieldValue} on the tenant data object retrieved by {@code TenantContainer.getTenantData}.
*
* @param obj Object the field associates with
* @param fieldName Field name
* @param supplier Responsible for creating the initial field value
* @return Value of field.
*/
public <K, T> T getFieldValue(K obj, String fieldName, Supplier<T> supplier) {
return tenantData.getFieldValue(obj, fieldName, supplier);
}
/**
* Gets the field value stored in the data repository of this tenant, which is same to call the
* {@code TenantData.getFieldValue} on the tenant data object retrieved by {@code TenantContainer.getTenantData}.
*
* @param obj Object the field associates with
* @param fieldName Field name
* @return Value of field, null if not found
*/
public <K, T> T getFieldValue(K obj, String fieldName) {
return getFieldValue(obj, fieldName, () -> null);
}
/**
* Runs {@code Supplier.get} in the root tenant.
* @param supplier target used to call
......
package com.alibaba.tenant;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.function.Supplier;
/**
* Used to store all the data isolated per tenant.
*/
public class TenantData {
private Map<Object, Map<String, FieldReference<?>>> dataMap = new WeakHashMap<>();
private Map<String, FieldReference<?>> mapForObject(Object obj) {
Map<String, FieldReference<?>> map = null;
if (obj != null) {
map = dataMap.get(obj);
if (null == map) {
map = new HashMap<>();
dataMap.put(obj, map);
}
}
return map;
}
/**
* Retrieves the value of {@code obj}'s field isolated per tenant
* @param obj Object the field associates with
* @param fieldName Field name
* @param supplier Responsible for creating the initial field value
* @return Value of field.
*/
@SuppressWarnings("unchecked")
public synchronized <K, T> T getFieldValue(K obj, String fieldName, Supplier<T> supplier) {
if(null == obj || null == fieldName || null == supplier) {
throw new IllegalArgumentException("Failed to get the field value, argument is null.");
}
Map<String, FieldReference<?>> objMap = mapForObject(obj);
FieldReference<?> data = objMap.get(fieldName);
if(null == data) {
T initialValue = supplier.get();
// Only store non-null value to the map
if (initialValue != null) {
data = new FieldReference<T>(initialValue);
objMap.put(fieldName, data);
}
}
return (data == null ? null : (T)data.get());
}
/**
* Retrieves the value of {@code obj}'s field isolated per tenant
* @param obj Object the field associates with
* @param fieldName Field name
* @return Value of field, null if not found
*/
public synchronized <K, T> T getFieldValue(K obj, String fieldName) {
return getFieldValue(obj, fieldName, () -> null);
}
/**
* Sets the value for tenant isolated field
* @param key Object associated with field.
* @param fieldName name of field.
* @param value new field value
*/
public synchronized <K, T> void setFieldValue(K key, String fieldName, T value) {
if(null == key || null == fieldName) {
throw new IllegalArgumentException("Failed to get the field value, argument is null.");
}
Map<String, FieldReference<?>> objMap = mapForObject(key);
FieldReference<?> ref = objMap.get(fieldName);
if(null != ref) {
ref.set(value);
} else {
objMap.put(fieldName, new FieldReference<>(value));
}
}
/**
* Clear all recorded isolation data in this tenant
*/
void clear() {
dataMap.clear();
}
}
......@@ -51,6 +51,11 @@ public class TenantGlobals {
*/
public static final int TENANT_FLAG_CPU_THROTTLING_ENABLED = 0x4;
/**
* Bit to indicate that if data isolation feature is enabled
*/
public static final int TENANT_FLAG_DATA_ISOLATION_ENABLED = 0x8;
/**
* Bit to indicate that if cpu accounting feature is enabled
*/
......@@ -103,4 +108,12 @@ public class TenantGlobals {
public static boolean isCpuAccountingEnabled() {
return 0 != (flags & TENANT_FLAG_CPU_ACCOUNTING_ENABLED);
}
/**
* Test if data isolation feature is enabled.
* @return true if enabled otherwise false
*/
public static boolean isDataIsolationEnabled() {
return 0 != (flags & TENANT_FLAG_DATA_ISOLATION_ENABLED);
}
}
......@@ -25,6 +25,9 @@
package java.lang;
import com.alibaba.tenant.TenantContainer;
import com.alibaba.tenant.TenantGlobals;
import java.lang.ClassValue.ClassValueMap;
import java.util.WeakHashMap;
import java.lang.ref.WeakReference;
......@@ -186,7 +189,13 @@ public abstract class ClassValue<T> {
/** Return the cache, if it exists, else a dummy empty cache. */
private static Entry<?>[] getCacheCarefully(Class<?> type) {
// racing type.classValueMap{.cacheArray} : null => new Entry[X] <=> new Entry[Y]
ClassValueMap map = type.classValueMap;
ClassValueMap map = null;
if (TenantGlobals.isDataIsolationEnabled() && TenantContainer.current() != null) {
map = TenantContainer.current().getFieldValue(type, "classValueMap",
() -> new ClassValueMap(type));
} else {
map = type.classValueMap;
}
if (map == null) return EMPTY_CACHE;
Entry<?>[] cache = map.getCache();
return cache;
......@@ -364,7 +373,13 @@ public abstract class ClassValue<T> {
// racing type.classValueMap : null (blank) => unique ClassValueMap
// if a null is observed, a map is created (lazily, synchronously, uniquely)
// all further access to that map is synchronized
ClassValueMap map = type.classValueMap;
ClassValueMap map = null;
if (TenantGlobals.isDataIsolationEnabled() && TenantContainer.current() != null) {
map = TenantContainer.current().getFieldValue(type, "classValueMap",
() -> new ClassValueMap(type));
} else {
map = type.classValueMap;
}
if (map != null) return map;
return initializeMap(type);
}
......@@ -374,9 +389,15 @@ public abstract class ClassValue<T> {
ClassValueMap map;
synchronized (CRITICAL_SECTION) { // private object to avoid deadlocks
// happens about once per type
if ((map = type.classValueMap) == null)
if (TenantGlobals.isDataIsolationEnabled() && TenantContainer.current() != null) {
type.classValueMap = map = TenantContainer.current()
.getFieldValue(type, "classValueMap", () -> new ClassValueMap(type));
} else {
if ((map = type.classValueMap) == null) {
type.classValueMap = map = new ClassValueMap(type);
}
}
}
return map;
}
......
......@@ -28,6 +28,10 @@ package java.lang;
import java.io.*;
import java.util.StringTokenizer;
import com.alibaba.tenant.TenantContainer;
import com.alibaba.tenant.TenantGlobals;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;
......@@ -209,8 +213,12 @@ public class Runtime {
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
if(TenantGlobals.isDataIsolationEnabled() && null != TenantContainer.current()) {
TenantContainer.current().addShutdownHook(hook);
} else {
ApplicationShutdownHooks.add(hook);
}
}
/**
* De-registers a previously-registered virtual-machine shutdown hook. <p>
......@@ -237,8 +245,12 @@ public class Runtime {
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
if(TenantGlobals.isDataIsolationEnabled() && null != TenantContainer.current()) {
return TenantContainer.current().removeShutdownHook(hook);
} else {
return ApplicationShutdownHooks.remove(hook);
}
}
/**
* Forcibly terminates the currently running Java virtual machine. This
......
......@@ -38,6 +38,9 @@ import java.security.AllPermission;
import java.nio.channels.Channel;
import java.nio.channels.spi.SelectorProvider;
import com.alibaba.rcm.internal.AbstractResourceContainer;
import com.alibaba.tenant.TenantContainer;
import com.alibaba.tenant.TenantGlobals;
import sun.misc.VM;
import sun.nio.ch.Interruptible;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;
......@@ -632,9 +635,12 @@ public final class System {
if (sm != null) {
sm.checkPropertiesAccess();
}
if(TenantGlobals.isDataIsolationEnabled() && null != TenantContainer.current()) {
return TenantContainer.current().getProperties();
} else {
return props;
}
}
/**
* Returns the system-dependent line separator string. It always
......@@ -684,8 +690,13 @@ public final class System {
props = new Properties();
initProperties(props);
}
if (VM.isBooted() && TenantGlobals.isDataIsolationEnabled()
&& null != TenantContainer.current()) {
TenantContainer.current().setProperties(props);
} else {
System.props = props;
}
}
/**
* Gets the system property indicated by the specified key.
......@@ -720,8 +731,13 @@ public final class System {
sm.checkPropertyAccess(key);
}
if(VM.isBooted() && TenantGlobals.isDataIsolationEnabled()
&& null != TenantContainer.current()) {
return TenantContainer.current().getProperty(key);
} else {
return props.getProperty(key);
}
}
/**
* Gets the system property indicated by the specified key.
......@@ -756,8 +772,13 @@ public final class System {
sm.checkPropertyAccess(key);
}
if (VM.isBooted() && TenantGlobals.isDataIsolationEnabled()
&& null != TenantContainer.current()) {
return TenantContainer.current().getProperties().getProperty(key, def);
} else {
return props.getProperty(key, def);
}
}
/**
* Sets the system property indicated by the specified key.
......@@ -796,8 +817,13 @@ public final class System {
SecurityConstants.PROPERTY_WRITE_ACTION));
}
if(VM.isBooted() && TenantGlobals.isDataIsolationEnabled()
&& null != TenantContainer.current()) {
return (String) TenantContainer.current().setProperty(key, value);
} else {
return (String) props.setProperty(key, value);
}
}
/**
* Removes the system property indicated by the specified key.
......@@ -833,8 +859,12 @@ public final class System {
sm.checkPermission(new PropertyPermission(key, "write"));
}
if(TenantGlobals.isDataIsolationEnabled() && null != TenantContainer.current()) {
return (String) TenantContainer.current().clearProperty(key);
} else {
return (String) props.remove(key);
}
}
private static void checkKey(String key) {
if (key == null) {
......
......@@ -30,7 +30,6 @@ import javax.management.MBeanServerConnection;
import javax.management.MBeanServerFactory;
import javax.management.MBeanServerPermission;
import javax.management.NotificationEmitter;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
......@@ -39,6 +38,8 @@ import javax.management.MBeanRegistrationException;
import javax.management.NotCompliantMBeanException;
import javax.management.StandardEmitterMBean;
import javax.management.StandardMBean;
import com.alibaba.tenant.TenantContainer;
import com.alibaba.tenant.TenantGlobals;
import java.util.Collections;
import java.util.List;
import java.util.Set;
......@@ -465,8 +466,22 @@ public class ManagementFactory {
sm.checkPermission(perm);
}
MBeanServer thePlatformMBeanServer= null;
if(TenantGlobals.isDataIsolationEnabled() && null != TenantContainer.current()) {
thePlatformMBeanServer = TenantContainer.current().getFieldValue(ManagementFactory.class, "platformMBeanServer",
ManagementFactory::createPlatformMBeanServer);
} else {
//use the value in root.
if (platformMBeanServer == null) {
platformMBeanServer = MBeanServerFactory.createMBeanServer();
platformMBeanServer = createPlatformMBeanServer();
}
thePlatformMBeanServer = platformMBeanServer;
}
return thePlatformMBeanServer;
}
private static MBeanServer createPlatformMBeanServer() {
MBeanServer platformMBeanServer = MBeanServerFactory.createMBeanServer();
for (PlatformComponent pc : PlatformComponent.values()) {
List<? extends PlatformManagedObject> list =
pc.getMXBeans(pc.getMXBeanInterface());
......@@ -496,7 +511,6 @@ public class ManagementFactory {
addMXBean(platformMBeanServer, o);
}
}
}
return platformMBeanServer;
}
......
......@@ -27,9 +27,14 @@ package java.lang.ref;
import java.security.PrivilegedAction;
import java.security.AccessController;
import com.alibaba.tenant.TenantState;
import sun.misc.JavaLangAccess;
import sun.misc.SharedSecrets;
import sun.misc.VM;
import com.alibaba.tenant.TenantContainer;
import com.alibaba.tenant.TenantData;
import com.alibaba.tenant.TenantGlobals;
final class Finalizer extends FinalReference<Object> { /* Package-private; must be in
same package as the Reference
......@@ -39,16 +44,48 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must
private static Finalizer unfinalized = null;
private static final Object lock = new Object();
private static final String ID_UNFINALIZED = "unfinalized";
private Finalizer
next = null,
prev = null;
private static ReferenceQueue<Object> initTenantReferenceQueue() {
if (!TenantGlobals.isDataIsolationEnabled() || TenantContainer.current() == null) {
throw new UnsupportedOperationException();
}
// spawn a new finalizer thread for this tenant, put it into system thread group
// will be terminated after destruction of tenant container
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread finalizer = new FinalizerThread(tg);
SharedSecrets.getTenantAccess()
.registerServiceThread(TenantContainer.current(), finalizer);
finalizer.setName("TenantFinalizer-" + TenantContainer.current().getTenantId());
finalizer.setPriority(Thread.MAX_PRIORITY - 2);
finalizer.setDaemon(true);
finalizer.start();
return new ReferenceQueue<>();
}
private boolean hasBeenFinalized() {
return (next == this);
}
private void add() {
synchronized (lock) {
if (VM.isBooted() && TenantGlobals.isDataIsolationEnabled() && TenantContainer.current() != null) {
TenantData td = TenantContainer.current().getTenantData();
Finalizer tenantUnfinalized = td.getFieldValue(Finalizer.class, ID_UNFINALIZED);
if (tenantUnfinalized != null) {
this.next = tenantUnfinalized;
tenantUnfinalized.prev = this;
}
td.setFieldValue(Finalizer.class, ID_UNFINALIZED, this);
} else {
if (unfinalized != null) {
this.next = unfinalized;
unfinalized.prev = this;
......@@ -56,9 +93,20 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must
unfinalized = this;
}
}
}
private void remove() {
synchronized (lock) {
if (TenantGlobals.isDataIsolationEnabled() && TenantContainer.current() != null) {
TenantData td = TenantContainer.current().getTenantData();
if (td.getFieldValue(Finalizer.class, ID_UNFINALIZED) == this) {
if (this.next != null) {
td.setFieldValue(Finalizer.class, ID_UNFINALIZED, this.next);
} else {
td.setFieldValue(Finalizer.class, ID_UNFINALIZED, this.prev);
}
}
} else {
if (unfinalized == this) {
if (this.next != null) {
unfinalized = this.next;
......@@ -66,6 +114,7 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must
unfinalized = this.prev;
}
}
}
if (this.next != null) {
this.next.prev = this.prev;
}
......@@ -78,13 +127,18 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must
}
private Finalizer(Object finalizee) {
super(finalizee, queue);
super(finalizee, getQueue());
add();
}
static ReferenceQueue<Object> getQueue() {
if (VM.isBooted() && TenantGlobals.isDataIsolationEnabled() && TenantContainer.current() != null) {
return TenantContainer.current().getFieldValue(Finalizer.class, "queue",
Finalizer::initTenantReferenceQueue);
} else {
return queue;
}
}
/* Invoked by VM */
static void register(Object finalizee) {
......@@ -131,6 +185,12 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread sft = new Thread(tg, proc, "Secondary finalizer");
if (TenantGlobals.isDataIsolationEnabled() && TenantContainer.current() != null) {
SharedSecrets.getTenantAccess()
.registerServiceThread(TenantContainer.current(), sft);
}
sft.start();
try {
sft.join();
......@@ -155,8 +215,9 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must
return;
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
ReferenceQueue<Object> q = getQueue();
for (;;) {
Finalizer f = (Finalizer)queue.poll();
Finalizer f = (Finalizer)q.poll();
if (f == null) break;
f.runFinalizer(jla);
}
......@@ -180,11 +241,20 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must
running = true;
for (;;) {
Finalizer f;
if (TenantGlobals.isDataIsolationEnabled() && TenantContainer.current() != null) {
TenantData td = TenantContainer.current().getTenantData();
synchronized (lock) {
f = td.getFieldValue(Finalizer.class, ID_UNFINALIZED);
if (f == null) break;
td.setFieldValue(Finalizer.class, ID_UNFINALIZED, f.next);
}
} else {
synchronized (lock) {
f = unfinalized;
if (f == null) break;
unfinalized = f.next;
}
}
f.runFinalizer(jla);
}}});
}
......@@ -211,13 +281,21 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must
}
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
ReferenceQueue<Object> q = getQueue();
for (;q != null;) {
try {
Finalizer f = (Finalizer)queue.remove();
Finalizer f = (Finalizer)q.remove();
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
}
// terminate Finalizer thread proactively after TenantContainer.destroy()
if (TenantGlobals.isDataIsolationEnabled()
&& TenantContainer.current() != null
&& TenantContainer.current().getState() == TenantState.DEAD) {
break;
}
}
}
}
......
......@@ -32,6 +32,8 @@ import java.security.PrivilegedAction;
import java.util.concurrent.CopyOnWriteArrayList;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;
import com.alibaba.tenant.TenantContainer;
import com.alibaba.tenant.TenantGlobals;
/**
......@@ -92,6 +94,13 @@ public class DriverManager {
/* Prevent the DriverManager class from being instantiated. */
private DriverManager(){}
private static CopyOnWriteArrayList<DriverInfo> getRegisteredDrivers() {
if (TenantGlobals.isDataIsolationEnabled() && TenantContainer.current() != null) {
return TenantContainer.current().getFieldValue(DriverManager.class, "registeredDrivers",
() -> new CopyOnWriteArrayList<>());
}
return registeredDrivers;
}
/**
* Load the initial JDBC drivers by checking the System property
......@@ -291,7 +300,7 @@ public class DriverManager {
// Walk through the loaded registeredDrivers attempting to locate someone
// who understands the given URL.
for (DriverInfo aDriver : registeredDrivers) {
for (DriverInfo aDriver : getRegisteredDrivers()) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerClass)) {
......@@ -355,7 +364,7 @@ public class DriverManager {
/* Register the driver if it has not already been added to our list */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
getRegisteredDrivers().addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
......@@ -405,15 +414,16 @@ public class DriverManager {
println("DriverManager.deregisterDriver: " + driver);
DriverInfo aDriver = new DriverInfo(driver, null);
if(registeredDrivers.contains(aDriver)) {
CopyOnWriteArrayList<DriverInfo> drivers = getRegisteredDrivers();
if (drivers.contains(aDriver)) {
if (isDriverAllowed(driver, Reflection.getCallerClass())) {
DriverInfo di = registeredDrivers.get(registeredDrivers.indexOf(aDriver));
DriverInfo di = drivers.get(drivers.indexOf(aDriver));
// If a DriverAction was specified, Call it to notify the
// driver that it has been deregistered
if(di.action() != null) {
di.action().deregister();
}
registeredDrivers.remove(aDriver);
drivers.remove(aDriver);
} else {
// If the caller does not have permission to load the driver then
// throw a SecurityException.
......@@ -440,7 +450,7 @@ public class DriverManager {
Class<?> callerClass = Reflection.getCallerClass();
// Walk through the loaded registeredDrivers.
for(DriverInfo aDriver : registeredDrivers) {
for (DriverInfo aDriver : getRegisteredDrivers()) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerClass)) {
......@@ -655,7 +665,7 @@ public class DriverManager {
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
for (DriverInfo aDriver : getRegisteredDrivers()) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
......
......@@ -57,6 +57,8 @@ import java.util.concurrent.ConcurrentMap;
import java.util.jar.JarEntry;
import java.util.spi.ResourceBundleControlProvider;
import com.alibaba.tenant.TenantContainer;
import com.alibaba.tenant.TenantGlobals;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;
import sun.util.locale.BaseLocale;
......@@ -312,6 +314,14 @@ public abstract class ResourceBundle {
private static final ConcurrentMap<CacheKey, BundleReference> cacheList
= new ConcurrentHashMap<>(INITIAL_CACHE_SIZE);
private static ConcurrentMap<CacheKey, BundleReference> getCacheList() {
if (TenantGlobals.isDataIsolationEnabled() && TenantContainer.current() != null) {
return TenantContainer.current().getFieldValue(ResourceBundle.class, "cacheList",
() -> new ConcurrentHashMap<>(INITIAL_CACHE_SIZE));
}
return cacheList;
}
/**
* Queue for reference objects referring to class loaders or bundles.
*/
......@@ -1334,7 +1344,7 @@ public abstract class ResourceBundle {
ResourceBundle bundle = null;
// Quick lookup of the cache.
BundleReference bundleRef = cacheList.get(cacheKey);
BundleReference bundleRef = getCacheList().get(cacheKey);
if (bundleRef != null) {
bundle = bundleRef.get();
bundleRef = null;
......@@ -1445,7 +1455,7 @@ public abstract class ResourceBundle {
// information from the cache.
Object ref;
while ((ref = referenceQueue.poll()) != null) {
cacheList.remove(((CacheKeyReference)ref).getCacheKey());
getCacheList().remove(((CacheKeyReference)ref).getCacheKey());
}
// flag indicating the resource bundle has expired in the cache
......@@ -1468,9 +1478,9 @@ public abstract class ResourceBundle {
}
// Otherwise, remove the cached one since we can't keep
// the same bundles having different parents.
BundleReference bundleRef = cacheList.get(cacheKey);
BundleReference bundleRef = getCacheList().get(cacheKey);
if (bundleRef != null && bundleRef.get() == bundle) {
cacheList.remove(cacheKey, bundleRef);
getCacheList().remove(cacheKey, bundleRef);
}
}
}
......@@ -1597,7 +1607,7 @@ public abstract class ResourceBundle {
*/
private static ResourceBundle findBundleInCache(CacheKey cacheKey,
Control control) {
BundleReference bundleRef = cacheList.get(cacheKey);
BundleReference bundleRef = getCacheList().get(cacheKey);
if (bundleRef == null) {
return null;
}
......@@ -1644,7 +1654,7 @@ public abstract class ResourceBundle {
assert bundle != NONEXISTENT_BUNDLE;
bundle.expired = true;
bundle.cacheKey = null;
cacheList.remove(cacheKey, bundleRef);
getCacheList().remove(cacheKey, bundleRef);
bundle = null;
} else {
CacheKey key = bundleRef.getCacheKey();
......@@ -1675,7 +1685,7 @@ public abstract class ResourceBundle {
// return the bundle with the expired flag
// on.
bundle.cacheKey = null;
cacheList.remove(cacheKey, bundleRef);
getCacheList().remove(cacheKey, bundleRef);
} else {
// Update the expiration control info. and reuse
// the same bundle instance
......@@ -1685,7 +1695,7 @@ public abstract class ResourceBundle {
}
} else {
// We just remove NONEXISTENT_BUNDLE from the cache.
cacheList.remove(cacheKey, bundleRef);
getCacheList().remove(cacheKey, bundleRef);
bundle = null;
}
}
......@@ -1712,7 +1722,7 @@ public abstract class ResourceBundle {
bundle.cacheKey = key;
// Put the bundle in the cache if it's not been in the cache.
BundleReference result = cacheList.putIfAbsent(key, bundleRef);
BundleReference result = getCacheList().putIfAbsent(key, bundleRef);
// If someone else has put the same bundle in the cache before
// us and it has not expired, we should use the one in the cache.
......@@ -1728,7 +1738,7 @@ public abstract class ResourceBundle {
} else {
// Replace the invalid (garbage collected or expired)
// instance with the valid one.
cacheList.put(key, bundleRef);
getCacheList().put(key, bundleRef);
}
}
}
......@@ -1776,7 +1786,7 @@ public abstract class ResourceBundle {
if (loader == null) {
throw new NullPointerException();
}
Set<CacheKey> set = cacheList.keySet();
Set<CacheKey> set = getCacheList().keySet();
for (CacheKey key : set) {
if (key.getLoader() == loader) {
set.remove(key);
......
......@@ -35,6 +35,8 @@ import java.util.ArrayList;
import java.util.logging.Level;
import javax.management.loading.ClassLoaderRepository;
import sun.reflect.misc.ReflectUtil;
import com.alibaba.tenant.TenantContainer;
import com.alibaba.tenant.TenantGlobals;
/**
......@@ -361,10 +363,10 @@ public class MBeanServerFactory {
checkPermission("findMBeanServer");
if (agentId == null)
return new ArrayList<MBeanServer>(mBeanServerList);
return new ArrayList<MBeanServer>(getMBeanServerList());
ArrayList<MBeanServer> result = new ArrayList<MBeanServer>();
for (MBeanServer mbs : mBeanServerList) {
for (MBeanServer mbs : getMBeanServerList()) {
String name = mBeanServerId(mbs);
if (agentId.equals(name))
result.add(mbs);
......@@ -415,11 +417,11 @@ public class MBeanServerFactory {
}
private static synchronized void addMBeanServer(MBeanServer mbs) {
mBeanServerList.add(mbs);
getMBeanServerList().add(mbs);
}
private static synchronized void removeMBeanServer(MBeanServer mbs) {
boolean removed = mBeanServerList.remove(mbs);
boolean removed = getMBeanServerList().remove(mbs);
if (!removed) {
MBEANSERVER_LOGGER.logp(Level.FINER,
MBeanServerFactory.class.getName(),
......@@ -432,6 +434,15 @@ public class MBeanServerFactory {
private static final ArrayList<MBeanServer> mBeanServerList =
new ArrayList<MBeanServer>();
private static ArrayList<MBeanServer> getMBeanServerList() {
if (TenantGlobals.isDataIsolationEnabled() && TenantContainer.current() != null) {
return TenantContainer.current().getFieldValue(MBeanServerFactory.class, "mBeanServerList",
() -> new ArrayList<>());
} else {
return mBeanServerList;
}
}
/**
* Load the builder class through the context class loader.
* @param builderClassName The name of the builder class.
......
......@@ -61,6 +61,7 @@ public class SharedSecrets {
private static JavaxCryptoSealedObjectAccess javaxCryptoSealedObjectAccess;
private static JavaObjectInputStreamReadString javaObjectInputStreamReadString;
private static JavaObjectInputStreamAccess javaObjectInputStreamAccess;
private static TenantAccess tenantAccess;
public static JavaUtilJarAccess javaUtilJarAccess() {
if (javaUtilJarAccess == null) {
......@@ -235,4 +236,12 @@ public class SharedSecrets {
}
return javaxCryptoSealedObjectAccess;
}
public static void setTenantAccess(TenantAccess access) {
tenantAccess = access;
}
public static TenantAccess getTenantAccess() {
return tenantAccess;
}
}
package sun.misc;
import com.alibaba.tenant.TenantContainer;
public interface TenantAccess {
/**
* Register a thread to {@code tenant}'s service thread list.
* At present, a service thread is either a registered shutdown hook thread or Finalizer thread.
*
* @param tenant The teant container to register thread with
* @param thread Thread to be registered
*/
void registerServiceThread(TenantContainer tenant, Thread thread);
}
/*
* @test
* @summary Test isolation of various static fields
* @library /lib/testlibrary
* @run main/othervm -XX:+UseG1GC -XX:+MultiTenant -XX:+TenantDataIsolation TestStaticFieldIsolation
*/
import static jdk.testlibrary.Asserts.*;
import java.lang.reflect.Field;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicInteger;
import javax.management.MBeanServerFactory;
import com.alibaba.tenant.TenantConfiguration;
import com.alibaba.tenant.TenantContainer;
import com.alibaba.tenant.TenantException;
public class TestStaticFieldIsolation {
public static void main(String[] args) throws TenantException {
TestStaticFieldIsolation test = new TestStaticFieldIsolation();
test.testIsolation_java_lang_ResourceBundle_cacheList();
test.testIsolation_java_sql_DriverManager_registeredDrivers();
test.testIsolation_javax_management_MBeanServerFactory_mBeanServerList();
test.testIsolation_java_lang_Class_classValueMap();
}
private void testIsolation_java_lang_ResourceBundle_cacheList() throws TenantException {
Runnable task = () -> {
try {
ResourceBundle.getBundle("NON_EXIST");
} catch (Throwable t) { /* ignore */ }
};
testStaticFieldIsolation(ResourceBundle.class, task, "cacheList");
}
private void testIsolation_javax_management_MBeanServerFactory_mBeanServerList() throws TenantException {
Runnable task = () -> {
try {
MBeanServerFactory.findMBeanServer(null);
} catch (Throwable t) { /* ignore */ }
};
testStaticFieldIsolation(MBeanServerFactory.class, task, "mBeanServerList");
}
private void testIsolation_java_sql_DriverManager_registeredDrivers() throws TenantException {
Runnable task = () -> {
try {
java.sql.DriverManager.getDrivers();
} catch (Throwable t) { /* ignore */ }
};
testStaticFieldIsolation(java.sql.DriverManager.class, task, "registeredDrivers");
}
class TestClass {
int val;
}
private void testIsolation_java_lang_Class_classValueMap() throws TenantException {
final AtomicInteger atomicInteger = new AtomicInteger(0);
ClassValue<TestClass> value = new ClassValue<TestClass>() {
@Override protected TestClass computeValue(Class<?> type) {
TestClass obj = new TestClass();
obj.val = atomicInteger.getAndIncrement();
return obj;
}
};
TestClass tc = value.get(TestClass.class);
assertEquals(tc.val, 0);
TestClass tc2 = value.get(TestClass.class);
assertTrue(tc == tc2);
TestClass[] receivers = new TestClass[2];
TenantContainer tenant = createSimpleTenant();
tenant.run(() -> {
receivers[0] = value.get(TestClass.class);
});
assertEquals(receivers[0].val, 1);
tenant.run(() -> {
receivers[1] = value.get(TestClass.class);
});
assertEquals(receivers[1].val, 1);
assertTrue(receivers[1] == receivers[0]);
assertTrue(receivers[0] != tc);
}
/**
* test isolation of {@code classKay}'s static field {@code fieldName}
* @param classKey The class contains target static field
* @param tenantTask Task to trigger the static filed isolation per tenant,
* which is stored in TenantContainer.tenantData
* @param fieldName Field name
* @throws TenantException
*/
private static void testStaticFieldIsolation(Class classKey, Runnable tenantTask, String fieldName)
throws TenantException {
TenantContainer tenant = createSimpleTenant();
assertNull(tenant.getFieldValue(classKey, fieldName));
tenant.run(tenantTask);
Object isolatedFieldValue = tenant.getFieldValue(classKey, fieldName);
assertNotNull(isolatedFieldValue);
try {
Field field = classKey.getDeclaredField(fieldName);
field.setAccessible(true);
Object rootValue = field.get(null);
assertTrue(rootValue != isolatedFieldValue);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
fail();
}
tenant.destroy();
}
// convenient static method to create a simple {@code TenantContainer} object
private static TenantContainer createSimpleTenant() {
TenantConfiguration config = new TenantConfiguration(1024, 64 * 1024 * 1024);
return TenantContainer.create(config);
}
}
package test.com.alibaba.tenant;
import static jdk.testlibrary.Asserts.*;
import java.lang.management.ManagementFactory;
import java.util.Set;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.MBeanServerInvocationHandler;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.StandardMBean;
import org.junit.Test;
import com.alibaba.tenant.TenantConfiguration;
import com.alibaba.tenant.TenantContainer;
import com.alibaba.tenant.TenantException;
/* @test
* @summary JMX related unit tests
* @library /lib/testlibrary
* @compile TestJMX.java
* @run junit/othervm/timeout=300 -XX:+MultiTenant -XX:+TenantDataIsolation -XX:+TenantHeapThrottling -XX:+UseG1GC
* -XX:+TenantCpuThrottling -Xmx600m -Xms600m test.com.alibaba.tenant.TestJMX
*/
public class TestJMX {
public static interface MXBean {
public String getName();
}
public class MXBeanImpl implements MXBean {
public String getName() {
return "test";
}
}
private void registerAndVerifyMBean(MBeanServer mbs) {
try {
ObjectName myInfoObj = new ObjectName("com.alibaba.tenant.mxbean:type=MyTest");
MXBeanImpl myMXBean = new MXBeanImpl();
StandardMBean smb = new StandardMBean(myMXBean, MXBean.class);
mbs.registerMBean(smb, myInfoObj);
assertTrue(mbs.isRegistered(myInfoObj));
//call the method of MXBean
MXBean mbean =
(MXBean)MBeanServerInvocationHandler.newProxyInstance(
mbs,new ObjectName("com.alibaba.tenant.mxbean:type=MyTest"), MXBean.class, true);
assertTrue("test".equals(mbean.getName()));
Set<ObjectInstance> instances = mbs.queryMBeans(new ObjectName("com.alibaba.tenant.mxbean:type=MyTest"), null);
ObjectInstance instance = (ObjectInstance) instances.toArray()[0];
assertTrue(myMXBean.getClass().getName().equals(instance.getClassName()));
MBeanInfo info = mbs.getMBeanInfo(myInfoObj);
assertTrue(myMXBean.getClass().getName().equals(info.getClassName()));
} catch (Exception e) {
e.printStackTrace();
fail();
}
}
@Test
public void testMBeanServerIsolation() {
//verify in root.
registerAndVerifyMBean(ManagementFactory.getPlatformMBeanServer());
TenantConfiguration tconfig = new TenantConfiguration(1024, 100 * 1024 * 1024);
final TenantContainer tenant = TenantContainer.create(tconfig);
final TenantContainer tenant2 = TenantContainer.create(tconfig);
try {
tenant.run(() -> {
//verify in the tenant 1.
registerAndVerifyMBean(ManagementFactory.getPlatformMBeanServer());
});
} catch (TenantException e) {
e.printStackTrace();
fail();
}
try {
tenant2.run(() -> {
//verify in the tenant 1.
registerAndVerifyMBean(ManagementFactory.getPlatformMBeanServer());
});
} catch (TenantException e) {
e.printStackTrace();
fail();
}
}
public static void main(String[] args) throws Exception {
new TestJMX().testMBeanServerIsolation();
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册