diff --git a/make/CopyFiles.gmk b/make/CopyFiles.gmk index cb9f98f86c43fdf712fe68d791b6de24b9033ab6..be9595f8af18dc178effe10a04814802171cbee3 100644 --- a/make/CopyFiles.gmk +++ b/make/CopyFiles.gmk @@ -43,7 +43,8 @@ H_TARGET_FILES = $(INCLUDEDIR)/jdwpTransport.h \ $(INCLUDEDIR)/classfile_constants.h \ $(INCLUDEDIR)/jawt.h \ $(OPENJDK_TARGET_OS_INCLUDE)/jni_md.h \ - $(OPENJDK_TARGET_OS_INCLUDE)/jawt_md.h + $(OPENJDK_TARGET_OS_INCLUDE)/jawt_md.h \ + $(INCLUDEDIR)/tenantenv.h \ $(INCLUDEDIR)/%.h: $(JDK_TOPDIR)/src/share/javavm/export/%.h $(call install-file) diff --git a/make/CreateJars.gmk b/make/CreateJars.gmk index f1628154f86a62a3acba4dcbf0c35ee3399cce29..6d85be350aa1733e75961cd5b6230b93ebf57d97 100644 --- a/make/CreateJars.gmk +++ b/make/CreateJars.gmk @@ -560,7 +560,8 @@ EXPORTED_PRIVATE_PKGS = com.oracle.net \ com.alibaba.jwarmup \ com.alibaba.management \ com.alibaba.jvm.gc \ - com.alibaba.rcm + com.alibaba.rcm \ + com.alibaba.tenant $(IMAGES_OUTPUTDIR)/symbols/_the.symbols: $(IMAGES_OUTPUTDIR)/lib/rt.jar $(RM) -r $(IMAGES_OUTPUTDIR)/symbols/META-INF/sym diff --git a/make/data/classlist/classlist.linux b/make/data/classlist/classlist.linux index 466f1abadfe6ea69430c430e8b85e572dbc6bc29..86a2a150bec267ee48271596b14c2c8336156d62 100644 --- a/make/data/classlist/classlist.linux +++ b/make/data/classlist/classlist.linux @@ -2559,4 +2559,10 @@ sun/awt/X11/XErrorEvent com/alibaba/jwarmup/JWarmUp com/alibaba/management com/alibaba/jvm/gc -# eea35d9d56e0006e +com/alibaba/tenant/TenantContainer +com/alibaba/tenant/TenantConfiguration +com/alibaba/tenant/TenantException +com/alibaba/tenant/TenantState +com/alibaba/tenant/TenantGlobals +com/alibaba/tenant/TenantContainerFactory +# eea35d9d56e0006e \ No newline at end of file diff --git a/make/lib/CoreLibraries.gmk b/make/lib/CoreLibraries.gmk index 910fbc9dbd2f03a5d3591f6aa23284a1b6956d3f..ffdeb712b3112b5e2d80796c400eeda0667c1a51 100644 --- a/make/lib/CoreLibraries.gmk +++ b/make/lib/CoreLibraries.gmk @@ -143,6 +143,7 @@ LIBJAVA_SRC_DIRS += $(JDK_TOPDIR)/src/$(OPENJDK_TARGET_OS_API_DIR)/native/java/l $(JDK_TOPDIR)/src/share/native/common \ $(JDK_TOPDIR)/src/share/native/com/alibaba/jwarmup \ $(JDK_TOPDIR)/src/share/native/com/alibaba/jvm/gc \ + $(JDK_TOPDIR)/src/share/native/com/alibaba/tenant \ $(JDK_TOPDIR)/src/share/native/sun/misc \ $(JDK_TOPDIR)/src/share/native/sun/reflect \ $(JDK_TOPDIR)/src/share/native/java/util \ diff --git a/make/mapfiles/libjava/mapfile-vers b/make/mapfiles/libjava/mapfile-vers index 178a7604c6fc5a4a541d01f961045de91702b947..4725579998fe8efbc490e9fe39a8a46a2a717aca 100644 --- a/make/mapfiles/libjava/mapfile-vers +++ b/make/mapfiles/libjava/mapfile-vers @@ -215,7 +215,10 @@ SUNWprivate_1.1 { Java_java_lang_System_setIn0; Java_java_lang_System_setOut0; Java_java_lang_Thread_registerNatives; + Java_com_alibaba_tenant_NativeDispatcher_registerNatives0; + Java_com_alibaba_tenant_NativeDispatcher_getThreadsAllocatedMemory; Java_com_alibaba_jwarmup_JWarmUp_registerNatives; + Java_com_alibaba_tenant_TenantGlobals_getTenantFlags; Java_com_alibaba_jvm_gc_ElasticHeapMXBeanImpl_registerNatives; Java_java_lang_Throwable_fillInStackTrace; Java_java_lang_Throwable_getStackTraceDepth; diff --git a/make/mapfiles/libjava/reorder-x86 b/make/mapfiles/libjava/reorder-x86 index 91361ea63682629fcc170280f7cddad51011c97c..05792d0e304f82f458aa709da124b7f45c0519f7 100644 --- a/make/mapfiles/libjava/reorder-x86 +++ b/make/mapfiles/libjava/reorder-x86 @@ -10,6 +10,7 @@ text: .text%collapse: OUTPUTDIR/canonicalize_md.o; text: .text%Java_java_lang_Object_registerNatives; text: .text%Java_java_lang_System_registerNatives; text: .text%Java_java_lang_Thread_registerNatives; +text: .text%Java_com_alibaba_tenant_TenantGlobals_getTenantFlags; text: .text%Java_com_alibaba_jwarmup_JWarmUp_registerNatives; text: .text%Java_com_alibaba_jvm_gc_ElasticHeapMXBeanImpl_registerNatives; text: .text%Java_java_security_AccessController_getStackAccessControlContext; diff --git a/src/share/classes/com/alibaba/management/TenantContainerMXBean.java b/src/share/classes/com/alibaba/management/TenantContainerMXBean.java new file mode 100644 index 0000000000000000000000000000000000000000..23a000c10ba101e26335449a51dd76a62fcc67cc --- /dev/null +++ b/src/share/classes/com/alibaba/management/TenantContainerMXBean.java @@ -0,0 +1,35 @@ +package com.alibaba.management; + +import java.lang.management.PlatformManagedObject; +import java.util.List; + +/** + * MXBean interface to interact with TenantContainer object + */ +public interface TenantContainerMXBean extends PlatformManagedObject { + + /** + * Retrieve all the IDs of all created {@code TenantContainer}'s + * @return List of TenantContainer IDs + */ + public List getAllTenantIds(); + + /** + * Retrieve the accumulated allocated memory size in bytes + * of a {@code TenantContainer} represented by {@code id} + * @param id ID of the {@code TenantContainer} object to be queried + * @return Accumulated allocated memory size in bytes + * @throws IllegalArgumentException If cannot find valid {@code TenantContainer} + * object corresponding to the given ID + */ + public long getTenantAllocatedMemoryById(long id); + + /** + * Retrieve name of a {@code TenantContainer} object represent by 'id' + * @param id ID of the {@code TenantContainer} object to be queried + * @return Name of found ID + * @throws IllegalArgumentException If cannot find valid {@code TenantContainer} + * object corresponding to the given ID + */ + public String getTenantNameById(long id); +} diff --git a/src/share/classes/com/alibaba/tenant/NativeDispatcher.java b/src/share/classes/com/alibaba/tenant/NativeDispatcher.java new file mode 100644 index 0000000000000000000000000000000000000000..e09f4bcb6b32c78456a5b458a2bef425cc8a76f7 --- /dev/null +++ b/src/share/classes/com/alibaba/tenant/NativeDispatcher.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package com.alibaba.tenant; + +import java.security.AccessController; + +/** + * All cgroup operations are in this class + * + */ +class NativeDispatcher { + + // Attach the current thread to given {@code tenant} + native void attach(TenantContainer tenant); + + // Gets an array containing the amount of memory allocated on the Java heap for a set of threads (in bytes) + native void getThreadsAllocatedMemory(long[] ids, long[] memSizes); + + private static native void registerNatives0(); + + static { + AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Void run() { + System.loadLibrary("java"); + return null; + } + }); + registerNatives0(); + } +} \ No newline at end of file diff --git a/src/share/classes/com/alibaba/tenant/TenantConfiguration.java b/src/share/classes/com/alibaba/tenant/TenantConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..71179370729e0c04128573ccc92257ad0730ae54 --- /dev/null +++ b/src/share/classes/com/alibaba/tenant/TenantConfiguration.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package com.alibaba.tenant; + +import sun.misc.SharedSecrets; +import sun.misc.VM; +import sun.security.action.GetPropertyAction; +import java.security.AccessController; +import java.util.HashMap; +import java.util.Map; +import java.util.Collection; +import com.alibaba.rcm.ResourceType; +import com.alibaba.rcm.Constraint; + +/** + * + * The configuration used by tenant + */ +public class TenantConfiguration { + + /* + * Resource throttling configurations + */ + private Map constraints = new HashMap<>(); + + /** + * Create an empty TenantConfiguration, no limitations on any resource + */ + public TenantConfiguration() { + } + + /** + * Build TenantConfiguration with given constraints + * @param constraints + */ + public TenantConfiguration(Iterable constraints) { + if (constraints != null) { + for (Constraint c : constraints) { + this.constraints.put(c.getResourceType(), c); + } + } + } + + /* + * @return all resource limits specified by this configuration + */ + Collection getAllConstraints() { + return constraints.values(); + } + + void setConstraint(Constraint constraint) { + constraints.put(constraint.getResourceType(), constraint); + } +} diff --git a/src/share/classes/com/alibaba/tenant/TenantContainer.java b/src/share/classes/com/alibaba/tenant/TenantContainer.java new file mode 100644 index 0000000000000000000000000000000000000000..a1309eb0c481ccd20def352aa1710b5a3c4e3f7e --- /dev/null +++ b/src/share/classes/com/alibaba/tenant/TenantContainer.java @@ -0,0 +1,662 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package com.alibaba.tenant; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.LinkedList; +import java.util.Map; +import java.util.Properties; +import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import com.alibaba.rcm.ResourceContainer; +import com.alibaba.rcm.Constraint; +import com.alibaba.rcm.internal.AbstractResourceContainer; +import static com.alibaba.tenant.TenantState.*; + + +/** + * TenantContainer is a "virtual container" for a tenant of application, the + * resource consumption of tenant such as CPU, heap is constrained by the policy + * of this "virtual container". The thread can run in virtual container by + * calling TenantContainer.run + * + */ +public class TenantContainer { + + TenantResourceContainer resourceContainer; + + private static NativeDispatcher nd = new NativeDispatcher(); + + /* + * Used to generate the tenant id. + */ + private static AtomicLong nextTenantID = new AtomicLong(0); + + /* + * Used to hold the mapping from tenant id to TenantContainer object for all + * tenants + */ + private static Map tenantContainerMap = null; + + /* + * Holds the threads attached with this tenant container + */ + private List attachedThreads = new LinkedList<>(); + + /* + * Newly created threads which attach to this tenant container + */ + private List> spawnedThreads = Collections.synchronizedList(new ArrayList<>()); + + /* + * Used to contain service threads, including finalizer threads, shutdown hook threads. + */ + private Map serviceThreads = Collections.synchronizedMap(new WeakHashMap<>()); + + /* + * the configuration of this tenant container + */ + private TenantConfiguration configuration = null; + + /* + * tenant state + */ + private volatile TenantState state; + + /* + * tenant id + */ + private long tenantId; + + /* + * tenant name + */ + private String name; + + /* + * Used to store the system properties per tenant + */ + private Properties props; + + /* + * allocated memory of attached threads, which is accumulated for current tenant + */ + private long accumulatedMemory = 0L; + + /** + * Get total allocated memory of this tenant. + * @return the total allocated memory of this tenant. + */ + public synchronized long getAllocatedMemory() { + Thread[] threads = getAttachedThreads(); + int size = threads.length; + long[] ids = new long[size]; + long[] memSizes = new long[size]; + + for (int i = 0; i < size; i++) { + ids[i] = threads[i].getId(); + } + + nd.getThreadsAllocatedMemory(ids, memSizes); + + long totalThreadsAllocatedMemory = 0; + for (long s : memSizes) { + totalThreadsAllocatedMemory += s; + } + return totalThreadsAllocatedMemory + accumulatedMemory; + } + + /** + * Used to track and run tenant shutdown hooks + */ + private TenantShutdownHooks tenantShutdownHooks = new TenantShutdownHooks(); + + /** + * 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. + */ + public void setProperties(Properties props) { + if (props == null) { + props = new Properties(); + Properties sysProps = System.getProperties(); + for(Object key: sysProps.keySet()) { + props.put(key, sysProps.get(key)); + } + } + this.props = props; + } + + /** + * Gets the properties of tenant + * @return the tenant properties + */ + public Properties getProperties() { + return props; + } + + /** + * Sets the property indicated by the specified key. + * @param key the name of the property. + * @param value the value of the property. + * @return the previous value of the property, + * or null if it did not have one. + */ + public String setProperty(String key, String value) { + checkKey(key); + return (String) props.setProperty(key, value); + } + + /** + * Gets the property indicated by the specified key. + * @param key the name of the property. + * @return the string value of the property, + * or null if there is no property with that key. + */ + public String getProperty(String key) { + checkKey(key); + return props.getProperty(key); + } + + /** + * Removes the property indicated by the specified key. + * @param key the name of the property to be removed. + * @return the previous string value of the property, + * or null if there was no property with that key. + */ + public String clearProperty(String key) { + checkKey(key); + return (String) props.remove(key); + } + + private void checkKey(String key) { + if (null == key) { + throw new NullPointerException("key can't be null"); + } + if ("".equals(key)) { + throw new IllegalArgumentException("key can't be empty"); + } + } + + // + // Used to synchronize between destroy() and runThread() + private ReentrantReadWriteLock destroyLock = new ReentrantReadWriteLock(); + + + /** + * Destroy this tenant container and release occupied resources including memory, cpu, FD, etc. + * + */ + public void destroy() { + if (TenantContainer.current() != null) { + throw new RuntimeException("Should only call destroy() in ROOT tenant"); + } + + destroyLock.writeLock().lock(); + try { + if (state != TenantState.STOPPING && state != TenantState.DEAD) { + setState(TenantState.STOPPING); + + tenantContainerMap.remove(getTenantId()); + + // finish all finalizers + resourceContainer.attach(); + nd.attach(this); + try { + Runtime.getRuntime().runFinalization(); + } finally { + nd.attach(null); + resourceContainer.detach(); + } + + // execute all shutdown hooks + tenantShutdownHooks.runHooks(); + } + } catch (Throwable t) { + System.err.println("Exception from TenantContainer.destroy()"); + t.printStackTrace(); + } finally { + setState(TenantState.DEAD); + cleanUp(); + destroyLock.writeLock().unlock(); + } + } + + /* + * Release all native resources and Java references + * should be the very last step of {@link #destroy()} operation. + * If cannot kill all threads in {@link #killAllThreads()}, should do this in {@link WatchDogThread} + * + */ + private void cleanUp() { + // clear references + spawnedThreads.clear(); + attachedThreads.clear(); + tenantShutdownHooks = null; + } + + private TenantContainer(TenantContainer parent, String name, TenantConfiguration configuration) { + this.tenantId = nextTenantID.getAndIncrement(); + this.resourceContainer = new TenantResourceContainer( + (parent == null ? null : parent.resourceContainer), + this, + configuration.getAllConstraints()); + this.name = (name == null ? "Tenant-" + getTenantId() : name); + this.configuration = configuration; + + setState(STARTING); + + //Initialize the tenant properties. + props = new Properties(); + props.putAll(System.getProperties()); + tenantContainerMap.put(this.tenantId, this); + } + + TenantConfiguration getConfiguration() { + return configuration; + } + + /** + * @return the tenant state + */ + public TenantState getState() { + return state; + } + + /* + * Set the tenant state + * @param state used to set + */ + void setState(TenantState state) { + this.state = state; + } + + /** + * Returns the tenant' id + * @return tenant id + */ + public long getTenantId() { + return tenantId; + } + + /** + * Returns this tenant's name. + * @return this tenant's name. + */ + public String getName() { + return name; + } + + /** + * @return A collection of all threads attached to the container. + */ + public synchronized Thread[] getAttachedThreads() { + return attachedThreads.toArray(new Thread[attachedThreads.size()]); + } + + /** + * Get the tenant container by id + * @param id tenant id. + * @return the tenant specified by id, null if the id doesn't exist. + */ + public static TenantContainer getTenantContainerById(long id) { + checkIfTenantIsEnabled(); + return tenantContainerMap.get(id); + } + + /** + * Create tenant container by the configuration + * @param configuration used to create tenant + * @return the tenant container + */ + public static TenantContainer create(TenantConfiguration configuration) { + return create(TenantContainer.current(), configuration); + } + + /** + * Create tenant container by the configuration + * @param parent parent tenant container + * @param configuration used to create tenant + * @return the tenant container + */ + public static TenantContainer create(TenantContainer parent, + TenantConfiguration configuration) { + checkIfTenantIsEnabled(); + //parameter checking + if (null == configuration) { + throw new IllegalArgumentException("Failed to create tenant, illegal arguments: configuration is null"); + } + + return create(parent, null, configuration); + } + /** + * Create tenant container by the name and configuration + * @param name the tenant name + * @param configuration used to create tenant + * @return the tenant container + */ + public static TenantContainer create(String name, TenantConfiguration configuration) { + return create(TenantContainer.current(), name, configuration); + } + + /** + * Create tenant container by the name and configuration + * @param parent parent tenant container + * @param name the tenant name + * @param configuration used to create tenant + * @return the tenant container + */ + public static TenantContainer create(TenantContainer parent, String name, TenantConfiguration configuration) { + checkIfTenantIsEnabled(); + //parameter checking + if (null == configuration) { + throw new IllegalArgumentException("Failed to create tenant, illegal arguments: configuration is null"); + } + TenantContainer tc = new TenantContainer(parent, name, configuration); + tenantContainerMap.put(tc.getTenantId(), tc); + return tc; + } + + /** + * Gets the tenant id list + * @return the tenant id list, Collections.emptyList if no tenant exists. + */ + public static List getAllTenantIds() { + checkIfTenantIsEnabled(); + if (null == tenantContainerMap) { + throw new IllegalStateException("TenantContainer class is not initialized !"); + } + if (tenantContainerMap.size() == 0) { + return Collections.EMPTY_LIST; + } + + return new ArrayList<>(tenantContainerMap.keySet()); + } + + /** + * Gets the TenantContainer attached to the current thread. + * @return The TenantContainer attached to the current thread, null if no + * TenantContainer is attached to the current thread. + */ + public static TenantContainer current() { + checkIfTenantIsEnabled(); + AbstractResourceContainer curResContainer = TenantResourceContainer.current(); + if (TenantResourceContainer.root() == curResContainer) { + return null; + } + assert curResContainer instanceof TenantResourceContainer; + return ((TenantResourceContainer)curResContainer).getTenant(); + } + + /** + * Gets the cpu time consumed by this tenant + * @return the cpu time used by this tenant, 0 if tenant cpu throttling or accounting feature is disabled. + */ + public long getProcessCpuTime() { + if (!TenantGlobals.isCpuAccountingEnabled()) { + throw new IllegalStateException("-XX:+TenantCpuAccounting is not enabled"); + } + long cpuTime = 0; + return cpuTime; + } + + /** + * Runs the code in the target tenant container + * @param task the code to run + */ + public void run(Runnable task) throws TenantException { + if (getState() == DEAD || getState() == STOPPING) { + throw new TenantException("Tenant is dead"); + } + TenantContainer container = current(); + assert container != null; + if (container == this) { + task.run(); + } else { + if (container != null) { + throw new TenantException("must be in root container " + + "before running into non-root container."); + } + attach(); + try { + task.run(); + } finally { + detach(); + } + } + } + + /* + * Get accumulatedMemory value of current thread + */ + private long getThreadAllocatedMemory() { + long[] memSizes = new long[1]; + nd.getThreadsAllocatedMemory(null, memSizes); + return memSizes[0]; + } + + private void attach() { + // This is the first thread which runs in this tenant container + if (getState() == TenantState.STARTING) { + // move the tenant state to RUNNING + this.setState(TenantState.RUNNING); + } + + Thread curThread = Thread.currentThread(); + + long curAllocBytes = getThreadAllocatedMemory(); + + synchronized (this) { + attachedThreads.add(curThread); + + accumulatedMemory -= curAllocBytes; + + resourceContainer.attach(); + + nd.attach(this); + } + } + + private void detach() { + Thread curThread = Thread.currentThread(); + + long curAllocBytes = getThreadAllocatedMemory(); + + synchronized (this) { + nd.attach(null); + + resourceContainer.detach(); + + attachedThreads.remove(curThread); + + accumulatedMemory += curAllocBytes; + } + } + + /* + * Check if the tenant feature is enabled. + */ + private static void checkIfTenantIsEnabled() { + if (!TenantGlobals.isTenantEnabled()) { + throw new UnsupportedOperationException("The multi-tenant feature is not enabled!"); + } + } + + /* + * Invoked by the VM to run a thread in multi-tenant mode. + * + * NOTE: please ensure relevant logic has been fully understood before changing any code + * + * @throws TenantException + */ + private void runThread(final Thread thread) throws TenantException { + if (destroyLock.readLock().tryLock()) { + if (getState() != STOPPING && getState() != DEAD) { + spawnedThreads.add(new WeakReference<>(thread)); + this.run(() -> { + destroyLock.readLock().unlock(); + thread.run(); + }); + } else { + destroyLock.readLock().unlock(); + } + + // try to clean up once + if (destroyLock.readLock().tryLock()) { + if (getState() != STOPPING && getState() != DEAD) { + spawnedThreads.removeIf(ref -> ref.get() == null || ref.get() == thread); + } + destroyLock.readLock().unlock(); + } + } else { + // shutdown in progress + if (serviceThreads.containsKey(thread)) { + // attach to current thread to run without registering + resourceContainer.attach(); + nd.attach(this); + try { + thread.run(); + } finally { + nd.attach(null); + resourceContainer.detach(); + removeServiceThread(thread); + } + } + } + } + + /* + * Initialize the TenantContainer class, called after System.initializeSystemClass by VM. + */ + private static void initializeTenantContainerClass() { + //Initialize this field after the system is booted. + tenantContainerMap = Collections.synchronizedMap(new HashMap()); + + try { + // force initialization of TenantConfiguration + Class.forName("com.alibaba.tenant.TenantConfiguration"); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + + /** + * Runs {@code Supplier.get} in the root tenant. + * @param supplier target used to call + * @return the result of {@code Supplier.get} + */ + public static T primitiveRunInRoot(Supplier supplier) { + // thread is already in root tenant. + if(null == TenantContainer.current()) { + return supplier.get(); + } else{ + TenantContainer tenant = TenantContainer.current(); + //Force to root tenant. + tenant.resourceContainer.detach(); + nd.attach(null); + try { + T t = supplier.get(); + return t; + } finally { + nd.attach(tenant); + tenant.resourceContainer.attach(); + } + } + } + + /** + * Runs a block of code in the root tenant. + * @param runnable the code to run + */ + public static void primitiveRunInRoot(Runnable runnable) { + primitiveRunInRoot(() -> { + runnable.run(); + return null; + }); + } + + /** + * Register a new tenant shutdown hook. + * When the tenant begins its destroy it will + * start all registered shutdown hooks in some unspecified order and let + * them run concurrently. + * @param hook + * An initialized but unstarted {@link Thread} object + */ + public void addShutdownHook(Thread hook) { + addServiceThread(hook); + tenantShutdownHooks.add(hook); + } + + /** + * De-registers a previously-registered tenant shutdown hook. + * @param hook the hook to remove + * @return true if the specified hook had previously been + * registered and was successfully de-registered, false + * otherwise. + */ + public boolean removeShutdownHook(Thread hook) { + removeServiceThread(hook); + return tenantShutdownHooks.remove(hook); + } + + // add a thread to the service thread list + private void addServiceThread(Thread thread) { + if (thread != null) { + serviceThreads.put(thread, null); + } + } + + // remove a thread from the service thread list + private void removeServiceThread(Thread thread) { + serviceThreads.remove(thread); + } + + /** + * Try to modify resource limit of current tenant, + * for resource whose limit cannot be changed after creation of {@code TenantContainer}, its limit will be ignored. + * @param config new TenantConfiguration to + */ + public void update(TenantConfiguration config) { + for (Constraint constraint : config.getAllConstraints()) { + updateConstraint(constraint); + } + } + + void updateConstraint(Constraint constraint) { + resourceContainer.updateConstraint(constraint); + getConfiguration().setConstraint(constraint); + } + + /** + * @return {@code ResourceContainer} of this tenant + */ + public ResourceContainer getResourceContainer() { + return resourceContainer; + } +} + diff --git a/src/share/classes/com/alibaba/tenant/TenantContainerFactory.java b/src/share/classes/com/alibaba/tenant/TenantContainerFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..7341a4322bc5c278e8c2e46c404e53a49ae67e6c --- /dev/null +++ b/src/share/classes/com/alibaba/tenant/TenantContainerFactory.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package com.alibaba.tenant; + +import com.alibaba.rcm.Constraint; +import com.alibaba.rcm.ResourceContainer; +import com.alibaba.rcm.ResourceContainerFactory; + +/** + * Singleton factory specialized for multi-tenant {@code ResourceContainer} + * With support of new RCM API (com.alibaba.rcm) + * + */ +public class TenantContainerFactory implements ResourceContainerFactory { + + /** + * Create a {@code ResourceContainer} which is capable of throttling resource + * using MuliTenant facitlities. + * A {@code TenantContainer} object will be created implicitly for each successful + * call to {@code TenantContainerFactory.createContainer} + * @param constraints the target {@code Constraint}s + * @return + */ + @Override + public TenantResourceContainer createContainer(Iterable constraints) { + TenantContainer tenant = TenantContainer.create(new TenantConfiguration(constraints)); + return tenant.resourceContainer; + } + + /** + * Retrieve the {@code TenantContainer} object associated with given {@code ResourceContainer} + * + * @param resourceContainer + * @return + */ + public static TenantContainer tenantContainerOf(ResourceContainer resourceContainer) { + if (!(resourceContainer instanceof TenantResourceContainer)) { + throw new IllegalArgumentException("Incoming ResourceContainer is not for MultiTenant"); + } + return ((TenantResourceContainer) resourceContainer).getTenant(); + } + + private TenantContainerFactory() { } + + private static final class Holder { + private static final TenantContainerFactory INSTANCE = new TenantContainerFactory(); + } + + /** + * + * @return Singleton instance of TenantContainerFactory + */ + public static TenantContainerFactory instance() { + return Holder.INSTANCE; + } +} \ No newline at end of file diff --git a/src/share/classes/com/alibaba/tenant/TenantContainerMXBeanImpl.java b/src/share/classes/com/alibaba/tenant/TenantContainerMXBeanImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..50be3821f063cb3bd7b8c633b9ba8cfa5028cba4 --- /dev/null +++ b/src/share/classes/com/alibaba/tenant/TenantContainerMXBeanImpl.java @@ -0,0 +1,41 @@ +package com.alibaba.tenant; + +import com.alibaba.management.TenantContainerMXBean; +import sun.management.Util; +import javax.management.ObjectName; +import java.util.List; + +/** + * Implementation class for TenantContainerMXBean. + */ +public class TenantContainerMXBeanImpl implements TenantContainerMXBean { + + private final static String TENANT_CONTAINER_MXBEAN_NAME = "com.alibaba.management:type=TenantContainer"; + + @Override + public List getAllTenantIds() { + return TenantContainer.getAllTenantIds(); + } + + public long getTenantAllocatedMemoryById(long id) { + TenantContainer container = TenantContainer.getTenantContainerById(id); + if (null == container) { + throw new IllegalArgumentException("The id of tenant is invalid !"); + } + return container.getAllocatedMemory(); + } + + @Override + public ObjectName getObjectName() { + return Util.newObjectName(TENANT_CONTAINER_MXBEAN_NAME); + } + + @Override + public String getTenantNameById(long id) { + TenantContainer container = TenantContainer.getTenantContainerById(id); + if (null == container) { + throw new IllegalArgumentException("The id of tenant is invalid !"); + } + return container.getName(); + } +} diff --git a/src/share/classes/com/alibaba/tenant/TenantException.java b/src/share/classes/com/alibaba/tenant/TenantException.java new file mode 100644 index 0000000000000000000000000000000000000000..1249e24f0906f5c652509dbc5706a30fce42a7d4 --- /dev/null +++ b/src/share/classes/com/alibaba/tenant/TenantException.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package com.alibaba.tenant; + +/** + * Will be thrown when problems found with running a given code snippet + * in a {@code TenantContainer} with method {@code TenantContainer.run()}, + * or from newly created thread in a TenantContainer. + * + */ +public class TenantException extends Exception { + private static final long serialVersionUID = 436814125546098458L; + + /** + * Construct a {@code TenantException} object with given message + */ + public TenantException(String msg) { + super(msg); + } + +} diff --git a/src/share/classes/com/alibaba/tenant/TenantGlobals.java b/src/share/classes/com/alibaba/tenant/TenantGlobals.java new file mode 100644 index 0000000000000000000000000000000000000000..51e1c6b2285a4a8ffe3471b91cebf7db45ed4d48 --- /dev/null +++ b/src/share/classes/com/alibaba/tenant/TenantGlobals.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package com.alibaba.tenant; + +/** + * This class defines the constants used by multi-tenant JDK + */ +public class TenantGlobals { + /** + * Retrieves the flags used by multi-tenant module. + * @return the flags + */ + private native static int getTenantFlags(); + + private final static int flags = getTenantFlags(); + + /**** Be careful: the following bit definitions must be consistent with + **** the ones defined in prims/tenantenv.cpp **/ + + /** + * Bit to indicate that if the multi-tenant feature is enabled. + */ + public static final int TENANT_FLAG_MULTI_TENANT_ENABLED = 0x1; + + /** + * Bit to indicate that if heap throttling feature is enabled + */ + public static final int TENANT_FLAG_HEAP_THROTTLING_ENABLED = 0x2; + + /** + * Bit to indicate that if cpu throttling feature is enabled + */ + public static final int TENANT_FLAG_CPU_THROTTLING_ENABLED = 0x4; + + /** + * Bit to indicate that if cpu accounting feature is enabled + */ + public static final int TENANT_FLAG_CPU_ACCOUNTING_ENABLED = 0x40; + + /** + * Bit to indicate that if heap isolation feature is enabled + */ + public static final int TENANT_FLAG_HEAP_ISOLATION_ENABLED = 0x80; + + private TenantGlobals() { } + + /** + * Test if multi-tenant feature is enabled. + * @return true if enabled otherwise false + */ + public static boolean isTenantEnabled() { + return 0 != (flags & TENANT_FLAG_MULTI_TENANT_ENABLED); + } + + /** + * Test if heap throttling feature is enabled. + * @return true if enabled otherwise false + */ + public static boolean isHeapThrottlingEnabled() { + return 0 != (flags & TENANT_FLAG_HEAP_THROTTLING_ENABLED); + } + + /** + * Test if heap isolation feature is enabled. + * @return true if enabled otherwise false + */ + public static boolean isHeapIsolationEnabled() { + return 0 != (flags & TENANT_FLAG_HEAP_ISOLATION_ENABLED); + } + + + /** + * Test if cpu throttling feature is enabled. + * @return true if enabled otherwise false + */ + public static boolean isCpuThrottlingEnabled() { + return 0 != (flags & TENANT_FLAG_CPU_THROTTLING_ENABLED); + } + + /** + * Test if cpu accounting feature is enabled. + * @return true if enabled otherwise false + */ + public static boolean isCpuAccountingEnabled() { + return 0 != (flags & TENANT_FLAG_CPU_ACCOUNTING_ENABLED); + } +} diff --git a/src/share/classes/com/alibaba/tenant/TenantResourceContainer.java b/src/share/classes/com/alibaba/tenant/TenantResourceContainer.java new file mode 100644 index 0000000000000000000000000000000000000000..8e0d4e84ba7387fd2c8a6ad3cbaa4be1d2324b98 --- /dev/null +++ b/src/share/classes/com/alibaba/tenant/TenantResourceContainer.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package com.alibaba.tenant; + +import com.alibaba.rcm.Constraint; +import com.alibaba.rcm.ResourceType; +import com.alibaba.rcm.internal.AbstractResourceContainer; +import java.util.HashMap; +import java.util.Map; +import static com.alibaba.tenant.TenantState.*; + +class TenantResourceContainer extends AbstractResourceContainer { + + TenantResourceContainer(TenantResourceContainer parent, + TenantContainer tenant, + Iterable constraints) { + super(); + this.parent = parent; + this.constraints = new HashMap<>(); + this.tenant = tenant; + if (constraints != null) { + constraints.forEach(c -> this.constraints.put(c.getResourceType(), c)); + } + init(); + } + + private void init() { + if (TenantGlobals.isCpuThrottlingEnabled() || TenantGlobals.isCpuAccountingEnabled()) { + if (constraints.containsKey(ResourceType.CPU_PERCENT)) { + Constraint c = translate(constraints.remove(ResourceType.CPU_PERCENT)); + constraints.put(c.getResourceType(), c); + } + } + } + + private static Constraint translate(Constraint constraint) { + return constraint; + } + + // cached constraints + private Map constraints; + + /* + * The parent container. + */ + private TenantResourceContainer parent; + + /* + * Associated TenantContainer object + */ + private TenantContainer tenant; + + TenantResourceContainer getParent() { + return parent; + } + + TenantContainer getTenant() { + return this.tenant; + } + + @Override + protected void attach() { + super.attach(); + } + + @Override + protected void detach() { + super.detach(); + } + + + @Override + public void run(Runnable command) { + // This is the first thread which runs in this tenant container + if (tenant.getState() == STARTING) { + // move the tenant state to RUNNING + tenant.setState(RUNNING); + } + super.run(command); + } + + @Override + public State getState() { + return TenantState.translate(tenant.getState()); + } + + @Override + public void updateConstraint(Constraint constraint) { + Constraint c = translate(constraint); + constraints.put(c.getResourceType(), c); + } + + @Override + public Iterable getConstraints() { + return constraints.values(); + } + + @Override + public void destroy() { + throw new UnsupportedOperationException("Should not call TenantResourceContainer::destroy() directly"); + } + + void destroyImpl() { + parent = null; + tenant = null; + } + + // exposed to TenantContainer implementation + long getProcessCpuTime() { + return 0; + } +} diff --git a/src/share/classes/com/alibaba/tenant/TenantResourceType.java b/src/share/classes/com/alibaba/tenant/TenantResourceType.java new file mode 100644 index 0000000000000000000000000000000000000000..f1cff5fc0275a0266907e4f9d07f9ac0e07993e0 --- /dev/null +++ b/src/share/classes/com/alibaba/tenant/TenantResourceType.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package com.alibaba.tenant; + +import com.alibaba.rcm.ResourceType; + +/* + * Type of resource that can be throttled when MultiTenant feature enabled + */ +class TenantResourceType extends ResourceType { + private TenantResourceType(String name, boolean isJGroup) { + super(name); + } +} diff --git a/src/share/classes/com/alibaba/tenant/TenantShutdownHooks.java b/src/share/classes/com/alibaba/tenant/TenantShutdownHooks.java new file mode 100644 index 0000000000000000000000000000000000000000..d908ab7f81b0b6b194d4438f80ca1aaeb8a4259b --- /dev/null +++ b/src/share/classes/com/alibaba/tenant/TenantShutdownHooks.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package com.alibaba.tenant; + +import java.util.Collection; +import java.util.IdentityHashMap; + +/* + * Class to track and run tenant level shutdown hooks registered through + * {@link Runtime#addShutdownHook Runtime.addShutdownHook} or + * {@link TenantContainer#addShutdownHook TenantContainer.addShutdownHook} + * + */ +class TenantShutdownHooks { + private IdentityHashMap hooks = new IdentityHashMap<>(); + + private Collection threads = null; + + TenantShutdownHooks() { + } + + //Add a new shutdown hook. + synchronized void add(Thread hook) { + if(hooks == null) { + throw new IllegalStateException("Shutdown in progress"); + } + if (hook.isAlive()) { + throw new IllegalArgumentException("Hook already running"); + } + if (hooks.containsKey(hook)) { + throw new IllegalArgumentException("Hook previously registered"); + } + hooks.put(hook, hook); + } + + //Remove a previously-registered hook. + synchronized boolean remove(Thread hook) { + if(hooks == null) { + throw new IllegalStateException("Shutdown in progress"); + } + if (hook == null) { + throw new NullPointerException(); + } + return hooks.remove(hook) != null; + } + + /* Iterates over all hooks creating a new thread for each + * to run in. Hooks are running concurrently and this method waits for + * them to finish. This function will be called by the tenant destroying + * thread at the beginning of {@code TenantContainer.destroy}. + */ + void runHooks() { + synchronized(this) { + threads = hooks.keySet(); + hooks = null; + } + + for (Thread hook : threads) { + hook.start(); + } + for (Thread hook : threads) { + try { + hook.join(); + } catch (InterruptedException x) { } + } + } +} diff --git a/src/share/classes/com/alibaba/tenant/TenantState.java b/src/share/classes/com/alibaba/tenant/TenantState.java new file mode 100644 index 0000000000000000000000000000000000000000..afea8c1e365492133cb67b79cb6dc328ef37435d --- /dev/null +++ b/src/share/classes/com/alibaba/tenant/TenantState.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package com.alibaba.tenant; + +import com.alibaba.rcm.ResourceContainer.State; + +/** + * Defines the state used by TenantContainer + */ +public enum TenantState { + /** + * Just created + */ + STARTING, + /** + * At least one task has been submitted after creation + */ + RUNNING, + /** + * {@code TenantContainer.destroy()} has been called, no new thread + * can be created in this container. + */ + STOPPING, + /** + * All resource has been released fromm this tenant. + * No new calls to {@code TenantContainer.run()} can happen. + */ + DEAD; + + static TenantState translate(State state) { + return TenantState.values()[state.ordinal()]; + } + + static State translate(TenantState tenantState) { + return State.values()[tenantState.ordinal()]; + } +} diff --git a/src/share/classes/java/lang/Thread.java b/src/share/classes/java/lang/Thread.java index 01bc46f154b7069e532ddd69550890d518f54cc3..e67486e9f898041fce600032d7951320bf666e47 100644 --- a/src/share/classes/java/lang/Thread.java +++ b/src/share/classes/java/lang/Thread.java @@ -37,10 +37,13 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.LockSupport; import com.alibaba.rcm.internal.AbstractResourceContainer; +import sun.misc.VM; import sun.nio.ch.Interruptible; import sun.reflect.CallerSensitive; import sun.reflect.Reflection; import sun.security.util.SecurityConstants; +import com.alibaba.tenant.TenantContainer; +import com.alibaba.tenant.TenantGlobals; /** @@ -214,6 +217,11 @@ class Thread implements Runnable { private volatile int threadStatus = 0; + /** + * The tenant container which creates this thread object + */ + TenantContainer inheritedTenantContainer; + /** * The thread attached {@code ResourceContainer} */ @@ -430,8 +438,12 @@ class Thread implements Runnable { tid = nextThreadID(); /* com.alibaba.rcm API */ - this.resourceContainer = parent.resourceContainer != null ? - parent.resourceContainer : AbstractResourceContainer.root(); + this.resourceContainer = AbstractResourceContainer.root(); + + /* Set the tenant container */ + if (VM.isBooted() && TenantGlobals.isTenantEnabled()) { + inheritedTenantContainer = TenantContainer.current(); + } } /** @@ -775,6 +787,7 @@ class Thread implements Runnable { inheritedAccessControlContext = null; blocker = null; uncaughtExceptionHandler = null; + inheritedTenantContainer = null; } /** diff --git a/src/share/classes/java/lang/management/PlatformComponent.java b/src/share/classes/java/lang/management/PlatformComponent.java index 505b2b4b90d5e3af293296bd323bf3ee0798f355..b07d4bb2b62a62fc01250fbb0721759aa45b513b 100644 --- a/src/share/classes/java/lang/management/PlatformComponent.java +++ b/src/share/classes/java/lang/management/PlatformComponent.java @@ -35,6 +35,7 @@ import java.util.Set; import javax.management.MBeanServerConnection; import javax.management.ObjectName; +import com.alibaba.management.TenantContainerMXBean; import com.alibaba.management.ElasticHeapMXBean; import com.sun.management.HotSpotDiagnosticMXBean; import com.sun.management.UnixOperatingSystemMXBean; @@ -276,6 +277,24 @@ enum PlatformComponent { } }), + /** + * Tenant Container. + */ + TENANT_CONTAINER( + "com.alibaba.management.TenantContainerMXBean", + "com.alibaba.management", "TenantContainer", defaultKeyProperties(), + true, + new MXBeanFetcher() { + public List getMXBeans() { + TenantContainerMXBean m = ManagementFactoryHelper.getTenantContainerMXBean(); + if (null == m) { + return Collections.emptyList(); + } else { + return Collections.singletonList(m); + } + } + }), + /** * Flight Recorder. */ diff --git a/src/share/classes/sun/management/ManagementFactoryHelper.java b/src/share/classes/sun/management/ManagementFactoryHelper.java index 648ba69688e720b105d3774f3d6ee08681f995bc..3f2a1c63e79685950a1e1ddccdd5829e445b7196 100644 --- a/src/share/classes/sun/management/ManagementFactoryHelper.java +++ b/src/share/classes/sun/management/ManagementFactoryHelper.java @@ -39,7 +39,9 @@ import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +import com.alibaba.management.TenantContainerMXBean; import com.alibaba.management.ElasticHeapMXBean; +import com.alibaba.tenant.TenantContainerMXBeanImpl; import com.alibaba.jvm.gc.ElasticHeapMXBeanImpl; import sun.util.logging.LoggingSupport; @@ -70,6 +72,7 @@ public class ManagementFactoryHelper { private static RuntimeImpl runtimeMBean = null; private static CompilationImpl compileMBean = null; private static OperatingSystemImpl osMBean = null; + private static TenantContainerMXBeanImpl tenantContainerMBean = null; private static FlightRecorderMXBeanImpl flightRecorderMBean = null; private static ElasticHeapMXBeanImpl elasticHeapMXBean = null; @@ -115,6 +118,13 @@ public class ManagementFactoryHelper { return osMBean; } + public static synchronized TenantContainerMXBean getTenantContainerMXBean() { + if (tenantContainerMBean == null) { + tenantContainerMBean = new TenantContainerMXBeanImpl(); + } + return tenantContainerMBean; + } + public static synchronized FlightRecorderMXBean getFlightRecorderMXBean() { if (flightRecorderMBean == null) { flightRecorderMBean = new FlightRecorderMXBeanImpl(); diff --git a/src/share/javavm/export/jvm.h b/src/share/javavm/export/jvm.h index 162ac6033fdc6c97b39adcb540eb3910d8f19e13..18997baeb1acaff59701a42d17b4c1547bdeb42f 100644 --- a/src/share/javavm/export/jvm.h +++ b/src/share/javavm/export/jvm.h @@ -361,6 +361,12 @@ JVM_ElasticHeapGetSoftmxPercent(JNIEnv *env, jclass clazz); JNIEXPORT jlong JNICALL JVM_ElasticHeapGetTotalUncommittedBytes(JNIEnv *env, jclass clazz); +/* + * com.alibaba.tenant.TenantContainer + */ +JNIEXPORT void JNICALL +JVM_AttachToTenant(JNIEnv *env, jobject tenant); + /* * java.lang.reflect.Array */ diff --git a/src/share/javavm/export/tenantenv.h b/src/share/javavm/export/tenantenv.h new file mode 100644 index 0000000000000000000000000000000000000000..80d828f8bd73b312fd4e28dac60d1f7516b134e0 --- /dev/null +++ b/src/share/javavm/export/tenantenv.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _JAVASOFT_TENANT_ENV_H +#define _JAVASOFT_TENANT_ENV_H + +#include "jni.h" + +// 0x00200000 represents tenant module and the last 10 represents version 1.0 +#define TENANT_ENV_VERSION_1_0 0x00200010 + +/* + * Tenant Native Method Interface. + */ +struct TenantNativeInterface_; +struct TenantEnv_; + +#ifdef __cplusplus +typedef TenantEnv_ TenantEnv; +#else +typedef const struct TenantNativeInterface_* TenantEnv; +#endif + +/* + * We use inlined functions for C++ so that programmers can write: + * tenantEnv->GetTenantFlags(cls); + * in C++ rather than: + * (*tenantEnv)->GetTenantFlags(tenantEnv, cls); + * in C. + */ +struct TenantNativeInterface_ { + jint (JNICALL *GetTenantFlags)(TenantEnv *env, jclass cls); +}; + +struct TenantEnv_ { + const struct TenantNativeInterface_ *functions; +#ifdef __cplusplus + jint GetTenantFlags(jclass cls) { + return functions->GetTenantFlags(this, cls); + } +#endif +}; + +#endif // _JAVASOFT_TENANT_ENV_H_ diff --git a/src/share/native/com/alibaba/tenant/NativeDispatcher.c b/src/share/native/com/alibaba/tenant/NativeDispatcher.c new file mode 100644 index 0000000000000000000000000000000000000000..ab5208efce7ca151c93274101d4dfd9c277c05d7 --- /dev/null +++ b/src/share/native/com/alibaba/tenant/NativeDispatcher.c @@ -0,0 +1,35 @@ + +#include "jni.h" +#include "jni_util.h" +#include "jvm.h" +#include "jmm.h" +#include "com_alibaba_tenant_NativeDispatcher.h" + +#define TENANT "Lcom/alibaba/tenant/TenantContainer;" +#define THREAD "Ljava/lang/Thread;" +#define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a[0])) + +static const JmmInterface* jmm_interface = NULL; + +static JNINativeMethod methods[] = { + {"attach", "(" TENANT ")V", (void *)&JVM_AttachToTenant}, +}; + +JNIEXPORT void JNICALL +Java_com_alibaba_tenant_NativeDispatcher_registerNatives0(JNIEnv *env, jclass cls) +{ + (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods)); +} + +JNIEXPORT void JNICALL +Java_com_alibaba_tenant_NativeDispatcher_getThreadsAllocatedMemory(JNIEnv *env, + jobject obj, + jlongArray ids, + jlongArray sizeArray) +{ + if (NULL == jmm_interface) { + jmm_interface = (JmmInterface*) JVM_GetManagement(JMM_VERSION_1_0); + } + + jmm_interface->GetThreadAllocatedMemory(env, ids, sizeArray); +} diff --git a/src/share/native/com/alibaba/tenant/TenantGlobals.c b/src/share/native/com/alibaba/tenant/TenantGlobals.c new file mode 100644 index 0000000000000000000000000000000000000000..74299b223218aa960518acae955e9606930a3fe3 --- /dev/null +++ b/src/share/native/com/alibaba/tenant/TenantGlobals.c @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "jni.h" +#include "tenantenv.h" +#include "jni_util.h" +#include "jvm.h" +#include "com_alibaba_tenant_TenantGlobals.h" + +static TenantEnv* +getTenantEnv(JNIEnv *env) +{ + jint rc = JNI_ERR; + JavaVM *jvm = NULL; + TenantEnv *tenantEnv = NULL; + (*env)->GetJavaVM(env, &jvm); + if(NULL != jvm) { + /* Get tenant environment */ + rc = (*jvm)->GetEnv(jvm, (void **)&tenantEnv, TENANT_ENV_VERSION_1_0); + if (JNI_OK != rc) { + tenantEnv = NULL; + } + } + return tenantEnv; +} + +JNIEXPORT jint JNICALL +Java_com_alibaba_tenant_TenantGlobals_getTenantFlags(JNIEnv *env, jclass cls) +{ + jint rc = JNI_ERR; + TenantEnv* tenantEnv = getTenantEnv(env); + if(NULL != tenantEnv) { + rc = (*tenantEnv)->GetTenantFlags(tenantEnv, cls); + } else { + //throw exception + JNU_ThrowByName(env, "java/lang/InternalError", "Can not get tenant environment."); + } + return rc; +} diff --git a/test/lib/testlibrary/jdk/testlibrary/TestUtils.java b/test/lib/testlibrary/jdk/testlibrary/TestUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..9c1365c4915fd9a23111e3e7bd1c194aab792d72 --- /dev/null +++ b/test/lib/testlibrary/jdk/testlibrary/TestUtils.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package jdk.testlibrary; + +import java.lang.reflect.Method; +import java.util.Arrays; +import static jdk.testlibrary.Asserts.fail; + +public class TestUtils { + + /** + * Run all methods of {@code clazz} whose names start with {@code prefix} + * @param prefix + * @param clazz + * @param object + */ + public static void runWithPrefix(String prefix, Class clazz, Object object) { + if (prefix == null || clazz == null || object == null) { + throw new IllegalArgumentException("Bad arguments"); + } + + long totalTests = Arrays.stream(clazz.getDeclaredMethods()) + .filter(m -> m.getName().startsWith(prefix)) + .count(); + long passed = 0; + long failed = 0; + + for (Method method : clazz.getDeclaredMethods()) { + if (method.getName().startsWith(prefix)) { + method.setAccessible(true); + String name = clazz.getName() + "." + method.getName(); + println("=== Begin test " + name + " ==="); + try { + method.invoke(object); + ++passed; + println("=== PASSED (" + passed + " passed, " + failed +" failed, " + + totalTests + " total) ==="); + } catch (Throwable e) { + e.printStackTrace(); + ++failed; + println("=== FAILED ( " + passed + " passed, " + failed +" failed" + + totalTests + " total) ==="); + } + } + } + + if (failed != 0) { + fail("Total " + failed + "/" + totalTests + " testcases failed, class " + clazz.getName()); + } else { + println("All " + totalTests + " testcases passed, class " + clazz.getName()); + } + } + + private static void println(String msg) { + System.err.println(msg); + System.out.println(msg); + } +} diff --git a/test/multi-tenant/test/com/alibaba/tenant/TestTenantContainer.java b/test/multi-tenant/test/com/alibaba/tenant/TestTenantContainer.java new file mode 100644 index 0000000000000000000000000000000000000000..5952b58173139868bce4fb6f7f18f969cecb1a10 --- /dev/null +++ b/test/multi-tenant/test/com/alibaba/tenant/TestTenantContainer.java @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package test.com.alibaba.tenant; + +import com.alibaba.tenant.*; +import org.junit.Test; +import static org.junit.Assert.*; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.IntStream; + +/* @test + * @summary unit tests for com.alibaba.tenant.TenantContainer + * @library /lib/testlibrary + * @compile TestTenantContainer.java + * @run junit/othervm/timeout=300 -XX:+MultiTenant -XX:+UseG1GC -Xmx600m -Xms200m + * -Dcom.alibaba.tenant.test.prop=root test.com.alibaba.tenant.TestTenantContainer + */ +public class TestTenantContainer { + static private final int MAP_SIZE = 128; + static private final int MAP_ARRAY_LENGTH = 40; + + private Map populateMap(int size) { + Map map = new HashMap(); + for (int i = 0; i < size; i += 1) { + String valStr = "value is [" + i + "]"; + map.put(i, valStr); + } + return map; + } + + @Test + public void testRunInRootTenant() throws TenantException { + TenantConfiguration tconfig = new TenantConfiguration(); + final TenantContainer tenant = TenantContainer.create(tconfig); + tenant.run(() -> { + assertSame(TenantContainer.current(), tenant); + TenantContainer.primitiveRunInRoot(() -> { + //should be in root tenant. + assertNull(TenantContainer.current()); + }); + }); + } + + @Test + public void testTenantSystemProperty() { + String value = System.getProperty("com.alibaba.tenant.enableMultiTenant"); + assertTrue(value != null && "true".equalsIgnoreCase(value)); + } + + @Test + public void testCurrent() throws TenantException { + // should be in the root tenant at the begging + assertNull(TenantContainer.current()); + + TenantConfiguration tconfig = new TenantConfiguration(); + final TenantContainer tenant = TenantContainer.create(tconfig); + tenant.run(() -> { + // run in 'current' tenant + assertSame(TenantContainer.current(), tenant); + System.out.println("testCurrent: thread [" + Thread.currentThread().getName() + "] is running in tenant: " + + TenantContainer.current().getTenantId()); + }); + + // switch back to root tenant. + assertNull(TenantContainer.current()); + tenant.destroy(); + } + + @Test + public void testCurrentWithGC() throws TenantException { + // should be in the root tenant at the begging + assertNull(TenantContainer.current()); + + TenantConfiguration tconfig = new TenantConfiguration(); + final TenantContainer tenant = TenantContainer.create(tconfig); + tenant.run(() -> { + int arrayIndex = 0; + Object[] array = new Object[MAP_ARRAY_LENGTH]; + while (arrayIndex < MAP_ARRAY_LENGTH * 20) { + // run in 'current' tenant + assertSame(TenantContainer.current(), tenant); + Map map = populateMap(MAP_SIZE); + array[arrayIndex % MAP_ARRAY_LENGTH] = map; + arrayIndex++; + + Thread.yield(); + System.gc(); + } + + System.out.println("testCurrent: thread [" + Thread.currentThread().getName() + "] is running in tenant: " + + TenantContainer.current().getTenantId()); + + }); + + // switch back to root tenant. + assertNull(TenantContainer.current()); + tenant.destroy(); + } + + @Test + public void testGetTenantID() throws TenantException { + TenantConfiguration tconfig = new TenantConfiguration(); + final TenantContainer tenant = TenantContainer.create(tconfig); + tenant.run(() -> { + assertEquals(TenantContainer.current().getTenantId(), tenant.getTenantId()); + System.out.println("testGetTenantID: thread [" + Thread.currentThread().getName() + + "] is running in tenant: " + TenantContainer.current().getTenantId()); + }); + tenant.destroy(); + } + + @Test + public void testGetState() throws TenantException { + TenantConfiguration tconfig = new TenantConfiguration(); + final TenantContainer tenant = TenantContainer.create(tconfig); + assertTrue(TenantState.STARTING == tenant.getState()); + tenant.run(() -> { + assertTrue(TenantState.RUNNING == tenant.getState()); + System.out.println("testGetState: thread [" + Thread.currentThread().getName() + "] is running in tenant: " + + TenantContainer.current().getTenantId()); + }); + tenant.destroy(); + } + + @Test + public void testRun() throws TenantException { + TenantConfiguration tconfig = new TenantConfiguration(); + final TenantContainer tenant = TenantContainer.create(tconfig); + final TenantContainer tenant2 = TenantContainer.create(tconfig); + try { + tenant.run(() -> { + // it is not allowed to run into tenant 2 from tenant + try { + tenant2.run(() -> { + // should not reach here. + fail(); + }); + } catch (TenantException e) { + // Expected + } + // it is allowed to run into same tenant + try { + tenant.run(() -> { + assertEquals(TenantContainer.current().getTenantId(), tenant.getTenantId()); + System.out.println("testRun: thread [" + Thread.currentThread().getName() + + "] is running in tenant: " + TenantContainer.current().getTenantId()); + }); + } catch (TenantException e) { + fail(); + } + + }); + } catch (TenantException e) { + fail(); + } + tenant.destroy(); + tenant2.destroy(); + } + + @Test + public void testGetAttachedThreads() throws TenantException { + TenantConfiguration tconfig = new TenantConfiguration(); + final TenantContainer tenant = TenantContainer.create(tconfig); + tenant.run(() -> { + Thread[] threads = tenant.getAttachedThreads(); + assertEquals(threads.length, 1); + assertEquals(threads[0].getId(), Thread.currentThread().getId()); + System.out.println("testGetAttachedThreads: thread [" + Thread.currentThread().getName() + + "] is running in tenant: " + TenantContainer.current().getTenantId()); + }); + tenant.destroy(); + } + + @Test + public void testTenantInheritance() throws TenantException { + TenantConfiguration tconfig = new TenantConfiguration(); + final TenantContainer tenant = TenantContainer.create(tconfig); + tenant.run(() -> { + assertSame(TenantContainer.current(), tenant); + Thread thread = new Thread(() -> { + TenantContainer tc = TenantContainer.current(); + assertSame(tc, tenant); + Thread[] threads = tc.getAttachedThreads(); + assertEquals(threads.length, 2); + assertTrue(Thread.currentThread().getId() == threads[0].getId() || + Thread.currentThread().getId() == threads[1].getId()); + + System.out.println("testTenantInheritance: thread [" + Thread.currentThread().getName() + + "] is running in tenant: " + TenantContainer.current().getTenantId()); + }); + + thread.start(); + try { + thread.join(); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + Thread[] threads = tenant.getAttachedThreads(); + assertEquals(threads.length, 1); + assertEquals(threads[0].getId(), Thread.currentThread().getId()); + }); + tenant.destroy(); + } + + @Test + public void testTenantGetAllocatedMemory() throws TenantException, InterruptedException { + TenantConfiguration tconfig = new TenantConfiguration(); + final TenantContainer tenant = TenantContainer.create(tconfig); + + CountDownLatch p1 = new CountDownLatch(1); + CountDownLatch p2 = new CountDownLatch(1); + CountDownLatch p3 = new CountDownLatch(1); + CountDownLatch p4 = new CountDownLatch(1); + CountDownLatch startTest = new CountDownLatch(2); + CountDownLatch endTest = new CountDownLatch(1); + + long allocatedMem0 = 0; + long allocatedMem1 = 0; + long allocatedMem2 = 0; + long allocatedMem3 = 0; + + assertTrue(0 == tenant.getAllocatedMemory()); + tenant.run(() -> { + Thread thread1 = new Thread(() -> { + assertTrue(TenantContainer.current() == tenant); + System.out.println("thread1 started"); + try { + byte[] byteArray0 = new byte[10240]; + startTest.countDown(); + p1.await(); + byte[] byteArray1 = new byte[10240]; + endTest.await(); + } catch (InterruptedException ee) { + ee.printStackTrace(); + fail(); + } finally { + System.out.println("thread1 ended"); + } + }); + thread1.start(); + }); + + Thread thread2 = new Thread(() -> { + System.out.println("thread2 started"); + try { + startTest.countDown(); + + p2.await(); + + tenant.run(()->{ + byte[] byteArray0 = new byte[10240]; + }); + + p3.await(); + + byte[] byteArray1 = new byte[10240]; + + tenant.run(()->{ + byte[] byteArray0 = new byte[10240]; + }); + byte[] byteArray2 = new byte[10240]; + endTest.await(); + + } catch (InterruptedException | TenantException ee) { + ee.printStackTrace(); + fail(); + } finally { + System.out.println("thread2 ended"); + } + }); + thread2.start(); + + startTest.await(); + + allocatedMem0 = tenant.getAllocatedMemory(); + System.out.println("allocatedMem0 = " + allocatedMem0); + assertTrue(allocatedMem0 >= 10240); + + p1.countDown(); + Thread.sleep(1000); + allocatedMem1 = tenant.getAllocatedMemory(); + System.out.println("allocatedMem1 = " + allocatedMem1); + assertTrue((allocatedMem1 - allocatedMem0) >= 10240 && (allocatedMem1 - allocatedMem0) < 11240); + + p2.countDown(); + Thread.sleep(1000); + allocatedMem2 = tenant.getAllocatedMemory(); + System.out.println("allocatedMem2 = " + allocatedMem2); + assertTrue((allocatedMem2 - allocatedMem1) >= 10240 && (allocatedMem2 - allocatedMem1) < 11240); + + p3.countDown(); + Thread.sleep(1000); + allocatedMem3 = tenant.getAllocatedMemory(); + System.out.println("allocatedMem3 = " + allocatedMem3); + assertTrue((allocatedMem3 - allocatedMem2) >= 10240 && (allocatedMem3 - allocatedMem2) < 11240); + + p4.countDown(); + Thread.sleep(1000); + tenant.destroy(); + + endTest.countDown(); + System.out.println("testTenantGetAllocatedMemory passed!"); + } + + public static void main(String[] args) throws Exception { + TestTenantContainer test = new TestTenantContainer(); + test.testCurrent(); + test.testGetTenantID(); + test.testGetState(); + test.testGetAttachedThreads(); + test.testRun(); + test.testCurrentWithGC(); + test.testTenantInheritance(); + test.testRunInRootTenant(); + test.testTenantGetAllocatedMemory(); + } +} diff --git a/test/multi-tenant/test/com/alibaba/tenant/TestTenantContainerFactory.java b/test/multi-tenant/test/com/alibaba/tenant/TestTenantContainerFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..a9b86adcedf93c624d357ad989616100fd97e8d3 --- /dev/null +++ b/test/multi-tenant/test/com/alibaba/tenant/TestTenantContainerFactory.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +package com.alibaba.tenant; + +import com.alibaba.rcm.Constraint; +import com.alibaba.rcm.ResourceContainer; +import com.alibaba.rcm.ResourceContainerFactory; +import com.alibaba.rcm.internal.AbstractResourceContainer; +import java.util.Collections; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import static com.alibaba.rcm.ResourceType.*; +import static jdk.testlibrary.Asserts.*; + +/* + * @test + * @library /lib/testlibrary + * @summary test RCM API based TenantContainerFactory + * @run main/othervm/bootclasspath -XX:+MultiTenant com.alibaba.tenant.TestTenantContainerFactory + */ +public class TestTenantContainerFactory { + + private void testSingleton() { + ResourceContainerFactory factory = TenantContainerFactory.instance(); + assertTrue(factory == TenantContainerFactory.instance()); + ResourceContainerFactory factory2 = TenantContainerFactory.instance(); + assertSame(factory, factory2); + } + + private void testCreation() { + try { + ResourceContainer container = TenantContainerFactory.instance() + .createContainer(Collections.emptySet()); + assertNotNull(container); + assertTrue(container instanceof TenantResourceContainer); + assertNull(TenantContainer.current()); + assertNotNull(TenantResourceContainer.root()); + } catch (Throwable t) { + fail(); + } + } + + private void testCreationWithHeapLimit() { + try { + Iterable constraints = Stream.of(HEAP_RETAINED.newConstraint(32 * 1024 * 1024)) + .collect(Collectors.toSet()); + ResourceContainer container = TenantContainerFactory.instance() + .createContainer(constraints); + assertNotNull(container); + assertTrue(container instanceof TenantResourceContainer); + assertNull(TenantContainer.current()); + assertNotNull(TenantResourceContainer.root()); + } catch (Throwable t) { + fail(); + } + } + + private void testDestroy() { + ResourceContainer container = TenantContainerFactory.instance() + .createContainer(null); + try { + container.destroy(); + fail("Should throw UnsupportedException"); + } catch (UnsupportedOperationException e) { + // expected + } + try { + TenantContainer tenant = TenantContainerFactory.tenantContainerOf(container); + tenant.destroy(); + } catch (Throwable t) { + fail(); + } + } + + private void testImplicitTenantContainer() { + try { + Iterable constraints = Stream.of(CPU_PERCENT.newConstraint(10)) + .collect(Collectors.toList()); + ResourceContainer rc = TenantContainerFactory.instance().createContainer(constraints); + TenantContainer tenant = TenantContainerFactory.tenantContainerOf(rc); + assertNotNull(tenant); + assertNull(TenantContainer.current()); + assertSame(AbstractResourceContainer.root(), AbstractResourceContainer.current()); + tenant.run(()->{ + System.out.println("Hello"); + assertSame(TenantContainer.current(), tenant); + assertSame(AbstractResourceContainer.current(), rc); + }); + tenant.destroy(); + } catch (Throwable e) { + fail(); + } + } + + public static void main(String[] args) { + TestTenantContainerFactory test = new TestTenantContainerFactory(); + test.testSingleton(); + test.testCreation(); + test.testDestroy(); + test.testImplicitTenantContainer(); + if (TenantGlobals.isHeapThrottlingEnabled()) { + test.testCreationWithHeapLimit(); + } + } +} diff --git a/test/multi-tenant/test/com/alibaba/tenant/TestTenantGlobals.java b/test/multi-tenant/test/com/alibaba/tenant/TestTenantGlobals.java new file mode 100644 index 0000000000000000000000000000000000000000..912e6006fab550daa9366a1afec169b92c47625e --- /dev/null +++ b/test/multi-tenant/test/com/alibaba/tenant/TestTenantGlobals.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package test.com.alibaba.tenant; + +import org.junit.Test; +import com.alibaba.tenant.TenantConfiguration; +import com.alibaba.tenant.TenantContainer; +import com.alibaba.tenant.TenantGlobals; +import static org.junit.Assert.*; + +/* @test + * @summary unit tests for com.alibaba.tenant.TenantGlobals + * @library /lib/testlibrary + * @compile TestTenantGlobals.java + * @run junit/othervm test.com.alibaba.tenant.TestTenantGlobals + */ +public class TestTenantGlobals { + + @Test + public void testIfTenantIsDisabled() { + assertFalse(TenantGlobals.isTenantEnabled()); + String value = System.getProperty("com.alibaba.tenant.enableMultiTenant"); + boolean bTenantIsEnabled = false; + if(value != null && "true".equalsIgnoreCase(value)) { + bTenantIsEnabled = true; + } + assertFalse(bTenantIsEnabled); + TenantConfiguration tconfig = new TenantConfiguration(); + try { + TenantContainer.create(tconfig); + fail(); // should not reach here. + } catch (UnsupportedOperationException exception) { + // Expected + System.out.println("Can not create tenant without -XX:+MultiTenant enabled."); + exception.printStackTrace(); + } + try { + TenantContainer.create(tconfig); + fail(); // should not reach here. + } catch (UnsupportedOperationException exception) { + // Expected + System.out.println("Can not create tenant without -XX:+MultiTenant enabled."); + exception.printStackTrace(); + } + try { + TenantContainer.current(); + fail(); // should not reach here. + } catch (UnsupportedOperationException exception) { + // Expected + System.out.println("Can not get current tenant without -XX:+MultiTenant enabled."); + exception.printStackTrace(); + } + } + + public static void main(String[] args) { + TestTenantGlobals test = new TestTenantGlobals(); + test.testIfTenantIsDisabled(); + } + +}