Skip to content

Commit

Permalink
OF-1522: Ensure Jetty contexts are fully cleaned
Browse files Browse the repository at this point in the history
  • Loading branch information
GregDThomas authored and guusdk committed Nov 30, 2018
1 parent caab100 commit 14dc991
Show file tree
Hide file tree
Showing 2 changed files with 261 additions and 8 deletions.
232 changes: 232 additions & 0 deletions xmppserver/src/main/java/org/eclipse/jetty/util/WebAppLoaderFix.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
//
// ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//

package org.eclipse.jetty.util;

import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.jar.JarFile;

public class WebAppLoaderFix
{
public static void checkAndClose(ClassLoader classLoader)
{
if (!isWindows() || !(classLoader instanceof URLClassLoader))
{
return;
}
HashSet<String> leakedJarNames = preClose((URLClassLoader)classLoader);
cleanupJarFileFactory(leakedJarNames);
}

private static boolean isWindows()
{
String osProp = System.getProperty("os.name").toLowerCase();
return osProp.indexOf("win") >= 0;
}

private static HashSet<String> preClose(URLClassLoader loader)
{
HashSet<String> leakedJarNames = new HashSet<>();
Field f = getClassField(URLClassLoader.class, "ucp");
if (f != null)
{
Object obj = null;
try
{
obj = f.get(loader);
final Object ucp = obj;
f = getClassField(ucp.getClass(), "loaders");
if (f != null)
{
ArrayList loaders = null;
try
{
loaders = (ArrayList) f.get(ucp);
}
catch (IllegalAccessException ex)
{
}
for (int i = 0; loaders != null && i < loaders.size(); i++)
{
obj = loaders.get(i);
f = getClassField(obj.getClass(), "jar");
if (f != null)
{
try
{
obj = f.get(obj);
} catch (IllegalAccessException ex)
{
}
if (obj instanceof JarFile)
{
final JarFile jarFile = (JarFile) obj;
leakedJarNames.add(jarFile.getName());
}
}
}
}
}
catch (IllegalAccessException ex)
{
}
}
return leakedJarNames;
}

private static Field getClassField(Class clz, String fieldName)
{
Field field = null;
try
{
field = clz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (Exception e)
{
}
return field;
}

private static void cleanupJarFileFactory(HashSet<String> leakedJarNames) {

Class classJarURLConnection = null;
try
{
classJarURLConnection = Class.forName("sun.net.www.protocol.jar.JarURLConnection");
}
catch (ClassNotFoundException ex)
{
return;
}

Field f = getClassField(classJarURLConnection, "factory");

if (f == null)
{
return;
}
Object obj = null;
try
{
obj = f.get(null);
} catch (IllegalAccessException ex)
{
return;
}

Class classJarFileFactory = obj.getClass();
HashMap fileCache = null;
f = getClassField(classJarFileFactory, "fileCache");
if (f == null)
{
return;
}
try
{
obj = f.get(null);
if (obj instanceof HashMap)
{
fileCache = (HashMap) obj;
}
}
catch (IllegalAccessException ex)
{
}
HashMap urlCache = null;
f = getClassField(classJarFileFactory, "urlCache");
if (f == null)
{
return;
}
try
{
obj = f.get(null);
if (obj instanceof HashMap)
{
urlCache = (HashMap) obj;
}
}
catch (IllegalAccessException ex)
{
}

if (urlCache != null)
{
HashMap urlCacheTmp = (HashMap) urlCache.clone();
Iterator it = urlCacheTmp.keySet().iterator();
while (it.hasNext())
{
obj = it.next();
if (!(obj instanceof JarFile))
{
continue;
}
JarFile jarFile = (JarFile) obj;
if (leakedJarNames.contains(jarFile.getName()))
{
try
{
jarFile.close();
}
catch (IOException ex)
{
}
if (fileCache != null)
{
fileCache.remove(urlCache.get(jarFile));
}
urlCache.remove(jarFile);
}
}
}
else if (fileCache != null)
{
HashMap fileCacheTmp = (HashMap) fileCache.clone();
Iterator it = fileCacheTmp.keySet().iterator();
while (it.hasNext())
{
Object key = it.next();
obj = fileCache.get(key);
if (!(obj instanceof JarFile))
{
continue;
}
JarFile jarFile = (JarFile) obj;
if (leakedJarNames.contains(jarFile.getName()))
{
try
{
jarFile.close();
}
catch (IOException ex)
{
}
fileCache.remove(key);
}
}
}
leakedJarNames.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,32 @@

package org.jivesoftware.openfire.http;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.jasper.servlet.JasperInitializer;
import org.apache.tomcat.InstanceManager;
import org.apache.tomcat.SimpleInstanceManager;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.plus.annotation.ContainerInitializer;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.WebAppLoaderFix;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.webapp.WebAppContext;
Expand All @@ -40,16 +55,16 @@
import org.jivesoftware.openfire.spi.ConnectionType;
import org.jivesoftware.openfire.spi.EncryptionArtifactFactory;
import org.jivesoftware.openfire.websocket.OpenfireWebSocketServlet;
import org.jivesoftware.util.*;
import org.jivesoftware.util.CertificateEventListener;
import org.jivesoftware.util.CertificateManager;
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener;
import org.jivesoftware.util.TaskEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Responsible for making available BOSH (functionality to the outside world, using an embedded web server.
*/
Expand Down Expand Up @@ -733,6 +748,12 @@ public void addJettyHandler( Handler handler )
*/
public void removeJettyHandler( Handler handler )
{
if (handler instanceof WebAppContext) {
// A work-around of the Jetty bug described at https://github.com/eclipse/jetty.project/issues/1425
// NOTE: According to some comments on WebAppLoaderFix, this may stop working on Java 9.
// Hopefully the Jetty team will have fixed the underlying bug by then
WebAppLoaderFix.checkAndClose(((WebAppContext) handler).getClassLoader());
}
extensionHandlers.removeHandler( handler );
if ( handler.isStarted() )
{
Expand Down

0 comments on commit 14dc991

Please sign in to comment.