Thursday, 19 November 2009

Undeploying Google Guice jar file locked

When trying to undeploy an webapp that uses Google Guice from an webapp server like Tomcat, you may notice that it doesn't undeploy properly. The problem seems to be related to com.google.inject.internal.Finalizer, which is a Thread created to clean up after Guice is finished. However the thread doesn't stop properly.

My solution is to modify this class (by just adding that individual source file to the project as I don't want to rebuild the whole project) adding a stop method that can be call from a ServletContextListener.contextDestroyed().

My modified Finializer:
private boolean stop = false;

/**
 * Loops continuously, pulling references off the queue and cleaning them up.
 */
//  @SuppressWarnings("InfiniteLoopStatement")
@Override
public void run() {
  try {
    while (!stop) {
      try {
        cleanUp(queue.remove());
      } catch (InterruptedException e) { /* ignore */ }
    }
  } catch (ShutDown shutDown) { /* ignore */ }
}

public void stopThread() {
  this.stop = true;
  this.interrupt();
}

Then from listener (in this case I've modified GuiceContextListener from GuiceSF, which seems to have now disappeared but you could write your own from scratch), you need to stop the thread:
public class GuiceContextListener implements ServletContextListener {
  public void contextDestroyed(ServletContextEvent contextEvent)
  {
    ServletContext context = contextEvent.getServletContext();
    //start custom code
    
    //first use the GuiceyFruit Injectors class to notify all objects their being destroyed
    try
    {
      Injector injector = getInjector(context);
      Injectors.close(injector);
    }
    catch (CloseFailedException e)
    {
      LOG.log(Level.SEVERE, "Guicesf finhised", e);
    }
    
    ThreadGroup root = Thread.currentThread().getThreadGroup().getParent();
    while (root.getParent() != null)
    {
      root = root.getParent();
    }
  
    //now find the thread
    Thread thread = getThread(root, Finalizer.class.getName());
    if (thread == null)
    {
      LOG.log(Level.INFO, "finalizer not found");
    }
    else
    {
      LOG.log(Level.INFO, "Stopping finalizer");
      callMethod(thread, "stopThread");
    }
    //end custom code
    context.removeAttribute(Injector.class.getName());
    LOG.log(Level.INFO, "Guicesf finished");
  }

  private static Thread getThread(ThreadGroup group, String name)
  {
    int num = group.activeCount();
    Thread[] threads = new Thread[num * 2];
    num = group.enumerate(threads, true);
    for (int i = 0; i < num; i++)
    {
      Thread thread = threads[i];
      if (thread == null)
      {
      }
      else if (thread.getName().equals(name))
      {
        return thread;
      }
    }
    return null;
  }

  private static void callMethod(Object obj, String methodName)
  {
    try
    {
      Class clazz = obj.getClass();
      Method method = clazz.getMethod(methodName, (Class[]) null);
      method.invoke(obj, (Object[]) null);
    }
    catch (Exception e)
    {
      LOG.log(Level.SEVERE, "callMethod caught exception", e);
    }
  }

Download the modified source: undeploy_guice-src.zip

Other people have noticed the problem, which trace it to problems with FinalizableReferenceQueue. There is an attached patch which resolves the issue of having to manually stop the thread.

No comments:

Post a Comment