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