13 Replies Latest reply on Jan 21, 2009 7:54 AM by alesj

    Lifecycle stop() method and new objects

      Hello,

      I just noticed an interesting problem with the bean lifecycle method "stop()".

      It seems that once we are in the stop method the classloader is not available anymore...

      Currently in my stop method, the code build an array from a list doing the following bit:

      handlers = m_Handlers.toArray(new Handler[m_Handlers.size()]);

      Now if, for some reason, the service was not used and the list was empty then the Handler class has not been loaded yet. It is only when my "stop" implementation tries to clean up everything that it needs to create a dummy array of "Handler".

      I agree I can easily fix my code (already done! ;-) ) but still, I wonder if it would not be safer to close the access to the classloader only once the "stop" method has executed.

      I kind of agree that for "destroy" it is probably reasonable. But I do wonder if there is not some legitimate situations where "stop" could really need to load one or two objects using the classloader (for example reporting some final stats results into some dedicated objects).

      Any opinions?

      Gilles.

        • 1. Re: Lifecycle stop() method and new objects
          dmlloyd

          Are you setting up explicit classloader dependencies via jboss-classloading.xml? If not, how are you setting up your classloaders?

          • 2. Re: Lifecycle stop() method and new objects
            alesj

            The classloader should be there,
            since we only remove it from BMD once it's uninstalled:
            - http://anonsvn.jboss.org/repos/jbossas/projects/jboss-deployers/trunk/deployers-vfs/src/main/java/org/jboss/deployers/vfs/deployer/kernel/BeanMetaDataDeployer.java

            In deployment phase it's removed here:
            - http://anonsvn.jboss.org/repos/jbossas/projects/jboss-deployers/trunk/deployers-impl/src/main/java/org/jboss/deployers/plugins/classloading/AbstractLevelClassLoaderSystemDeployer.java

            I'm not saying its impossible, but I doubt it's us. ;-)
            Since this would already pop-up, like you said,
            some stop/destroy method needs to do something.

            Perhaps you don't have your dependencies right,
            and something snatches cl from the bean,
            w/o properly unwinding/uninstalling it?

            • 3. Re: Lifecycle stop() method and new objects
              alesj

               

              "alesj" wrote:

              I'm not saying its impossible, but I doubt it's us. ;-)

              This doesn't mean I won't consider/accept a test case. :-)

              • 4. Re: Lifecycle stop() method and new objects

                At the moment I must admit I haven't defined any dependencies metadata as everything is self contained in a single small jar file. Do I still need to define them? (admittedly I kind of relied on the JAR structure deployer to define the default CL going with a JAR...)

                The sample is a simple telnet echo server. If I connect to the echo server the "Handler" object gets created fine. And then if I delete the JAR, the bean is also undeployed fine.

                Now, if I deploy and then immediately undeploy the JAR (without connecting to the telnet server) then I have a NoClassDefFoundError that occurs during the undeploy stage (typically when I try the close down any Handlers if any but to do that the code needs to create one dummy Handler...).

                Here is the stack trace:

                11:17:53,876 WARN [StartStopLifecycleAction] Error during stop for Controler
                java.lang.NoClassDefFoundError: mcfproto/impl/Handler
                 at mcfproto.impl.NetServer.closeAll(NetServer.java:110)
                 at mcfproto.impl.NetServer.stop(NetServer.java:72)
                 at mcfproto.impl.ControlerImpl.stop(ControlerImpl.java:20)
                 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
                 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
                 at java.lang.reflect.Method.invoke(Method.java:597)
                 at org.jboss.reflect.plugins.introspection.ReflectionUtils.invoke(ReflectionUtils.java:59)
                 at org.jboss.reflect.plugins.introspection.ReflectMethodInfoImpl.invoke(ReflectMethodInfoImpl.java:150)
                 at org.jboss.joinpoint.plugins.BasicMethodJoinPoint.dispatch(BasicMethodJoinPoint.java:66)
                 at org.jboss.kernel.plugins.dependency.KernelControllerContextAction$JoinpointDispatchWrapper.execute(KernelCont
                rollerContextAction.java:241)
                 at org.jboss.kernel.plugins.dependency.ExecutionWrapper.execute(ExecutionWrapper.java:47)
                 at org.jboss.kernel.plugins.dependency.KernelControllerContextAction.dispatchExecutionWrapper(KernelControllerCo
                ntextAction.java:109)
                 at org.jboss.kernel.plugins.dependency.KernelControllerContextAction.dispatchJoinPoint(KernelControllerContextAc
                tion.java:70)
                 at org.jboss.kernel.plugins.dependency.LifecycleAction.uninstallActionInternal(LifecycleAction.java:249)
                 at org.jboss.kernel.plugins.dependency.InstallsAwareAction.uninstallAction(InstallsAwareAction.java:157)
                 at org.jboss.kernel.plugins.dependency.InstallsAwareAction.uninstallAction(InstallsAwareAction.java:42)
                 at org.jboss.dependency.plugins.action.SimpleControllerContextAction.simpleUninstallAction(SimpleControllerConte
                xtAction.java:79)
                 at org.jboss.dependency.plugins.action.AccessControllerContextAction.uninstall(AccessControllerContextAction.jav
                a:131)
                 at org.jboss.dependency.plugins.AbstractControllerContextActions.uninstall(AbstractControllerContextActions.java
                :58)
                 at org.jboss.dependency.plugins.AbstractControllerContext.uninstall(AbstractControllerContext.java:354)
                 at org.jboss.dependency.plugins.AbstractController.uninstall(AbstractController.java:1631)
                 at org.jboss.dependency.plugins.AbstractController.uninstallContext(AbstractController.java:1242)
                 at org.jboss.dependency.plugins.AbstractController.uninstallContext(AbstractController.java:1146)
                 at org.jboss.dependency.plugins.AbstractController.uninstall(AbstractController.java:655)
                 at org.jboss.dependency.plugins.AbstractController.uninstall(AbstractController.java:568)
                 at org.jboss.deployers.vfs.deployer.kernel.BeanMetaDataDeployer.undeploy(BeanMetaDataDeployer.java:156)
                 at org.jboss.deployers.vfs.deployer.kernel.BeanMetaDataDeployer.undeploy(BeanMetaDataDeployer.java:51)
                 at org.jboss.deployers.spi.deployer.helpers.AbstractSimpleRealDeployer.internalUndeploy(AbstractSimpleRealDeploy
                er.java:69)
                 at org.jboss.deployers.spi.deployer.helpers.AbstractRealDeployer.undeploy(AbstractRealDeployer.java:112)
                 at org.jboss.deployers.plugins.deployers.DeployerWrapper.undeploy(DeployerWrapper.java:192)
                 at org.jboss.deployers.plugins.deployers.DeployersImpl.doUndeploy(DeployersImpl.java:1315)
                 at org.jboss.deployers.plugins.deployers.DeployersImpl.doUninstallParentLast(DeployersImpl.java:1222)
                 at org.jboss.deployers.plugins.deployers.DeployersImpl.doUninstallParentLast(DeployersImpl.java:1215)
                 at org.jboss.deployers.plugins.deployers.DeployersImpl.uninstall(DeployersImpl.java:1177)
                 at org.jboss.dependency.plugins.AbstractControllerContext.uninstall(AbstractControllerContext.java:354)
                 at org.jboss.dependency.plugins.AbstractController.uninstall(AbstractController.java:1631)
                 at org.jboss.dependency.plugins.AbstractController.uninstallContext(AbstractController.java:1242)
                 at org.jboss.dependency.plugins.AbstractController.change(AbstractController.java:827)
                 at org.jboss.dependency.plugins.AbstractController.change(AbstractController.java:553)
                 at org.jboss.deployers.plugins.deployers.DeployersImpl.process(DeployersImpl.java:543)
                 at org.jboss.deployers.plugins.main.MainDeployerImpl.process(MainDeployerImpl.java:541)
                 at org.jboss.system.server.profileservice.hotdeploy.HDScanner.scan(HDScanner.java:290)
                 at org.jboss.system.server.profileservice.hotdeploy.HDScanner.run(HDScanner.java:221)
                 at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441)
                 at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:317)
                 at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:150)
                 at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$101(ScheduledThreadPoolExecutor.j
                ava:98)
                 at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.runPeriodic(ScheduledThreadPoolExecutor.
                java:181)
                 at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:205
                )
                 at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:885)
                 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:907)
                 at java.lang.Thread.run(Thread.java:619)
                11:18:09,057 INFO [ControlerImpl] ControlerImpl started...
                11:18:29,085 INFO [ControlerImpl] ControlerImpl stopped...
                11:19:59,656 INFO [ControlerImpl] ControlerImpl started...
                


                Here is the jboss-beans.xml

                <?xml version="1.0" encoding="UTF-8"?>
                
                <deployment xmlns="urn:jboss:bean-deployer:2.0">
                
                 <bean name="Controler" class="mcfproto.impl.ControlerImpl">
                 </bean>
                
                </deployment>
                


                Here is the source for ControlerImpl:
                package mcfproto.impl;
                
                import org.jboss.logging.Logger;
                
                public class ControlerImpl {
                 private static Logger log = Logger.getLogger(ControlerImpl.class);
                
                 private NetServer server;
                
                 public ControlerImpl() {
                 server = new NetServer();
                 }
                
                 public void start(){
                 server.start();
                 log.info("ControlerImpl started...");
                 }
                
                 public void stop() {
                 server.stop();
                 log.info("ControlerImpl stopped...");
                 }
                
                 public void destroy() {
                 server = null;
                 }
                
                }
                


                Here is the source for NetServer:
                package mcfproto.impl;
                
                import java.net.ServerSocket;
                import java.net.Socket;
                import java.util.HashSet;
                import java.util.Set;
                
                import org.jboss.logging.Logger;
                
                /**
                 * Dummy telnet server that listen on port 23 and is used for testing
                 */
                public class NetServer {
                
                 private final static Logger LOG = Logger.getLogger(NetServer.class);
                
                 private static final int TELNET_PORT = 23;
                
                 private ServerSocket m_Server;
                
                 volatile boolean m_Quit;
                
                 private Set<Handler> m_Handlers;
                
                 private Thread m_Thread;
                
                 public NetServer() {
                 m_Handlers = new HashSet<Handler>();
                 }
                
                 public void start() {
                 try {
                 m_Thread = new Thread(new Runnable() {
                 public void run() {
                 while (!m_Quit) {
                 try {
                 m_Server = new ServerSocket(TELNET_PORT);
                 loop();
                 } catch (Exception e) {
                 if (m_Quit) {
                 // It is expected that we get an exception here
                 // as the "stop" method violently close the socket...
                 LOG.debug(e);
                 } else {
                 LOG.error("[NetServer] Inner server listen loop...", e);
                 sleep(1000);
                 }
                 }
                 }
                 closeAll();
                 }
                 }, "NetServer Thread");
                 m_Thread.start();
                 } catch (Exception e) {
                 throw new RuntimeException(e);
                 }
                 }
                
                 public void stop() {
                 m_Quit = true;
                 try {
                 // force the "loop" to quit TCP "accept" by closing socket...
                 m_Server.close();
                 } catch (Exception e) {
                 throw new RuntimeException(e);
                 }
                 try {
                 m_Thread.join();
                 } catch (InterruptedException e) {
                 // demo code, so ignore (but should not in production code)
                 }
                 closeAll();
                 m_Server = null;
                 m_Handlers = null;
                 m_Thread = null;
                 }
                
                 void remove(Handler handler) {
                 synchronized (m_Handlers) {
                 m_Handlers.remove(handler);
                 }
                 }
                
                 private void loop() throws Exception {
                 // no need to synchronize as m_Quit is volatile and
                 // is acting as a memory synchronization block.
                 // (and here we don't need sequencing, just mem sync).
                 while (!m_Quit) {
                 Socket socket = m_Server.accept();
                
                 Handler handler = new Handler(socket, this);
                
                 synchronized (m_Handlers) {
                 m_Handlers.add(handler);
                 }
                 handler.start();
                 }
                 }
                
                 private void sleep(int ms) {
                 try {
                 Thread.sleep(ms);
                 } catch (InterruptedException interruptedexception) {
                 }
                 }
                
                 private void closeAll() {
                 Handler[] handlers = null;
                 synchronized (m_Handlers) {
                 handlers = m_Handlers.toArray(new Handler[m_Handlers.size()]);
                 }
                 for (Handler handler : handlers) {
                 try {
                 handler.close();
                 handler.join();
                 } catch (Throwable throwable) {
                 }
                 }
                 }
                }
                


                And finally, here is the source for the Handler class:
                /*
                 * Filename : Handler.java
                 * Version : $Revision: $
                 * Date : $Date: $
                 * Copyright(c) Ubiquity Software Corporation
                 * http://www.ubiquity.net
                 */
                package mcfproto.impl;
                
                import java.io.BufferedReader;
                import java.io.IOException;
                import java.io.InputStreamReader;
                import java.io.OutputStreamWriter;
                import java.io.PrintWriter;
                import java.net.Socket;
                
                import org.jboss.logging.Logger;
                
                public class Handler extends Thread {
                
                 private final static Logger LOG = Logger.getLogger(Handler.class);
                
                 private volatile boolean quit;
                
                 private Socket m_Socket;
                
                 private PrintWriter m_Writer;
                
                 private NetServer m_Parent;
                
                 public Handler(Socket socket, NetServer activator) throws Exception {
                 m_Socket = socket;
                
                 m_Parent = activator;
                 m_Writer = new PrintWriter(new OutputStreamWriter(socket
                 .getOutputStream()));
                 }
                
                 void close() {
                 try {
                 quit = true;
                 m_Writer.close();
                 m_Socket.close();
                 } catch (IOException ioexception) {
                 }
                 }
                
                 public void run() {
                 try {
                 send("Welcome to Proto. Please enter a command:");
                 send("Ex: *[echo msg]");
                 send("Ex: quit");
                 BufferedReader reader = new BufferedReader(new InputStreamReader(
                 m_Socket.getInputStream()));
                
                 String line;
                 send("\n\r");
                 while (!quit && (line = reader.readLine()) != null) {
                 process(clean(line));
                 }
                 send("\n\rbye");
                
                 } catch (Exception e) {
                 if (quit) {
                 LOG.debug(e);
                 } else {
                 LOG.error(
                 "Error whilst handler was trying to process client response.",
                 e);
                 }
                 } finally {
                 // TODO: check: need reader.close() ???
                 m_Parent.remove(this);
                 close();
                 m_Socket = null;
                 m_Writer = null;
                 m_Parent = null;
                 }
                 return;
                 }
                
                 private void send(String msg) throws IOException {
                 m_Writer.println(msg);
                 m_Writer.flush();
                 }
                
                 private void process(String line) throws IOException {
                
                 LOG.debug("Handler:process() method called with \"" + line + "\"");
                 if (line.startsWith("quit")) {
                 LOG.debug("QUIT command received : (" + line + ")");
                 quit = true;
                 } else if (line.startsWith("*")) {
                 line = line.substring(1, line.length());
                 dispatch(line);
                 } else {
                 send("Unknown command received.");
                 }
                 }
                
                 private void dispatch(String line) throws IOException {
                 send(line);
                 }
                
                 private String clean(String line) {
                 int size = line.length();
                 char[] buffer = new char[size];
                 int out = 0;
                 for (int in = 0; in < size; in++) {
                 char c = line.charAt(in);
                 if (c == '\b') // backspace - ignore
                 {
                 if (out > 0) {
                 out--;
                 }
                 } else if (c >= ' ' && c < '\177') {
                 buffer[out++] = c;
                 }
                 }
                 String cleanedString = new String(buffer, 0, out);
                
                 return cleanedString;
                 }
                
                }
                


                • 5. Re: Lifecycle stop() method and new objects
                  alesj
                  • 6. Re: Lifecycle stop() method and new objects

                    Ah! I forgot to precise. For this test I am using JBoss 5 RC 2.

                    Thanks again for your help.

                    Gilles.

                    • 7. Re: Lifecycle stop() method and new objects

                      Ok, thanks (message crossover...).

                      I am going to try what JIRA suggest.

                      Gilles.

                      • 8. Re: Lifecycle stop() method and new objects

                        Yes, it worked.

                        Thanks :-)

                        • 9. Re: Lifecycle stop() method and new objects

                          For info.

                          Having though about the problem, I agree with JIRA if the situation is the case where components start threads and they still go on (creating new objects) after the stop method on the main POJO has ended.

                          But in my example, the "stop" method returns only once all threads involved in the component have stopped. So I have the feeling it could still highlight something strange somewhere.

                          But anyway, the given workaround works fine so I am definitivelly not going to complain ;-) (this is just for info in case this could highlight the why of some other problem).

                          • 10. Re: Lifecycle stop() method and new objects
                            alesj

                             

                            "gcompienne" wrote:
                            So I have the feeling it could still highlight something strange somewhere.

                            But anyway, the given workaround ...

                            Actually I don't see this as a workaround. :-)
                            Although this is marked as workaround in mentioned JIRA issue.

                            It's a normal behavior on how you load classes,
                            when the deployment is deleted --> undeployed.

                            Unless you make a temp copy of it,
                            I don't see how else you're still gonna access that resource.

                            It's probably the fact that we now do it differently
                            due to VFS's capability of handling resources as they are (even nested one's),
                            that makes this example a bit unexpected - since almost everybody else does it the old way == temp copy.

                            • 11. Re: Lifecycle stop() method and new objects

                              Ah ok, I understand now (my fault).

                              I had been assuming that VFS was doing its own copy of the artifact and that the removal (or update) of the original file was only used as an indicator of an undeploy (or redeploy) to be done (then followed by a removal of the copy).

                              But, yeah, if it does not make a copy then yes, I can see it would have serious problems to try and load anything ;-)

                              Thanks for the clarification.

                              Gilles.

                              • 12. Re: Lifecycle stop() method and new objects

                                So, if deleting the file from the deploy directory is no longer correct, what is the "correct" way to undeploy on JBoss AS now?

                                • 13. Re: Lifecycle stop() method and new objects
                                  alesj

                                   

                                  "norman.richards@jboss.com" wrote:
                                  So, if deleting the file from the deploy directory is no longer correct, what is the "correct" way to undeploy on JBoss AS now?

                                  It depends on what your app is doing at undeploy.
                                  e.g. still loading/reading some resources
                                  Then you need to apply a temp mechanism.

                                  The fact that we did that before by default,
                                  doesn't make what we do now not "correct". ;-)