提交 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; ...@@ -36,6 +36,8 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.alibaba.rcm.ResourceContainer; import com.alibaba.rcm.ResourceContainer;
import com.alibaba.rcm.Constraint; import com.alibaba.rcm.Constraint;
import com.alibaba.rcm.internal.AbstractResourceContainer; import com.alibaba.rcm.internal.AbstractResourceContainer;
import sun.misc.SharedSecrets;
import sun.misc.TenantAccess;
import static com.alibaba.tenant.TenantState.*; import static com.alibaba.tenant.TenantState.*;
...@@ -136,11 +138,24 @@ public class TenantContainer { ...@@ -136,11 +138,24 @@ public class TenantContainer {
return totalThreadsAllocatedMemory + accumulatedMemory; 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 * Used to track and run tenant shutdown hooks
*/ */
private TenantShutdownHooks tenantShutdownHooks = new TenantShutdownHooks(); 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. * 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. * @param props the properties to be set, CoW the system properties if it is null.
...@@ -266,6 +281,7 @@ public class TenantContainer { ...@@ -266,6 +281,7 @@ public class TenantContainer {
// clear references // clear references
spawnedThreads.clear(); spawnedThreads.clear();
attachedThreads.clear(); attachedThreads.clear();
tenantData.clear();
tenantShutdownHooks = null; tenantShutdownHooks = null;
} }
...@@ -584,6 +600,18 @@ public class TenantContainer { ...@@ -584,6 +600,18 @@ public class TenantContainer {
//Initialize this field after the system is booted. //Initialize this field after the system is booted.
tenantContainerMap = Collections.synchronizedMap(new HashMap()); 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 { try {
// force initialization of TenantConfiguration // force initialization of TenantConfiguration
Class.forName("com.alibaba.tenant.TenantConfiguration"); Class.forName("com.alibaba.tenant.TenantConfiguration");
...@@ -605,6 +633,31 @@ public class TenantContainer { ...@@ -605,6 +633,31 @@ public class TenantContainer {
return obj != null ? nd.containerOf(obj) : null; 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. * Runs {@code Supplier.get} in the root tenant.
* @param supplier target used to call * @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 { ...@@ -51,6 +51,11 @@ public class TenantGlobals {
*/ */
public static final int TENANT_FLAG_CPU_THROTTLING_ENABLED = 0x4; 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 * Bit to indicate that if cpu accounting feature is enabled
*/ */
...@@ -103,4 +108,12 @@ public class TenantGlobals { ...@@ -103,4 +108,12 @@ public class TenantGlobals {
public static boolean isCpuAccountingEnabled() { public static boolean isCpuAccountingEnabled() {
return 0 != (flags & TENANT_FLAG_CPU_ACCOUNTING_ENABLED); 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 @@ ...@@ -25,6 +25,9 @@
package java.lang; package java.lang;
import com.alibaba.tenant.TenantContainer;
import com.alibaba.tenant.TenantGlobals;
import java.lang.ClassValue.ClassValueMap; import java.lang.ClassValue.ClassValueMap;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
...@@ -186,7 +189,13 @@ public abstract class ClassValue<T> { ...@@ -186,7 +189,13 @@ public abstract class ClassValue<T> {
/** Return the cache, if it exists, else a dummy empty cache. */ /** Return the cache, if it exists, else a dummy empty cache. */
private static Entry<?>[] getCacheCarefully(Class<?> type) { private static Entry<?>[] getCacheCarefully(Class<?> type) {
// racing type.classValueMap{.cacheArray} : null => new Entry[X] <=> new Entry[Y] // 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; if (map == null) return EMPTY_CACHE;
Entry<?>[] cache = map.getCache(); Entry<?>[] cache = map.getCache();
return cache; return cache;
...@@ -364,7 +373,13 @@ public abstract class ClassValue<T> { ...@@ -364,7 +373,13 @@ public abstract class ClassValue<T> {
// racing type.classValueMap : null (blank) => unique ClassValueMap // racing type.classValueMap : null (blank) => unique ClassValueMap
// if a null is observed, a map is created (lazily, synchronously, uniquely) // if a null is observed, a map is created (lazily, synchronously, uniquely)
// all further access to that map is synchronized // 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; if (map != null) return map;
return initializeMap(type); return initializeMap(type);
} }
...@@ -374,11 +389,17 @@ public abstract class ClassValue<T> { ...@@ -374,11 +389,17 @@ public abstract class ClassValue<T> {
ClassValueMap map; ClassValueMap map;
synchronized (CRITICAL_SECTION) { // private object to avoid deadlocks synchronized (CRITICAL_SECTION) { // private object to avoid deadlocks
// happens about once per type // happens about once per type
if ((map = type.classValueMap) == null) if (TenantGlobals.isDataIsolationEnabled() && TenantContainer.current() != null) {
type.classValueMap = map = new ClassValueMap(type); type.classValueMap = map = TenantContainer.current()
} .getFieldValue(type, "classValueMap", () -> new ClassValueMap(type));
return map; } else {
if ((map = type.classValueMap) == null) {
type.classValueMap = map = new ClassValueMap(type);
}
}
} }
return map;
}
static <T> Entry<T> makeEntry(Version<T> explicitVersion, T value) { static <T> Entry<T> makeEntry(Version<T> explicitVersion, T value) {
// Note that explicitVersion might be different from this.version. // Note that explicitVersion might be different from this.version.
......
...@@ -28,6 +28,10 @@ package java.lang; ...@@ -28,6 +28,10 @@ package java.lang;
import java.io.*; import java.io.*;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import com.alibaba.tenant.TenantContainer;
import com.alibaba.tenant.TenantGlobals;
import sun.reflect.CallerSensitive; import sun.reflect.CallerSensitive;
import sun.reflect.Reflection; import sun.reflect.Reflection;
...@@ -209,7 +213,11 @@ public class Runtime { ...@@ -209,7 +213,11 @@ public class Runtime {
if (sm != null) { if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks")); sm.checkPermission(new RuntimePermission("shutdownHooks"));
} }
ApplicationShutdownHooks.add(hook); if(TenantGlobals.isDataIsolationEnabled() && null != TenantContainer.current()) {
TenantContainer.current().addShutdownHook(hook);
} else {
ApplicationShutdownHooks.add(hook);
}
} }
/** /**
...@@ -237,7 +245,11 @@ public class Runtime { ...@@ -237,7 +245,11 @@ public class Runtime {
if (sm != null) { if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks")); sm.checkPermission(new RuntimePermission("shutdownHooks"));
} }
return ApplicationShutdownHooks.remove(hook); if(TenantGlobals.isDataIsolationEnabled() && null != TenantContainer.current()) {
return TenantContainer.current().removeShutdownHook(hook);
} else {
return ApplicationShutdownHooks.remove(hook);
}
} }
/** /**
......
...@@ -38,6 +38,9 @@ import java.security.AllPermission; ...@@ -38,6 +38,9 @@ import java.security.AllPermission;
import java.nio.channels.Channel; import java.nio.channels.Channel;
import java.nio.channels.spi.SelectorProvider; import java.nio.channels.spi.SelectorProvider;
import com.alibaba.rcm.internal.AbstractResourceContainer; 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.nio.ch.Interruptible;
import sun.reflect.CallerSensitive; import sun.reflect.CallerSensitive;
import sun.reflect.Reflection; import sun.reflect.Reflection;
...@@ -632,8 +635,11 @@ public final class System { ...@@ -632,8 +635,11 @@ public final class System {
if (sm != null) { if (sm != null) {
sm.checkPropertiesAccess(); sm.checkPropertiesAccess();
} }
if(TenantGlobals.isDataIsolationEnabled() && null != TenantContainer.current()) {
return props; return TenantContainer.current().getProperties();
} else {
return props;
}
} }
/** /**
...@@ -684,7 +690,12 @@ public final class System { ...@@ -684,7 +690,12 @@ public final class System {
props = new Properties(); props = new Properties();
initProperties(props); initProperties(props);
} }
System.props = props; if (VM.isBooted() && TenantGlobals.isDataIsolationEnabled()
&& null != TenantContainer.current()) {
TenantContainer.current().setProperties(props);
} else {
System.props = props;
}
} }
/** /**
...@@ -720,7 +731,12 @@ public final class System { ...@@ -720,7 +731,12 @@ public final class System {
sm.checkPropertyAccess(key); sm.checkPropertyAccess(key);
} }
return props.getProperty(key); if(VM.isBooted() && TenantGlobals.isDataIsolationEnabled()
&& null != TenantContainer.current()) {
return TenantContainer.current().getProperty(key);
} else {
return props.getProperty(key);
}
} }
/** /**
...@@ -756,7 +772,12 @@ public final class System { ...@@ -756,7 +772,12 @@ public final class System {
sm.checkPropertyAccess(key); sm.checkPropertyAccess(key);
} }
return props.getProperty(key, def); if (VM.isBooted() && TenantGlobals.isDataIsolationEnabled()
&& null != TenantContainer.current()) {
return TenantContainer.current().getProperties().getProperty(key, def);
} else {
return props.getProperty(key, def);
}
} }
/** /**
...@@ -796,7 +817,12 @@ public final class System { ...@@ -796,7 +817,12 @@ public final class System {
SecurityConstants.PROPERTY_WRITE_ACTION)); SecurityConstants.PROPERTY_WRITE_ACTION));
} }
return (String) props.setProperty(key, value); if(VM.isBooted() && TenantGlobals.isDataIsolationEnabled()
&& null != TenantContainer.current()) {
return (String) TenantContainer.current().setProperty(key, value);
} else {
return (String) props.setProperty(key, value);
}
} }
/** /**
...@@ -833,7 +859,11 @@ public final class System { ...@@ -833,7 +859,11 @@ public final class System {
sm.checkPermission(new PropertyPermission(key, "write")); sm.checkPermission(new PropertyPermission(key, "write"));
} }
return (String) props.remove(key); if(TenantGlobals.isDataIsolationEnabled() && null != TenantContainer.current()) {
return (String) TenantContainer.current().clearProperty(key);
} else {
return (String) props.remove(key);
}
} }
private static void checkKey(String key) { private static void checkKey(String key) {
......
...@@ -30,7 +30,6 @@ import javax.management.MBeanServerConnection; ...@@ -30,7 +30,6 @@ import javax.management.MBeanServerConnection;
import javax.management.MBeanServerFactory; import javax.management.MBeanServerFactory;
import javax.management.MBeanServerPermission; import javax.management.MBeanServerPermission;
import javax.management.NotificationEmitter; import javax.management.NotificationEmitter;
import javax.management.ObjectInstance;
import javax.management.ObjectName; import javax.management.ObjectName;
import javax.management.InstanceAlreadyExistsException; import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException; import javax.management.InstanceNotFoundException;
...@@ -39,6 +38,8 @@ import javax.management.MBeanRegistrationException; ...@@ -39,6 +38,8 @@ import javax.management.MBeanRegistrationException;
import javax.management.NotCompliantMBeanException; import javax.management.NotCompliantMBeanException;
import javax.management.StandardEmitterMBean; import javax.management.StandardEmitterMBean;
import javax.management.StandardMBean; import javax.management.StandardMBean;
import com.alibaba.tenant.TenantContainer;
import com.alibaba.tenant.TenantGlobals;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
...@@ -465,38 +466,51 @@ public class ManagementFactory { ...@@ -465,38 +466,51 @@ public class ManagementFactory {
sm.checkPermission(perm); sm.checkPermission(perm);
} }
if (platformMBeanServer == null) { MBeanServer thePlatformMBeanServer= null;
platformMBeanServer = MBeanServerFactory.createMBeanServer(); if(TenantGlobals.isDataIsolationEnabled() && null != TenantContainer.current()) {
for (PlatformComponent pc : PlatformComponent.values()) { thePlatformMBeanServer = TenantContainer.current().getFieldValue(ManagementFactory.class, "platformMBeanServer",
List<? extends PlatformManagedObject> list = ManagementFactory::createPlatformMBeanServer);
pc.getMXBeans(pc.getMXBeanInterface()); } else {
for (PlatformManagedObject o : list) { //use the value in root.
// Each PlatformComponent represents one management if (platformMBeanServer == null) {
// interface. Some MXBean may extend another one. platformMBeanServer = createPlatformMBeanServer();
// The MXBean instances for one platform component
// (returned by pc.getMXBeans()) might be also
// the MXBean instances for another platform component.
// e.g. com.sun.management.GarbageCollectorMXBean
//
// So need to check if an MXBean instance is registered
// before registering into the platform MBeanServer
if (!platformMBeanServer.isRegistered(o.getObjectName())) {
addMXBean(platformMBeanServer, o);
}
}
}
HashMap<ObjectName, DynamicMBean> dynmbeans =
ManagementFactoryHelper.getPlatformDynamicMBeans();
for (Map.Entry<ObjectName, DynamicMBean> e : dynmbeans.entrySet()) {
addDynamicMBean(platformMBeanServer, e.getValue(), e.getKey());
} }
for (final PlatformManagedObject o : thePlatformMBeanServer = platformMBeanServer;
ExtendedPlatformComponent.getMXBeans()) { }
return thePlatformMBeanServer;
}
private static MBeanServer createPlatformMBeanServer() {
MBeanServer platformMBeanServer = MBeanServerFactory.createMBeanServer();
for (PlatformComponent pc : PlatformComponent.values()) {
List<? extends PlatformManagedObject> list =
pc.getMXBeans(pc.getMXBeanInterface());
for (PlatformManagedObject o : list) {
// Each PlatformComponent represents one management
// interface. Some MXBean may extend another one.
// The MXBean instances for one platform component
// (returned by pc.getMXBeans()) might be also
// the MXBean instances for another platform component.
// e.g. com.sun.management.GarbageCollectorMXBean
//
// So need to check if an MXBean instance is registered
// before registering into the platform MBeanServer
if (!platformMBeanServer.isRegistered(o.getObjectName())) { if (!platformMBeanServer.isRegistered(o.getObjectName())) {
addMXBean(platformMBeanServer, o); addMXBean(platformMBeanServer, o);
} }
} }
} }
HashMap<ObjectName, DynamicMBean> dynmbeans =
ManagementFactoryHelper.getPlatformDynamicMBeans();
for (Map.Entry<ObjectName, DynamicMBean> e : dynmbeans.entrySet()) {
addDynamicMBean(platformMBeanServer, e.getValue(), e.getKey());
}
for (final PlatformManagedObject o :
ExtendedPlatformComponent.getMXBeans()) {
if (!platformMBeanServer.isRegistered(o.getObjectName())) {
addMXBean(platformMBeanServer, o);
}
}
return platformMBeanServer; return platformMBeanServer;
} }
......
...@@ -27,9 +27,14 @@ package java.lang.ref; ...@@ -27,9 +27,14 @@ package java.lang.ref;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.security.AccessController; import java.security.AccessController;
import com.alibaba.tenant.TenantState;
import sun.misc.JavaLangAccess; import sun.misc.JavaLangAccess;
import sun.misc.SharedSecrets; import sun.misc.SharedSecrets;
import sun.misc.VM; 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 final class Finalizer extends FinalReference<Object> { /* Package-private; must be in
same package as the Reference same package as the Reference
...@@ -39,31 +44,75 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must ...@@ -39,31 +44,75 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must
private static Finalizer unfinalized = null; private static Finalizer unfinalized = null;
private static final Object lock = new Object(); private static final Object lock = new Object();
private static final String ID_UNFINALIZED = "unfinalized";
private Finalizer private Finalizer
next = null, next = null,
prev = 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() { private boolean hasBeenFinalized() {
return (next == this); return (next == this);
} }
private void add() { private void add() {
synchronized (lock) { synchronized (lock) {
if (unfinalized != null) { if (VM.isBooted() && TenantGlobals.isDataIsolationEnabled() && TenantContainer.current() != null) {
this.next = unfinalized; TenantData td = TenantContainer.current().getTenantData();
unfinalized.prev = this; 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;
}
unfinalized = this;
} }
unfinalized = this;
} }
} }
private void remove() { private void remove() {
synchronized (lock) { synchronized (lock) {
if (unfinalized == this) { if (TenantGlobals.isDataIsolationEnabled() && TenantContainer.current() != null) {
if (this.next != null) { TenantData td = TenantContainer.current().getTenantData();
unfinalized = this.next; if (td.getFieldValue(Finalizer.class, ID_UNFINALIZED) == this) {
} else { if (this.next != null) {
unfinalized = this.prev; 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;
} else {
unfinalized = this.prev;
}
} }
} }
if (this.next != null) { if (this.next != null) {
...@@ -78,12 +127,17 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must ...@@ -78,12 +127,17 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must
} }
private Finalizer(Object finalizee) { private Finalizer(Object finalizee) {
super(finalizee, queue); super(finalizee, getQueue());
add(); add();
} }
static ReferenceQueue<Object> getQueue() { static ReferenceQueue<Object> getQueue() {
return queue; if (VM.isBooted() && TenantGlobals.isDataIsolationEnabled() && TenantContainer.current() != null) {
return TenantContainer.current().getFieldValue(Finalizer.class, "queue",
Finalizer::initTenantReferenceQueue);
} else {
return queue;
}
} }
/* Invoked by VM */ /* Invoked by VM */
...@@ -131,6 +185,12 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must ...@@ -131,6 +185,12 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must
tgn != null; tgn != null;
tg = tgn, tgn = tg.getParent()); tg = tgn, tgn = tg.getParent());
Thread sft = new Thread(tg, proc, "Secondary finalizer"); Thread sft = new Thread(tg, proc, "Secondary finalizer");
if (TenantGlobals.isDataIsolationEnabled() && TenantContainer.current() != null) {
SharedSecrets.getTenantAccess()
.registerServiceThread(TenantContainer.current(), sft);
}
sft.start(); sft.start();
try { try {
sft.join(); sft.join();
...@@ -155,8 +215,9 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must ...@@ -155,8 +215,9 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must
return; return;
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true; running = true;
ReferenceQueue<Object> q = getQueue();
for (;;) { for (;;) {
Finalizer f = (Finalizer)queue.poll(); Finalizer f = (Finalizer)q.poll();
if (f == null) break; if (f == null) break;
f.runFinalizer(jla); f.runFinalizer(jla);
} }
...@@ -180,10 +241,19 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must ...@@ -180,10 +241,19 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must
running = true; running = true;
for (;;) { for (;;) {
Finalizer f; Finalizer f;
synchronized (lock) { if (TenantGlobals.isDataIsolationEnabled() && TenantContainer.current() != null) {
f = unfinalized; TenantData td = TenantContainer.current().getTenantData();
if (f == null) break; synchronized (lock) {
unfinalized = f.next; 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); f.runFinalizer(jla);
}}}); }}});
...@@ -211,13 +281,21 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must ...@@ -211,13 +281,21 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must
} }
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true; running = true;
for (;;) { ReferenceQueue<Object> q = getQueue();
for (;q != null;) {
try { try {
Finalizer f = (Finalizer)queue.remove(); Finalizer f = (Finalizer)q.remove();
f.runFinalizer(jla); f.runFinalizer(jla);
} catch (InterruptedException x) { } catch (InterruptedException x) {
// ignore and continue // 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; ...@@ -32,6 +32,8 @@ import java.security.PrivilegedAction;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import sun.reflect.CallerSensitive; import sun.reflect.CallerSensitive;
import sun.reflect.Reflection; import sun.reflect.Reflection;
import com.alibaba.tenant.TenantContainer;
import com.alibaba.tenant.TenantGlobals;
/** /**
...@@ -92,6 +94,13 @@ public class DriverManager { ...@@ -92,6 +94,13 @@ public class DriverManager {
/* Prevent the DriverManager class from being instantiated. */ /* Prevent the DriverManager class from being instantiated. */
private DriverManager(){} 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 * Load the initial JDBC drivers by checking the System property
...@@ -291,7 +300,7 @@ public class DriverManager { ...@@ -291,7 +300,7 @@ public class DriverManager {
// Walk through the loaded registeredDrivers attempting to locate someone // Walk through the loaded registeredDrivers attempting to locate someone
// who understands the given URL. // 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 // If the caller does not have permission to load the driver then
// skip it. // skip it.
if(isDriverAllowed(aDriver.driver, callerClass)) { if(isDriverAllowed(aDriver.driver, callerClass)) {
...@@ -355,7 +364,7 @@ public class DriverManager { ...@@ -355,7 +364,7 @@ public class DriverManager {
/* Register the driver if it has not already been added to our list */ /* Register the driver if it has not already been added to our list */
if(driver != null) { if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da)); getRegisteredDrivers().addIfAbsent(new DriverInfo(driver, da));
} else { } else {
// This is for compatibility with the original DriverManager // This is for compatibility with the original DriverManager
throw new NullPointerException(); throw new NullPointerException();
...@@ -405,15 +414,16 @@ public class DriverManager { ...@@ -405,15 +414,16 @@ public class DriverManager {
println("DriverManager.deregisterDriver: " + driver); println("DriverManager.deregisterDriver: " + driver);
DriverInfo aDriver = new DriverInfo(driver, null); DriverInfo aDriver = new DriverInfo(driver, null);
if(registeredDrivers.contains(aDriver)) { CopyOnWriteArrayList<DriverInfo> drivers = getRegisteredDrivers();
if (drivers.contains(aDriver)) {
if (isDriverAllowed(driver, Reflection.getCallerClass())) { 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 // If a DriverAction was specified, Call it to notify the
// driver that it has been deregistered // driver that it has been deregistered
if(di.action() != null) { if(di.action() != null) {
di.action().deregister(); di.action().deregister();
} }
registeredDrivers.remove(aDriver); drivers.remove(aDriver);
} else { } else {
// If the caller does not have permission to load the driver then // If the caller does not have permission to load the driver then
// throw a SecurityException. // throw a SecurityException.
...@@ -440,7 +450,7 @@ public class DriverManager { ...@@ -440,7 +450,7 @@ public class DriverManager {
Class<?> callerClass = Reflection.getCallerClass(); Class<?> callerClass = Reflection.getCallerClass();
// Walk through the loaded registeredDrivers. // 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 // If the caller does not have permission to load the driver then
// skip it. // skip it.
if(isDriverAllowed(aDriver.driver, callerClass)) { if(isDriverAllowed(aDriver.driver, callerClass)) {
...@@ -655,7 +665,7 @@ public class DriverManager { ...@@ -655,7 +665,7 @@ public class DriverManager {
// Remember the first exception that gets raised so we can reraise it. // Remember the first exception that gets raised so we can reraise it.
SQLException reason = null; SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) { for (DriverInfo aDriver : getRegisteredDrivers()) {
// If the caller does not have permission to load the driver then // If the caller does not have permission to load the driver then
// skip it. // skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) { if(isDriverAllowed(aDriver.driver, callerCL)) {
......
...@@ -57,6 +57,8 @@ import java.util.concurrent.ConcurrentMap; ...@@ -57,6 +57,8 @@ import java.util.concurrent.ConcurrentMap;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.spi.ResourceBundleControlProvider; import java.util.spi.ResourceBundleControlProvider;
import com.alibaba.tenant.TenantContainer;
import com.alibaba.tenant.TenantGlobals;
import sun.reflect.CallerSensitive; import sun.reflect.CallerSensitive;
import sun.reflect.Reflection; import sun.reflect.Reflection;
import sun.util.locale.BaseLocale; import sun.util.locale.BaseLocale;
...@@ -312,6 +314,14 @@ public abstract class ResourceBundle { ...@@ -312,6 +314,14 @@ public abstract class ResourceBundle {
private static final ConcurrentMap<CacheKey, BundleReference> cacheList private static final ConcurrentMap<CacheKey, BundleReference> cacheList
= new ConcurrentHashMap<>(INITIAL_CACHE_SIZE); = 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. * Queue for reference objects referring to class loaders or bundles.
*/ */
...@@ -1334,7 +1344,7 @@ public abstract class ResourceBundle { ...@@ -1334,7 +1344,7 @@ public abstract class ResourceBundle {
ResourceBundle bundle = null; ResourceBundle bundle = null;
// Quick lookup of the cache. // Quick lookup of the cache.
BundleReference bundleRef = cacheList.get(cacheKey); BundleReference bundleRef = getCacheList().get(cacheKey);
if (bundleRef != null) { if (bundleRef != null) {
bundle = bundleRef.get(); bundle = bundleRef.get();
bundleRef = null; bundleRef = null;
...@@ -1445,7 +1455,7 @@ public abstract class ResourceBundle { ...@@ -1445,7 +1455,7 @@ public abstract class ResourceBundle {
// information from the cache. // information from the cache.
Object ref; Object ref;
while ((ref = referenceQueue.poll()) != null) { 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 // flag indicating the resource bundle has expired in the cache
...@@ -1468,9 +1478,9 @@ public abstract class ResourceBundle { ...@@ -1468,9 +1478,9 @@ public abstract class ResourceBundle {
} }
// Otherwise, remove the cached one since we can't keep // Otherwise, remove the cached one since we can't keep
// the same bundles having different parents. // the same bundles having different parents.
BundleReference bundleRef = cacheList.get(cacheKey); BundleReference bundleRef = getCacheList().get(cacheKey);
if (bundleRef != null && bundleRef.get() == bundle) { if (bundleRef != null && bundleRef.get() == bundle) {
cacheList.remove(cacheKey, bundleRef); getCacheList().remove(cacheKey, bundleRef);
} }
} }
} }
...@@ -1597,7 +1607,7 @@ public abstract class ResourceBundle { ...@@ -1597,7 +1607,7 @@ public abstract class ResourceBundle {
*/ */
private static ResourceBundle findBundleInCache(CacheKey cacheKey, private static ResourceBundle findBundleInCache(CacheKey cacheKey,
Control control) { Control control) {
BundleReference bundleRef = cacheList.get(cacheKey); BundleReference bundleRef = getCacheList().get(cacheKey);
if (bundleRef == null) { if (bundleRef == null) {
return null; return null;
} }
...@@ -1644,7 +1654,7 @@ public abstract class ResourceBundle { ...@@ -1644,7 +1654,7 @@ public abstract class ResourceBundle {
assert bundle != NONEXISTENT_BUNDLE; assert bundle != NONEXISTENT_BUNDLE;
bundle.expired = true; bundle.expired = true;
bundle.cacheKey = null; bundle.cacheKey = null;
cacheList.remove(cacheKey, bundleRef); getCacheList().remove(cacheKey, bundleRef);
bundle = null; bundle = null;
} else { } else {
CacheKey key = bundleRef.getCacheKey(); CacheKey key = bundleRef.getCacheKey();
...@@ -1675,7 +1685,7 @@ public abstract class ResourceBundle { ...@@ -1675,7 +1685,7 @@ public abstract class ResourceBundle {
// return the bundle with the expired flag // return the bundle with the expired flag
// on. // on.
bundle.cacheKey = null; bundle.cacheKey = null;
cacheList.remove(cacheKey, bundleRef); getCacheList().remove(cacheKey, bundleRef);
} else { } else {
// Update the expiration control info. and reuse // Update the expiration control info. and reuse
// the same bundle instance // the same bundle instance
...@@ -1685,7 +1695,7 @@ public abstract class ResourceBundle { ...@@ -1685,7 +1695,7 @@ public abstract class ResourceBundle {
} }
} else { } else {
// We just remove NONEXISTENT_BUNDLE from the cache. // We just remove NONEXISTENT_BUNDLE from the cache.
cacheList.remove(cacheKey, bundleRef); getCacheList().remove(cacheKey, bundleRef);
bundle = null; bundle = null;
} }
} }
...@@ -1712,7 +1722,7 @@ public abstract class ResourceBundle { ...@@ -1712,7 +1722,7 @@ public abstract class ResourceBundle {
bundle.cacheKey = key; bundle.cacheKey = key;
// Put the bundle in the cache if it's not been in the cache. // 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 // 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. // us and it has not expired, we should use the one in the cache.
...@@ -1728,7 +1738,7 @@ public abstract class ResourceBundle { ...@@ -1728,7 +1738,7 @@ public abstract class ResourceBundle {
} else { } else {
// Replace the invalid (garbage collected or expired) // Replace the invalid (garbage collected or expired)
// instance with the valid one. // instance with the valid one.
cacheList.put(key, bundleRef); getCacheList().put(key, bundleRef);
} }
} }
} }
...@@ -1776,7 +1786,7 @@ public abstract class ResourceBundle { ...@@ -1776,7 +1786,7 @@ public abstract class ResourceBundle {
if (loader == null) { if (loader == null) {
throw new NullPointerException(); throw new NullPointerException();
} }
Set<CacheKey> set = cacheList.keySet(); Set<CacheKey> set = getCacheList().keySet();
for (CacheKey key : set) { for (CacheKey key : set) {
if (key.getLoader() == loader) { if (key.getLoader() == loader) {
set.remove(key); set.remove(key);
......
...@@ -35,6 +35,8 @@ import java.util.ArrayList; ...@@ -35,6 +35,8 @@ import java.util.ArrayList;
import java.util.logging.Level; import java.util.logging.Level;
import javax.management.loading.ClassLoaderRepository; import javax.management.loading.ClassLoaderRepository;
import sun.reflect.misc.ReflectUtil; import sun.reflect.misc.ReflectUtil;
import com.alibaba.tenant.TenantContainer;
import com.alibaba.tenant.TenantGlobals;
/** /**
...@@ -361,10 +363,10 @@ public class MBeanServerFactory { ...@@ -361,10 +363,10 @@ public class MBeanServerFactory {
checkPermission("findMBeanServer"); checkPermission("findMBeanServer");
if (agentId == null) if (agentId == null)
return new ArrayList<MBeanServer>(mBeanServerList); return new ArrayList<MBeanServer>(getMBeanServerList());
ArrayList<MBeanServer> result = new ArrayList<MBeanServer>(); ArrayList<MBeanServer> result = new ArrayList<MBeanServer>();
for (MBeanServer mbs : mBeanServerList) { for (MBeanServer mbs : getMBeanServerList()) {
String name = mBeanServerId(mbs); String name = mBeanServerId(mbs);
if (agentId.equals(name)) if (agentId.equals(name))
result.add(mbs); result.add(mbs);
...@@ -415,11 +417,11 @@ public class MBeanServerFactory { ...@@ -415,11 +417,11 @@ public class MBeanServerFactory {
} }
private static synchronized void addMBeanServer(MBeanServer mbs) { private static synchronized void addMBeanServer(MBeanServer mbs) {
mBeanServerList.add(mbs); getMBeanServerList().add(mbs);
} }
private static synchronized void removeMBeanServer(MBeanServer mbs) { private static synchronized void removeMBeanServer(MBeanServer mbs) {
boolean removed = mBeanServerList.remove(mbs); boolean removed = getMBeanServerList().remove(mbs);
if (!removed) { if (!removed) {
MBEANSERVER_LOGGER.logp(Level.FINER, MBEANSERVER_LOGGER.logp(Level.FINER,
MBeanServerFactory.class.getName(), MBeanServerFactory.class.getName(),
...@@ -432,6 +434,15 @@ public class MBeanServerFactory { ...@@ -432,6 +434,15 @@ public class MBeanServerFactory {
private static final ArrayList<MBeanServer> mBeanServerList = private static final ArrayList<MBeanServer> mBeanServerList =
new ArrayList<MBeanServer>(); 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. * Load the builder class through the context class loader.
* @param builderClassName The name of the builder class. * @param builderClassName The name of the builder class.
......
...@@ -61,6 +61,7 @@ public class SharedSecrets { ...@@ -61,6 +61,7 @@ public class SharedSecrets {
private static JavaxCryptoSealedObjectAccess javaxCryptoSealedObjectAccess; private static JavaxCryptoSealedObjectAccess javaxCryptoSealedObjectAccess;
private static JavaObjectInputStreamReadString javaObjectInputStreamReadString; private static JavaObjectInputStreamReadString javaObjectInputStreamReadString;
private static JavaObjectInputStreamAccess javaObjectInputStreamAccess; private static JavaObjectInputStreamAccess javaObjectInputStreamAccess;
private static TenantAccess tenantAccess;
public static JavaUtilJarAccess javaUtilJarAccess() { public static JavaUtilJarAccess javaUtilJarAccess() {
if (javaUtilJarAccess == null) { if (javaUtilJarAccess == null) {
...@@ -235,4 +236,12 @@ public class SharedSecrets { ...@@ -235,4 +236,12 @@ public class SharedSecrets {
} }
return javaxCryptoSealedObjectAccess; 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.
先完成此消息的编辑!
想要评论请 注册