Michael Bedward ha scritto:
I'm following this with interest and am +0 about it so far because I
don't do web apps.

Regarding JAI, tthere is a method to unregister an operation
descriptor but I've never tried it.  Simone: have you any experience
with this ?

On GS devel there is a patch I provided that uses exactly that
(attached, it's a bit messy but you should see the JAI cleanup
 spots)


What I'm trying to do here is to donate that back to GeoTools.

Cheers
Andrea

--
Andrea Aime
OpenGeo - http://opengeo.org
Expert service straight from the developers.
diff --git a/src/extension/ogr/src/main/java/org/geoserver/wfs/response/Ogr2OgrConfigurator.java b/src/extension/ogr/src/main/java/org/geoserver/wfs/response/Ogr2OgrConfigurator.java
index 0eadbf3..c49cb9e 100644
--- a/src/extension/ogr/src/main/java/org/geoserver/wfs/response/Ogr2OgrConfigurator.java
+++ b/src/extension/ogr/src/main/java/org/geoserver/wfs/response/Ogr2OgrConfigurator.java
@@ -13,6 +13,9 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import org.geotools.util.logging.Logging;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextClosedEvent;
 import org.vfny.geoserver.global.GeoserverDataDirectory;
 
 import com.thoughtworks.xstream.XStream;
@@ -23,7 +26,7 @@ import com.thoughtworks.xstream.XStream;
  * @author Administrator
  *
  */
-public class Ogr2OgrConfigurator {
+public class Ogr2OgrConfigurator implements ApplicationListener {
     private static final Logger LOGGER = Logging.getLogger(Ogr2OgrConfigurator.class);
 
     Ogr2OgrOutputFormat of;
@@ -108,4 +111,13 @@ public class Ogr2OgrConfigurator {
         }
     }
 
+    /**
+     * Kill all threads on web app context shutdown to avoid permgen leaks
+     */
+    public void onApplicationEvent(ApplicationEvent event) {
+        if(event instanceof ContextClosedEvent) {
+            timer.cancel();
+        }
+    }
+
 }
diff --git a/src/web/app/src/main/java/org/geoserver/GeoserverInitStartupListener.java b/src/web/app/src/main/java/org/geoserver/GeoserverInitStartupListener.java
index 9d359a2..91d5d58 100644
--- a/src/web/app/src/main/java/org/geoserver/GeoserverInitStartupListener.java
+++ b/src/web/app/src/main/java/org/geoserver/GeoserverInitStartupListener.java
@@ -4,16 +4,45 @@
  */
 package org.geoserver;
 
+import java.beans.Introspector;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Formattable;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import javax.imageio.spi.IIORegistry;
+import javax.imageio.spi.IIOServiceProvider;
 import javax.imageio.spi.ImageReaderSpi;
+import javax.media.jai.JAI;
+import javax.media.jai.OperationRegistry;
+import javax.media.jai.RegistryElementDescriptor;
+import javax.media.jai.RegistryMode;
 import javax.servlet.ServletContextEvent;
 import javax.servlet.ServletContextListener;
 
+import org.apache.commons.logging.LogFactory;
+import org.apache.log4j.LogManager;
+import org.geoserver.logging.LoggingUtils;
+import org.geoserver.platform.GeoServerExtensions;
 import org.geotools.factory.Hints;
+import org.geotools.referencing.ReferencingFactoryFinder;
+import org.geotools.referencing.factory.AbstractAuthorityFactory;
+import org.geotools.referencing.factory.DeferredAuthorityFactory;
+import org.geotools.referencing.operation.DefaultMathTransformFactory;
 import org.geotools.resources.image.ImageUtilities;
 import org.geotools.util.WeakCollectionCleaner;
 import org.geotools.util.logging.Logging;
+import org.opengis.referencing.AuthorityFactory;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.operation.MathTransformFactory;
 
 /**
  * Listens for GeoServer startup and tries to configure axis order, logging
@@ -23,20 +52,27 @@ import org.geotools.util.logging.Logging;
 public class GeoserverInitStartupListener implements ServletContextListener {
     private static final Logger LOGGER = Logging
             .getLogger("org.geoserver.logging");
+    
+    boolean relinquishLoggingControl;
 
-    public void contextDestroyed(ServletContextEvent sce) {
-        WeakCollectionCleaner.DEFAULT.exit();
-    }
+    private Iterator<Class<?>> products;
 
     public void contextInitialized(ServletContextEvent sce) {
+        // start up tctool - remove it before committing!!!!
+        // new tilecachetool.TCTool().setVisible(true);
+        
+        
+        // make sure we remember if GeoServer controls logging or not
+        String strValue = GeoServerExtensions.getProperty(LoggingUtils.RELINQUISH_LOG4J_CONTROL, 
+                sce.getServletContext());
+        relinquishLoggingControl = Boolean.valueOf(strValue);
+        
         // if the server admin did not set it up otherwise, force X/Y axis
         // ordering
         // This one is a good place because we need to initialize this property
         // before any other opeation can trigger the initialization of the CRS
         // subsystem
         if (System.getProperty("org.geotools.referencing.forceXY") == null) {
-//            Hints.putSystemDefault(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER,
-//                    Boolean.TRUE);
             System.setProperty("org.geotools.referencing.forceXY", "true");
         }
         if (Boolean.TRUE.equals(Hints
@@ -70,5 +106,167 @@ public class GeoserverInitStartupListener implements ServletContextListener {
             ImageUtilities.allowNativeCodec("png", ImageReaderSpi.class, false);
         }
     }
+    
+    /**
+     * This method tries hard to stop all threads and remove all references to classes in GeoServer
+     * so that we can avoid permgen leaks on application undeploy.
+     * What happes is that, if any JDK class references to one of the classes loaded by the
+     * webapp classloader, then the classloader cannot be collected and neither can all the
+     * classes loaded by it (since each class keeps a back reference to the classloader that
+     * loaded it). The same happens for any residual thread launched by the web app.
+     */
+    public void contextDestroyed(ServletContextEvent sce) {
+        try {
+            LOGGER.info("Beginning GeoServer cleanup sequence");
+            
+            // the dreaded classloader
+            ClassLoader webappClassLoader = getClass().getClassLoader();
+            
+            // unload all of the jdbc drivers we have loaded. We need to store them and unregister
+            // later to avoid concurrent modification exceptions
+            Enumeration<Driver> drivers = DriverManager.getDrivers();
+            Set<Driver> driversToUnload = new HashSet<Driver>();
+            while (drivers.hasMoreElements()) {
+                    Driver driver = drivers.nextElement();
+                    try {
+                        // the driver class loader can be null if the driver comes from the JDK, such as the 
+                        // sun.jdbc.odbc.JdbcOdbcDriver
+                        ClassLoader driverClassLoader = driver.getClass().getClassLoader();
+                        if (driverClassLoader != null && webappClassLoader.equals(driverClassLoader)) {
+                                driversToUnload.add(driver);
+                        }
+                    } catch(Throwable t) {
+                        t.printStackTrace();
+                    }
+            }
+            for (Driver driver : driversToUnload) {
+                try {
+                    DriverManager.deregisterDriver(driver);
+                    LOGGER.info("Unregistered JDBC driver " + driver);
+                } catch(Exception e) {
+                    LOGGER.log(Level.SEVERE, "Could now unload driver " + driver.getClass(), e);
+                }
+            }
+            drivers = DriverManager.getDrivers();
+            while (drivers.hasMoreElements()) {
+                    Driver driver = drivers.nextElement();
+            }
+            org.h2.Driver.unload();
+            
+            // unload all deferred authority factories so that we get rid of the timer tasks in them
+            try {
+                disposeAuthorityFactories(ReferencingFactoryFinder.getCoordinateOperationAuthorityFactories(null));
+            } catch (Throwable e) {
+                LOGGER.log(Level.WARNING, "Error occurred trying to dispose authority factories", e);
+            }
+            try {
+                disposeAuthorityFactories(ReferencingFactoryFinder.getCRSAuthorityFactories(null));
+            } catch (Throwable e) {
+                LOGGER.log(Level.WARNING, "Error occurred trying to dispose authority factories", e);
+            }
+            try {
+                disposeAuthorityFactories(ReferencingFactoryFinder.getCSAuthorityFactories(null));
+            } catch (Throwable e) {
+                LOGGER.log(Level.WARNING, "Error occurred trying to dispose authority factories", e);
+            }
+            try {
+                for(MathTransformFactory factory : ReferencingFactoryFinder.getMathTransformFactories(null)) {
+                    if(factory instanceof DefaultMathTransformFactory) {
+                        ((DefaultMathTransformFactory) factory).exit();
+                    }
+                }
+            } catch (Throwable e) {
+                LOGGER.log(Level.WARNING, "Error occurred trying to dispose authority factories", e);
+            }
+            
+            // kill the threads created by referencing
+            WeakCollectionCleaner.DEFAULT.exit();
+            DeferredAuthorityFactory.exit();
+            org.geotools.referencing.wkt.Formattable.exit();
+            LOGGER.info("Shut down GT referencing threads ");
+            
+            // unload everything that JAI ImageIO can still refer to
+            // We need to store them and unregister later to avoid concurrent modification exceptions
+            final IIORegistry ioRegistry = IIORegistry.getDefaultInstance();
+            Set<IIOServiceProvider> providersToUnload = new HashSet();
+            for(Iterator<Class<?>> cats = ioRegistry.getCategories(); cats.hasNext(); ) {
+                Class<?> category = cats.next();
+                for (Iterator it = ioRegistry.getServiceProviders(category, false); it.hasNext();) {
+                    final IIOServiceProvider provider = (IIOServiceProvider) it.next();
+                    if(webappClassLoader.equals(provider.getClass().getClassLoader())) {
+                        providersToUnload.add(provider);
+                    }
+                }
+            }
+            for (IIOServiceProvider provider : providersToUnload) {
+                ioRegistry.deregisterServiceProvider(provider);
+                LOGGER.info("Unregistering Image I/O provider " + provider);
+            }
+            
+            // unload everything that JAI can still refer to
+            final OperationRegistry opRegistry = JAI.getDefaultInstance().getOperationRegistry();
+            for(String mode : RegistryMode.getModeNames()) {
+                for (Iterator descriptors = opRegistry.getDescriptors(mode).iterator(); descriptors != null && descriptors.hasNext();) {
+                    RegistryElementDescriptor red = (RegistryElementDescriptor) descriptors.next();
+                    int factoryCount = 0;
+                    int unregisteredCount = 0;
+                    // look for all the factories for that operation
+                    for (Iterator factories = opRegistry.getFactoryIterator(mode, red.getName()); factories != null && factories.hasNext();) {
+                        Object factory = factories.next();
+                        if(factory == null) {
+                            continue;
+                        }
+                        factoryCount++;
+                        if(webappClassLoader.equals(factory.getClass().getClassLoader())) {
+                            boolean unregistered = false;
+                            // we need to scan against all "products" to unregister the factory
+                            Vector orderedProductList = opRegistry.getOrderedProductList(mode, red.getName());
+                            if(orderedProductList != null) {
+                                for(Iterator products = orderedProductList.iterator(); products != null && products.hasNext();) {
+                                    String product = (String) products.next();
+                                    try {
+                                        opRegistry.unregisterFactory(mode, red.getName(), product, factory);
+                                        LOGGER.info("Unregistering JAI factory " + factory.getClass());
+                                    } catch(Throwable t) {
+                                        // may fail due to the factory not being registered against that product
+                                    }
+                                }
+                            }
+                            if(unregistered) {
+                                unregisteredCount++;
+                            }
+                            
+                        } 
+                    }
+                    
+                    // if all the factories were unregistered, get rid of the descriptor as well
+                    if(factoryCount > 0 && unregisteredCount == factoryCount) {
+                        opRegistry.unregisterDescriptor(red);
+                    }
+                }
+            }
+            
+            // flush all javabean introspection caches as this too can keep a webapp classloader from being unloaded
+            Introspector.flushCaches();
+            LOGGER.info("Cleaned up javabean caches");
+            
+            // unload the logging framework
+            if(!relinquishLoggingControl)
+                LogManager.shutdown();
+            LogFactory.release(Thread.currentThread().getContextClassLoader());
+        } catch(Throwable t) {
+            // if anything goes south during the cleanup procedures I want to know what it is
+            t.printStackTrace();
+        }
+    }
+
+    private void disposeAuthorityFactories(Set<? extends AuthorityFactory> factories)
+            throws FactoryException {
+        for (AuthorityFactory af : factories) {
+            if(af instanceof AbstractAuthorityFactory) {
+                ((AbstractAuthorityFactory) af).dispose();
+            }
+        }
+    }
 
 }
------------------------------------------------------------------------------

_______________________________________________
Geotools-devel mailing list
Geotools-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/geotools-devel

Reply via email to