Skip to content

Commit

Permalink
caching warlibs class loader across deployments, speeding them up eve…
Browse files Browse the repository at this point in the history
…n more
  • Loading branch information
lprimak committed Dec 6, 2024
1 parent b981b61 commit 4272f61
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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<DelegatingClassLoader> globalConnectorCL = new AtomicReference<>();
private final AtomicReference<DelegatingClassLoader> globalConnectorWithWarLibCL = new AtomicReference<>();

@Inject
private AppSpecificConnectorClassLoaderUtil appsSpecificCCLUtil;

@Inject
private Provider<ClassLoaderHierarchy> classLoaderHierarchyProvider;
private Provider<ConnectorsClassLoaderUtil> connectorsClassLoaderUtil;

private Logger logger = LogDomains.getLogger(ConnectorClassLoaderServiceImpl.class, LogDomains.RSR_LOGGER);

Expand All @@ -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<DelegatingClassLoader>() {
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);
Expand All @@ -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<DelegatingClassLoader> updateOperator = currentValue -> {
if (currentValue == null) {
//[parent is assumed to be common-class-loader in ConnectorClassLoaderUtil.createRARClassLoader() also]
var newValue = AccessController.doPrivileged(new PrivilegedAction<DelegatingClassLoader>() {
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()){
Expand Down Expand Up @@ -178,7 +186,7 @@ private void addRarClassLoader(String appName, DelegatingClassLoader appSpecific
}

private DelegatingClassLoader.ClassFinder getClassFinder(String raName) {
List<DelegatingClassLoader.ClassFinder> delegates = globalConnectorCL.getDelegates();
List<DelegatingClassLoader.ClassFinder> delegates = getGlobalConnectorClassLoader().getDelegates();
DelegatingClassLoader.ClassFinder classFinder = null;
for(DelegatingClassLoader.ClassFinder cf : delegates){
if(raName.equals(((ConnectorClassFinder)cf).getResourceAdapterName())){
Expand All @@ -188,5 +196,4 @@ private DelegatingClassLoader.ClassFinder getClassFinder(String raName) {
}
return classFinder;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,33 @@
* 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;

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;
Expand All @@ -81,7 +89,8 @@ public class ConnectorsClassLoaderUtil {
@Inject
private ClassLoaderHierarchy clh;

//private static List<ConnectorClassFinder> systemRARClassLoaders;
// warLibClassLoader is used to load libraries from the war file
private final AtomicReference<CurrentBeforeParentClassLoader> warLibClassLoader = new AtomicReference<>();

private Logger _logger = LogDomains.getLogger(ConnectorRuntime.class, LogDomains.RSR_LOGGER);

Expand Down Expand Up @@ -209,7 +218,7 @@ public Collection<ConnectorClassFinder> 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;
Expand All @@ -218,6 +227,30 @@ public Collection<ConnectorClassFinder> 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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down

0 comments on commit 4272f61

Please sign in to comment.