Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PAYARA-2848 catch ConcurrentModificationException #2840

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -41,6 +41,7 @@

package org.glassfish.weld;

import com.sun.enterprise.admin.util.JarFileUtils;
import com.sun.enterprise.deployment.Application;
import com.sun.enterprise.deployment.util.DOLUtils;
import org.glassfish.api.deployment.DeploymentContext;
Expand Down Expand Up @@ -761,30 +762,10 @@ private boolean isCDIAnnotatedClass(String className) {
protected BeansXml parseBeansXML(ReadableArchive archive, String beansXMLPath) throws IOException {
URL url = getBeansXMLFileURL(archive, beansXMLPath);
BeansXml result = weldBootstrap.parse(url);
try {
// Ensure JarFile is closed
Class clazz = Class.forName("sun.net.www.protocol.jar.JarFileFactory", true, URL.class.getClassLoader());
Field fields[] = clazz.getDeclaredFields();
for (Field field : fields) {
if ("fileCache".equals(field.getName())) {
field.setAccessible(true);
Map<String, JarFile> files = (Map<String, JarFile>) field.get(null);
if (!files.isEmpty()) {
for (JarFile file : files.values()) {
if (file != null) {
file.close();
}
}
}
}
}
} catch (ClassNotFoundException | IllegalAccessException | SecurityException | IllegalArgumentException | IOException | ConcurrentModificationException ex) {
logger.log(Level.SEVERE, null, ex);
}
JarFileUtils.closeCachedJarFiles();
return result;
}


private void addBeansXMLURL(ReadableArchive archive, String beansXMLPath) throws IOException {
URL beansXmlUrl = getBeansXMLFileURL(archive, beansXMLPath);
if (!beansXmlURLs.contains(beansXmlUrl)) {
Expand Down
@@ -0,0 +1,63 @@
package com.sun.enterprise.admin.util;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file needs the Payara copyright header

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I'll fix this. I wasn't sure about the package though.


import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Utilities class for JarFiles
*
* @author Sven Diedrichsen
* @since 13.06.18
*/
public class JarFileUtils {

private static final Logger LOG = Logger.getLogger(JarFileUtils.class.getName());

/**
* Ensures that all cached JarFile instances are closed.
*/
public static void closeCachedJarFiles() {
try {
Map<String, JarFile> files = null;
Object jarFileFactoryInstance = null;
Class clazz = Class.forName("sun.net.www.protocol.jar.JarFileFactory", true, URL.class.getClassLoader());
Field fields[] = clazz.getDeclaredFields();
for (Field field : fields) {
if ("fileCache".equals(field.getName())) {
field.setAccessible(true);
files = (Map<String, JarFile>) field.get(null);
}
if ("instance".equals(field.getName())) {
field.setAccessible(true);
jarFileFactoryInstance = field.get(null);
}
}
if (files != null && !files.isEmpty()) {
Set<JarFile> jarFiles = new HashSet<>();
if(jarFileFactoryInstance != null) {
synchronized (jarFileFactoryInstance) {
jarFiles.addAll(files.values());
}
} else {
jarFiles.addAll(files.values());
}
for (JarFile file : jarFiles) {
if (file != null) {
file.close();
}
}
}
} catch (ClassNotFoundException | IllegalAccessException | SecurityException | IllegalArgumentException | IOException | ConcurrentModificationException ex) {
LOG.log(Level.SEVERE, null, ex);
}
}

}
Expand Up @@ -41,6 +41,7 @@

package org.glassfish.deployment.admin;

import com.sun.enterprise.admin.util.JarFileUtils;
import com.sun.enterprise.config.serverbeans.*;
import com.sun.enterprise.util.LocalStringManagerImpl;
import org.glassfish.internal.data.ApplicationInfo;
Expand Down Expand Up @@ -115,11 +116,11 @@
@RestEndpoints({
@RestEndpoint(configBean = Applications.class, opType = RestEndpoint.OpType.DELETE, path = "undeploy", description = "Undeploy an application")
})
public class UndeployCommand extends UndeployCommandParameters implements AdminCommand, DeploymentTargetResolver,
public class UndeployCommand extends UndeployCommandParameters implements AdminCommand, DeploymentTargetResolver,
AdminCommandSecurity.Preauthorization, AdminCommandSecurity.AccessCheckProvider {

final private static LocalStringManagerImpl localStrings = new LocalStringManagerImpl(UndeployCommand.class);

@Inject
Deployment deployment;

Expand Down Expand Up @@ -149,7 +150,7 @@ public class UndeployCommand extends UndeployCommandParameters implements AdminC

@Inject
Events events;

private ActionReport report;
private Logger logger;
private List<String> matchedVersions;
Expand All @@ -166,7 +167,7 @@ public boolean preAuthorization(AdminCommandContext context) {

deployment.validateSpecifiedTarget(target);

// we need to set the default target for non-paas case first
// we need to set the default target for non-paas case first
// so the versioned code could execute as expected
if (target == null) {
target = deployment.getDefaultTarget(_classicstyle);
Expand All @@ -189,13 +190,13 @@ public boolean preAuthorization(AdminCommandContext context) {
// retrieve matched version(s) if exist
matchedVersions = null;
try {
matchedVersions = versioningService.getMatchedVersions(name,
matchedVersions = versioningService.getMatchedVersions(name,
target);
} catch (VersioningException e) {
if (env.isDas()) {
report.failure(logger, e.getMessage());
} else {
// we should let undeployment go through
// we should let undeployment go through
// on instance side for partial deployment case
logger.fine(e.getMessage());
}
Expand All @@ -209,18 +210,18 @@ public boolean preAuthorization(AdminCommandContext context) {
report.setMessage(localStrings.getLocalString("ref.not.referenced.target","Application {0} is not referenced by target {1}", name, target));
report.setActionExitCode(ActionReport.ExitCode.FAILURE);
} else {
// we should let undeployment go through
// we should let undeployment go through
// on instance side for partial deployment case
logger.fine(localStrings.getLocalString("ref.not.referenced.target","Application {0} is not referenced by target {1}", name, target));
}
return false;
}

for (String appName : matchedVersions) {
if (target == null) {
target = deployment.getDefaultTarget(appName, origin, _classicstyle);
}

Application application = apps.getModule(Application.class, appName);

if (application==null) {
Expand All @@ -232,7 +233,7 @@ public boolean preAuthorization(AdminCommandContext context) {
return true;
}


@Override
public Collection<? extends AccessRequired.AccessCheck> getAccessChecks() {
return DeploymentCommandUtils.getAccessChecksForExistingApp(
Expand All @@ -241,15 +242,15 @@ public Collection<? extends AccessRequired.AccessCheck> getAccessChecks() {

@Override
public void execute(AdminCommandContext context) {



// for each matched version


// for each matched version
for (String appName : matchedVersions) {
if (target == null) {
target = deployment.getDefaultTarget(appName, origin, _classicstyle);
}

ApplicationInfo info = deployment.get(appName);

Application application = apps.getModule(Application.class, appName);
Expand Down Expand Up @@ -307,7 +308,7 @@ public void execute(AdminCommandContext context) {
} catch(TransactionFailure e) {
logger.warning("Module " + appName + " not found in configuration");
}
// also remove application from runtime registry
// also remove application from runtime registry
if (info != null) {
appRegistry.remove(appName);
}
Expand All @@ -318,7 +319,7 @@ public void execute(AdminCommandContext context) {
if (!source.exists()) {
logger.log(Level.WARNING, "Cannot find application bits at " +
sourceFile.getPath() + ". Please restart server to ensure server is in a consistent state before redeploy the application.");
// remove the application from the domain.xml so at least
// remove the application from the domain.xml so at least
// server is in a consistent state after restart
try {
deployment.unregisterAppFromDomainXML(appName, target);
Expand Down Expand Up @@ -350,7 +351,7 @@ public void execute(AdminCommandContext context) {
final DeployCommandSupplementalInfo suppInfo = new DeployCommandSupplementalInfo();
suppInfo.setDeploymentContext(deploymentContext);
report.setResultType(DeployCommandSupplementalInfo.class, suppInfo);

final Properties appProps = deploymentContext.getAppProps();
appProps.putAll(application.getDeployProperties());

Expand All @@ -360,7 +361,7 @@ public void execute(AdminCommandContext context) {

deploymentContext.setModulePropsMap(
application.getModulePropertiesMap());

events.send(new Event<DeploymentContext>(Deployment.UNDEPLOYMENT_VALIDATION, deploymentContext), false);

if (report.getActionExitCode()==ActionReport.ExitCode.FAILURE) {
Expand Down Expand Up @@ -389,19 +390,19 @@ public void execute(AdminCommandContext context) {
return;
}

if (DeploymentUtils.isDomainTarget(target)) {
if (DeploymentUtils.isDomainTarget(target)) {
List<String> targets = domain.getAllReferencedTargetsForApplication(appName);
// replicate command to all referenced targets
parameters.remove("isUndeploy");
notifier.ensureBeforeReported(ExtendedDeploymentContext.Phase.REPLICATION);
ClusterOperationUtil.replicateCommand("undeploy", FailurePolicy.Error, FailurePolicy.Warn,
ClusterOperationUtil.replicateCommand("undeploy", FailurePolicy.Error, FailurePolicy.Warn,
FailurePolicy.Ignore, targets, context, parameters, habitat);
}
} catch (Exception e) {
report.failure(logger, e.getMessage());
return;
}
}
}

/*
* Extract the generated artifacts from the application's properties
Expand All @@ -410,13 +411,13 @@ public void execute(AdminCommandContext context) {
*/
final Artifacts generatedArtifacts = DeploymentUtils.generatedArtifacts(application);
generatedArtifacts.record(deploymentContext);

if (info!=null) {
deployment.undeploy(appName, deploymentContext);
}

// check if it's directory deployment
boolean isDirectoryDeployed =
boolean isDirectoryDeployed =
Boolean.valueOf(application.getDirectoryDeployed());

// we should try to unregister the application for both success
Expand All @@ -432,34 +433,15 @@ public void execute(AdminCommandContext context) {

//remove context from generated
deploymentContext.clean();
// Ensure All cached JarFiles are closed after undeployment to


// Ensure All cached JarFiles are closed after undeployment to
// free file descriptors
try {

Class clazz = Class.forName("sun.net.www.protocol.jar.JarFileFactory", true, URL.class.getClassLoader());
Field fields[] = clazz.getDeclaredFields();
for (Field field : fields) {
if ("fileCache".equals(field.getName())) {
field.setAccessible(true);
HashMap<String,JarFile> files = (HashMap<String,JarFile>) field.get(null);
Set<JarFile> jars = new HashSet<>();
jars.addAll(files.values());
for (JarFile file : jars) {
file.close();
}
}
}
} catch (ClassNotFoundException | IllegalAccessException | SecurityException | IllegalArgumentException ex) {
logger.log(Level.SEVERE, null, ex);
} catch (IOException ex) {
logger.log(Level.SEVERE, null, ex);
}

JarFileUtils.closeCachedJarFiles();

// perform full GC after cleaning to ensure full clean up
System.gc();

//if directory deployment then do not remove the directory
if ( (! keepreposdir) && !isDirectoryDeployed && source.exists()) {
/*
Expand Down
Expand Up @@ -39,24 +39,18 @@
*/
package fish.payara.nucleus.util;

import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import com.sun.enterprise.admin.util.JarFileUtils;
import org.glassfish.api.StartupRunLevel;
import org.glassfish.api.event.EventListener;
import org.glassfish.api.event.EventTypes;
import org.glassfish.api.event.Events;
import org.glassfish.hk2.runlevel.RunLevel;
import org.jvnet.hk2.annotations.Service;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.logging.Logger;

/**
* Clean up to stop a stale file handles remaining open
*
Expand All @@ -79,29 +73,9 @@ public void postConstruct() {

@Override
public void event(Event event) {

if (event.is(EventTypes.SERVER_READY)) {
logger.config("Cleaning JarFileFactory Cache to prevent jar FD leaks");
try {
// Ensure JarFile is closed
Class clazz = Class.forName("sun.net.www.protocol.jar.JarFileFactory", true, URL.class.getClassLoader());
Field fields[] = clazz.getDeclaredFields();
for (Field field : fields) {
if ("fileCache".equals(field.getName())) {
field.setAccessible(true);
HashMap<String,JarFile> files = (HashMap<String,JarFile>) field.get(null);
Set<JarFile> jars = new HashSet<>();
jars.addAll(files.values());
for (JarFile file : jars) {
file.close();
}
}
}
} catch (ClassNotFoundException | IllegalAccessException | SecurityException | IllegalArgumentException ex) {
logger.log(Level.SEVERE, null, ex);
} catch (IOException ex) {
logger.log(Level.SEVERE, null, ex);
}
JarFileUtils.closeCachedJarFiles();
}
}

Expand Down