From 4272f61ff4ff70b54b2be7d97a8412f9c00e564e Mon Sep 17 00:00:00 2001 From: lprimak Date: Thu, 5 Dec 2024 18:03:25 -0600 Subject: [PATCH] caching warlibs class loader across deployments, speeding them up even more --- .../api/ConnectorClassLoaderServiceImpl.java | 79 ++++++++++--------- .../api/ConnectorsClassLoaderUtil.java | 37 ++++++++- .../web/loader/WebappClassLoader.java | 2 +- .../org/glassfish/weld/DeploymentImpl.java | 7 +- .../enterprise/loader/ASURLClassLoader.java | 2 +- .../sun/enterprise/loader/CacheCleaner.java | 40 ++++++++-- .../loader/CachingReflectionUtil.java | 3 + .../common/DeploymentContextImpl.java | 13 +-- 8 files changed, 124 insertions(+), 59 deletions(-) diff --git a/appserver/connectors/connectors-internal-api/src/main/java/com/sun/appserv/connectors/internal/api/ConnectorClassLoaderServiceImpl.java b/appserver/connectors/connectors-internal-api/src/main/java/com/sun/appserv/connectors/internal/api/ConnectorClassLoaderServiceImpl.java index 9839e947c26..c3a527b1da3 100644 --- a/appserver/connectors/connectors-internal-api/src/main/java/com/sun/appserv/connectors/internal/api/ConnectorClassLoaderServiceImpl.java +++ b/appserver/connectors/connectors-internal-api/src/main/java/com/sun/appserv/connectors/internal/api/ConnectorClassLoaderServiceImpl.java @@ -37,22 +37,27 @@ * only if the new code is made subject to such option by the copyright * holder. */ +// Portions Copyright [2024] [Payara Foundation and/or its affiliates] package com.sun.appserv.connectors.internal.api; +import jakarta.inject.Provider; +import org.glassfish.deployment.common.DeploymentUtils; import org.glassfish.internal.api.*; +import org.glassfish.internal.deployment.Deployment; import org.jvnet.hk2.annotations.Service; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.UnaryOperator; import java.util.logging.Logger; import java.util.logging.Level; import com.sun.logging.LogDomains; import jakarta.inject.Inject; -import jakarta.inject.Provider; /** * We support two policies: @@ -72,13 +77,13 @@ public class ConnectorClassLoaderServiceImpl implements ConnectorClassLoaderServ * class loader for all applications. In other words, we make every * standalone RARs available to all applications. */ - private volatile DelegatingClassLoader globalConnectorCL; + private final AtomicReference globalConnectorCL = new AtomicReference<>(); + private final AtomicReference globalConnectorWithWarLibCL = new AtomicReference<>(); @Inject private AppSpecificConnectorClassLoaderUtil appsSpecificCCLUtil; - @Inject - private Provider classLoaderHierarchyProvider; + private Provider connectorsClassLoaderUtil; private Logger logger = LogDomains.getLogger(ConnectorClassLoaderServiceImpl.class, LogDomains.RSR_LOGGER); @@ -92,34 +97,8 @@ public class ConnectorClassLoaderServiceImpl implements ConnectorClassLoaderServ public DelegatingClassLoader getConnectorClassLoader(String appName) { DelegatingClassLoader loader = null; - // We do not have dependency on common-class-loader explicitly - // and also cannot initialize globalConnectorCL during postConstruct via ClassLoaderHierarchy - // which will result in circular dependency injection between kernel and connector module - // Hence initializing globalConnectorCL lazily - if (globalConnectorCL == null) { - synchronized (ConnectorClassLoaderServiceImpl.class) { - if (globalConnectorCL == null) { - //[parent is assumed to be common-class-loader in ConnectorClassLoaderUtil.createRARClassLoader() also] - final ClassLoader parent = getCommonClassLoader(); - globalConnectorCL = AccessController.doPrivileged(new PrivilegedAction() { - public DelegatingClassLoader run() { - DelegatingClassLoader dcl = new DelegatingClassLoader(parent); - for (DelegatingClassLoader.ClassFinder cf : appsSpecificCCLUtil.getSystemRARClassLoaders()) { - dcl.addDelegate(cf); - } - return dcl; - } - }); - - for (DelegatingClassLoader.ClassFinder cf : appsSpecificCCLUtil.getSystemRARClassLoaders()) { - globalConnectorCL.addDelegate(cf); - } - } - } - } if (hasGlobalAccessForRARs(appName)) { - assert (globalConnectorCL != null); - loader = globalConnectorCL; + loader = getGlobalConnectorClassLoader(); } else { appsSpecificCCLUtil.detectReferredRARs(appName); loader = createConnectorClassLoaderForApplication(appName); @@ -133,14 +112,43 @@ private boolean hasGlobalAccessForRARs(String appName) { (ConnectorConstants.RAR_VISIBILITY_GLOBAL_ACCESS); } - private ClassLoader getCommonClassLoader(){ - return classLoaderHierarchyProvider.get().getCommonClassLoader(); + private DelegatingClassLoader getGlobalConnectorClassLoader() { + // We do not have dependency on common-class-loader explicitly + // and also cannot initialize globalConnectorCL during postConstruct via ClassLoaderHierarchy + // which will result in circular dependency injection between kernel and connector module + // Hence initializing globalConnectorCL lazily + UnaryOperator updateOperator = currentValue -> { + if (currentValue == null) { + //[parent is assumed to be common-class-loader in ConnectorClassLoaderUtil.createRARClassLoader() also] + var newValue = AccessController.doPrivileged(new PrivilegedAction() { + public DelegatingClassLoader run() { + DelegatingClassLoader dcl = new DelegatingClassLoader(connectorsClassLoaderUtil.get().getCommonClassLoader()); + for (DelegatingClassLoader.ClassFinder cf : appsSpecificCCLUtil.getSystemRARClassLoaders()) { + dcl.addDelegate(cf); + } + return dcl; + } + }); + + for (DelegatingClassLoader.ClassFinder cf : appsSpecificCCLUtil.getSystemRARClassLoaders()) { + newValue.addDelegate(cf); + } + return newValue; + } + return currentValue; + }; + if (DeploymentUtils.useWarLibraries(Globals.getDefaultHabitat().getService(Deployment.class) + .getCurrentDeploymentContext())) { + return globalConnectorWithWarLibCL.updateAndGet(updateOperator); + } else { + return globalConnectorCL.updateAndGet(updateOperator); + } } private DelegatingClassLoader createConnectorClassLoaderForApplication(String appName){ DelegatingClassLoader appSpecificConnectorClassLoader = - new DelegatingClassLoader(getCommonClassLoader()); + new DelegatingClassLoader(connectorsClassLoaderUtil.get().getCommonClassLoader()); //add system ra classloaders for(DelegatingClassLoader.ClassFinder cf : appsSpecificCCLUtil.getSystemRARClassLoaders()){ @@ -178,7 +186,7 @@ private void addRarClassLoader(String appName, DelegatingClassLoader appSpecific } private DelegatingClassLoader.ClassFinder getClassFinder(String raName) { - List delegates = globalConnectorCL.getDelegates(); + List delegates = getGlobalConnectorClassLoader().getDelegates(); DelegatingClassLoader.ClassFinder classFinder = null; for(DelegatingClassLoader.ClassFinder cf : delegates){ if(raName.equals(((ConnectorClassFinder)cf).getResourceAdapterName())){ @@ -188,5 +196,4 @@ private DelegatingClassLoader.ClassFinder getClassFinder(String raName) { } return classFinder; } - } diff --git a/appserver/connectors/connectors-internal-api/src/main/java/com/sun/appserv/connectors/internal/api/ConnectorsClassLoaderUtil.java b/appserver/connectors/connectors-internal-api/src/main/java/com/sun/appserv/connectors/internal/api/ConnectorsClassLoaderUtil.java index 015067aa5aa..aacd7a4a5de 100644 --- a/appserver/connectors/connectors-internal-api/src/main/java/com/sun/appserv/connectors/internal/api/ConnectorsClassLoaderUtil.java +++ b/appserver/connectors/connectors-internal-api/src/main/java/com/sun/appserv/connectors/internal/api/ConnectorsClassLoaderUtil.java @@ -37,15 +37,21 @@ * only if the new code is made subject to such option by the copyright * holder. */ +// Portions Copyright [2024] [Payara Foundation and/or its affiliates] package com.sun.appserv.connectors.internal.api; +import com.sun.enterprise.loader.CurrentBeforeParentClassLoader; +import org.glassfish.deployment.common.DeploymentUtils; +import org.glassfish.deployment.common.InstalledLibrariesResolver; import org.glassfish.internal.api.ClassLoaderHierarchy; import org.glassfish.internal.api.DelegatingClassLoader; import org.glassfish.api.admin.*; import org.glassfish.api.event.Events; import org.glassfish.api.event.EventListener; import org.glassfish.api.event.EventTypes; +import org.glassfish.internal.api.Globals; +import org.glassfish.internal.deployment.Deployment; import org.jvnet.hk2.annotations.Service; import jakarta.inject.Singleton; @@ -53,9 +59,11 @@ import java.io.*; import java.net.MalformedURLException; import java.net.URI; +import java.net.URL; import java.security.AccessController; import java.security.PrivilegedExceptionAction; import java.security.PrivilegedActionException; +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; import java.util.Collection; @@ -81,7 +89,8 @@ public class ConnectorsClassLoaderUtil { @Inject private ClassLoaderHierarchy clh; - //private static List systemRARClassLoaders; + // warLibClassLoader is used to load libraries from the war file + private final AtomicReference warLibClassLoader = new AtomicReference<>(); private Logger _logger = LogDomains.getLogger(ConnectorRuntime.class, LogDomains.RSR_LOGGER); @@ -209,7 +218,7 @@ public Collection getSystemRARClassLoaders() throws Connec libraries = ConnectorsUtil.getInstalledLibrariesFromManifest(location, env); } - ConnectorClassFinder ccf = createRARClassLoader(location, null, rarName, libraries); + ConnectorClassFinder ccf = createRARClassLoader(location, getCommonClassLoader(), rarName, libraries); classLoaders.add(ccf); } // systemRARClassLoaders = classLoaders; @@ -218,6 +227,30 @@ public Collection getSystemRARClassLoaders() throws Connec return classLoaders; } + public ClassLoader getCommonClassLoader() { + if (DeploymentUtils.useWarLibraries(Globals.getDefaultHabitat().getService(Deployment.class) + .getCurrentDeploymentContext())) { + return warLibClassLoader.updateAndGet(currentValue -> { + if (currentValue == null) { + var newValue = new CurrentBeforeParentClassLoader(InstalledLibrariesResolver.getWarLibraries() + .stream().map(uri -> { + try { + return uri.toUri().toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + }) + .toArray(URL[]::new), clh.getCommonClassLoader()); + newValue.enableCurrentBeforeParentUnconditional(); + return newValue; + } + return currentValue; + }); + } else { + return clh.getCommonClassLoader(); + } + } + public ConnectorClassFinder getSystemRARClassLoader(String rarName) throws ConnectorRuntimeException { if (ConnectorsUtil.belongsToSystemRA(rarName)) { diff --git a/appserver/web/war-util/src/main/java/org/glassfish/web/loader/WebappClassLoader.java b/appserver/web/war-util/src/main/java/org/glassfish/web/loader/WebappClassLoader.java index 72e3e16851c..2d50b915609 100644 --- a/appserver/web/war-util/src/main/java/org/glassfish/web/loader/WebappClassLoader.java +++ b/appserver/web/war-util/src/main/java/org/glassfish/web/loader/WebappClassLoader.java @@ -2052,7 +2052,7 @@ public void stop() throws Exception { // START SJSAS 6258619 ClassLoaderUtil.releaseLoader(this); // END SJSAS 6258619 - CacheCleaner.clearJaxRSCache(this); + CacheCleaner.clearCaches(this); synchronized(jarFilesLock) { started = false; diff --git a/appserver/web/weld-integration/src/main/java/org/glassfish/weld/DeploymentImpl.java b/appserver/web/weld-integration/src/main/java/org/glassfish/weld/DeploymentImpl.java index 67272f6ee7f..0758f03350f 100644 --- a/appserver/web/weld-integration/src/main/java/org/glassfish/weld/DeploymentImpl.java +++ b/appserver/web/weld-integration/src/main/java/org/glassfish/weld/DeploymentImpl.java @@ -71,6 +71,7 @@ import org.glassfish.cdi.CDILoggerInfo; import org.glassfish.common.util.ObjectInputStreamWithLoader; import org.glassfish.deployment.common.DeploymentContextImpl; +import org.glassfish.deployment.common.DeploymentUtils; import org.glassfish.deployment.common.InstalledLibrariesResolver; import org.glassfish.hk2.classmodel.reflect.Types; import org.glassfish.internal.data.ApplicationInfo; @@ -801,8 +802,10 @@ private void processBdasForAppLibs( ReadableArchive archive, DeploymentContext c } } - for(Path warLibrary : InstalledLibrariesResolver.getWarLibraries()) { - addLibBDA(archive, context, warLibrary.toUri(), libBdas); + if (DeploymentUtils.useWarLibraries(context)) { + for (Path warLibrary : InstalledLibrariesResolver.getWarLibraries()) { + addLibBDA(archive, context, warLibrary.toUri(), libBdas); + } } } catch (URISyntaxException | IOException e) { //todo: log error diff --git a/nucleus/common/common-util/src/main/java/com/sun/enterprise/loader/ASURLClassLoader.java b/nucleus/common/common-util/src/main/java/com/sun/enterprise/loader/ASURLClassLoader.java index 043eba8e07a..0fe46276584 100644 --- a/nucleus/common/common-util/src/main/java/com/sun/enterprise/loader/ASURLClassLoader.java +++ b/nucleus/common/common-util/src/main/java/com/sun/enterprise/loader/ASURLClassLoader.java @@ -208,7 +208,7 @@ public void done() { // because we've taken the snapshot. doneCalled = true; - CacheCleaner.clearJaxRSCache(this); + CacheCleaner.clearCaches(this); // closes the jar handles and sets the url entries to null for (URLEntry u : this.urlSet) { diff --git a/nucleus/common/common-util/src/main/java/com/sun/enterprise/loader/CacheCleaner.java b/nucleus/common/common-util/src/main/java/com/sun/enterprise/loader/CacheCleaner.java index 6949d8cf465..5360afee697 100644 --- a/nucleus/common/common-util/src/main/java/com/sun/enterprise/loader/CacheCleaner.java +++ b/nucleus/common/common-util/src/main/java/com/sun/enterprise/loader/CacheCleaner.java @@ -47,14 +47,16 @@ public class CacheCleaner { private static final Logger logger = CULoggerInfo.getLogger(); - public static void clearJaxRSCache(ClassLoader classLoader) { - doClearJaxRSCache(classLoader); - if (classLoader.getParent() != null) { - doClearJaxRSCache(classLoader.getParent()); + public static void clearCaches(ClassLoader classLoader) { + clearOmniFacesCache(classLoader); + clearJNACache(classLoader); + while (classLoader != null) { + clearJaxRSCache(classLoader); + classLoader = classLoader.getParent(); } } - private static void doClearJaxRSCache(ClassLoader classLoader) { + private static void clearJaxRSCache(ClassLoader classLoader) { try { Class cdiComponentProvider = CachingReflectionUtil .getClassFromCache("org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider", classLoader); @@ -70,4 +72,32 @@ private static void doClearJaxRSCache(ClassLoader classLoader) { logger.log(Level.WARNING, "Error clearing Jax-Rs cache", e); } } + + private static void clearOmniFacesCache(ClassLoader classLoader) { + try { + Class eagerBeans = CachingReflectionUtil + .getClassFromCache("org.omnifaces.cdi.eager.EagerBeansRepository", classLoader); + if (eagerBeans != null && eagerBeans.getClassLoader() instanceof CurrentBeforeParentClassLoader) { + Field instance = CachingReflectionUtil.getFieldFromCache(eagerBeans, "instance", true); + instance.set(null, null); + } + } catch (Exception e) { + logger.log(Level.WARNING, "Error clearing OmniFaces cache", e); + } + } + + private static void clearJNACache(ClassLoader classLoader) { + try { + Class cleanerClass = CachingReflectionUtil + .getClassFromCache("com.sun.jna.internal.Cleaner", classLoader); + if (cleanerClass != null && cleanerClass.getClassLoader() instanceof CurrentBeforeParentClassLoader) { + Field instanceField = CachingReflectionUtil.getFieldFromCache(cleanerClass, "INSTANCE", true); + Object instance = instanceField.get(null); + CachingReflectionUtil.getFieldFromCache(instance.getClass(), "cleanerThread", true) + .set(instance, null); + } + } catch (Exception e) { + logger.log(Level.WARNING, "Error clearing JNA cache", e); + } + } } diff --git a/nucleus/common/common-util/src/main/java/com/sun/enterprise/loader/CachingReflectionUtil.java b/nucleus/common/common-util/src/main/java/com/sun/enterprise/loader/CachingReflectionUtil.java index 71eac944562..4d38a905345 100644 --- a/nucleus/common/common-util/src/main/java/com/sun/enterprise/loader/CachingReflectionUtil.java +++ b/nucleus/common/common-util/src/main/java/com/sun/enterprise/loader/CachingReflectionUtil.java @@ -64,6 +64,9 @@ public static Class getClassFromCache(String className, ClassLoader classLoad return null; } }); + if (cls != null && cls.getClassLoader() == classLoader) { + classCache.remove(cls.getName()); + } return cls; } diff --git a/nucleus/deployment/common/src/main/java/org/glassfish/deployment/common/DeploymentContextImpl.java b/nucleus/deployment/common/src/main/java/org/glassfish/deployment/common/DeploymentContextImpl.java index d3552986c29..235ecdcc119 100644 --- a/nucleus/deployment/common/src/main/java/org/glassfish/deployment/common/DeploymentContextImpl.java +++ b/nucleus/deployment/common/src/main/java/org/glassfish/deployment/common/DeploymentContextImpl.java @@ -42,7 +42,6 @@ package org.glassfish.deployment.common; import com.sun.enterprise.config.serverbeans.ServerTags; -import com.sun.enterprise.loader.CurrentBeforeParentClassLoader; import org.glassfish.deployment.versioning.VersioningUtils; import java.lang.instrument.ClassFileTransformer; import org.glassfish.api.ActionReport; @@ -270,17 +269,7 @@ private ClassLoader createClassLoader(ClassLoaderHierarchy clh, ArchiveHandler h // first we create the appLib class loader, this is non shared libraries class loader ClassLoader applibCL = clh.getAppLibClassLoader(appName, getAppLibs()); - var warLibrariesCL = new CurrentBeforeParentClassLoader(InstalledLibrariesResolver.getWarLibraries() - .stream().map(uri -> { - try { - return uri.toUri().toURL(); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - }) - .toArray(URL[]::new), applibCL); - warLibrariesCL.enableCurrentBeforeParentUnconditional(); - ClassLoader parentCL = clh.createApplicationParentCL(warLibrariesCL, this); + ClassLoader parentCL = clh.createApplicationParentCL(applibCL, this); return handler.getClassLoader(parentCL, this); }