Cool - I am in favour of that.

Is there any way we could "register" some activites for this hook? I expect 
that an OSGi context could also make use of them for a clean exit.

For OSGi it is willing to startup / shutdown a module - we already have 
GeoTools init so cleanup could be the spiritual opposite. Is the GeoTools class 
an acceptable location for this patch?


Jody

On 14/05/2010, at 6:25 PM, Andrea Aime wrote:

> 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