9 Replies Latest reply on Aug 11, 2011 11:33 AM by clebert.suconic

    receiveNoWait doesn't work if parent is a daemon

    mainbrain

      Hello

       

      i got weird time outs and IllegalThreadStateExceptions when using another open source project in conjunction with HornetQ. A lengthy debugging session revealed an interresting problem.

       

      If the parent ThreadGroup is a daemon thread then MessageConsumer.receiveNoWait() doesn't work anymore when there are longer periods of time between calls to the receive method. Precondition is that System.getSecurityManager() == null.

       

      The first call to receiveNoWait results in HornetQThreadFactory class to create a ThreadGroup which inherits the daemon boolean from its parent. Then when HornetQThreadFactory processed the command/Runnable the ThreadGroup is destroy() 'ed after a while. Unfortunately subsequent calls to receiveNoWait use the same HornetqThreadFactory instance, which uses the destroy() 'ed ThreadGroup, which in turn causes an IllegalThreadStateException because it's still trying to use the destroyed ThreadGroup.

       

      I managed to reproduce the problem in a unit test. Just make sure there is a queue named "queue/test" and no security manager.

       

       

      
      
          
          @Test
          public void communicationShouldBePossibleAfterLongerPeriodsOfSilence() throws Exception {
              // setting the flag directly causes the same problem as creating a new ThreadGroup which is a daemon
              Thread.currentThread().getThreadGroup().setDaemon(true);
      
              final long DAEMON_THREAD_DESTROY_TIMEOUT = (long) (1000.0 * 60.0 * 1.3);
              final String QUEUE_NAME = "queue/test";
              final String TEST_DATA = "test";
      
              // empty queue
              while (getJMSMessage(JBOSS_HOST, JBOSS_PORT, QUEUE_NAME) != null) {
              }
      
              putJmsMessage(JBOSS_HOST, JBOSS_PORT, QUEUE_NAME, TEST_DATA);
              putJmsMessage(JBOSS_HOST, JBOSS_PORT, QUEUE_NAME, TEST_DATA);
      
              Thread.sleep(1000);
      
              String msg = getJMSMessage(JBOSS_HOST, JBOSS_PORT, QUEUE_NAME);
              assertEquals(TEST_DATA, msg);
      
              Thread.sleep(DAEMON_THREAD_DESTROY_TIMEOUT);
      
              msg = getJMSMessage(JBOSS_HOST, JBOSS_PORT, QUEUE_NAME);
              assertEquals(TEST_DATA, msg);
          }
      
          static void putJmsMessage(String host, String port, String queue, String msg) throws Exception {
              MessageProducer producer = null;
              Connection conn = null;
              Session session = null;
      
              try {
                  session = null;
                  conn = null;
      
                  Hashtable<String, String> env = null;
                  InitialContext iniCtx = null;
                  ConnectionFactory connFactory = null;
      
                  env = new Hashtable<String, String>();
                  env.put(Context.PROVIDER_URL, host + ":" + port);
                  env.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
                  env.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
                  // env.put("jnp.socket.Factory", "org.jnp.interfaces.TimedSocketFactory");
      
                  try {
                      iniCtx = new InitialContext(env);
                  } catch (NamingException e1) {
                      throw new Exception("Bad initial context. Maybe host and port are wrong.", e1);
                  }
      
                  Destination msgDestination;
                  // ................................lookup.............................
                  try {
                      Object o = iniCtx.lookup("ConnectionFactory");
                      connFactory = (ConnectionFactory) o;
      
                      msgDestination = (Destination) iniCtx.lookup(queue);
                  } catch (Exception e) {
                      throw new Exception(
                              "Error while performing lookup. Either JMS server is offline or lookup string(queue/topic) is not registered or misspelled.", e);
                  }
      
                  // ................................connect.............................
      
                  if (conn == null) {
                      conn = connFactory.createConnection();
                  }
      
                  if (session == null) {
                      session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
                  }
      
                  session.run();
                  conn.start();
      
                  producer = session.createProducer(msgDestination);
      
                  ObjectMessage jmsMessage = session.createObjectMessage();
                  jmsMessage.setObject(msg);
                  producer.send(jmsMessage);
      
              } finally {
                  if (producer != null)
                      producer.close();
                  if (session != null)
                      session.close();
                  if (conn != null)
                      conn.close();
              }
          }
      
          static String getJMSMessage(String host, String port, String queue) throws Exception {
              String msg = null;
      
              MessageConsumer receiver = null;
              Connection conn = null;
              Session session = null;
      
              try {
                  session = null;
                  conn = null;
      
                  Hashtable<String, String> env = null;
                  InitialContext iniCtx = null;
                  ConnectionFactory connFactory = null;
      
                  env = new Hashtable<String, String>();
                  env.put(Context.PROVIDER_URL, host + ":" + port);
                  env.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
                  env.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
                  // env.put("jnp.socket.Factory", "org.jnp.interfaces.TimedSocketFactory");
      
                  try {
                      iniCtx = new InitialContext(env);
                  } catch (NamingException e1) {
                      throw new Exception("Bad initial context. Maybe host and port are wrong.", e1);
                  }
      
                  Destination msgDestination;
                  // ................................lookup.............................
                  try {
                      Object o = iniCtx.lookup("ConnectionFactory");
                      connFactory = (ConnectionFactory) o;
      
                      msgDestination = (Destination) iniCtx.lookup(queue);
                  } catch (Exception e) {
                      throw new Exception(
                              "Error while performing lookup. Either JMS server is offline or lookup string(queue/topic) is not registered or misspelled.", e);
                  }
      
                  // ................................connect.............................
      
                  conn = connFactory.createConnection();
      
                  session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
      
                  session.run();
                  conn.start();
      
                  receiver = session.createConsumer(msgDestination);
      
                  ObjectMessage oMsg = (ObjectMessage) receiver.receiveNoWait();
      
                  if (oMsg == null)
                      return null;
      
                  msg = (String) oMsg.getObject();
      
                  connFactory = null;
      
              } finally {
      
                  if (receiver != null) {
                      receiver.close();
                      receiver = null;
                  }
                  if (session != null) {
                      session.close();
                      session = null;
                  }
                  if (conn != null) {
                      conn.close();
                      conn = null;
                  }
              }
              return msg;
          }
      
      
      

       

      Regards,

      Yelve

        • 1. Re: receiveNoWait doesn't work if parent is a daemon
          clebertsuconic

          I don't understand how your tests works, I don't see any threads being started?

           

           

          Isn't a SecurityManager a required condition? in that case that would make it a bug at the project you're using?

           

           

          Can you make it a valid testcase? Maybe you could extend either JMSTestBase (if you require JMS) or ServiceTestBase from our codebase.

          • 2. Re: receiveNoWait doesn't work if parent is a daemon
            clebertsuconic

            You could attach code here.

            • 3. Re: receiveNoWait doesn't work if parent is a daemon
              mainbrain

              The test I've posted has no dependencies, except HornetQ, so it should be possible to just run it. Starting threads inside the test is not necessary as the problematic thread is started in HornetQThreadFactory. The following snipped stems from the HornetQ sources. It shows that the thread is only spawned in a thread group if there is no security manager. So if there is a security manager, the thread group is not used and the problem does not show up.

               

               

               

              
                 public Thread newThread(final Runnable command)
                 {
                    final Thread t;
                    // attach the thread to a group only if there is no security manager:
                    // when sandboxed, the code does not have the RuntimePermission modifyThreadGroup
                    if (System.getSecurityManager() == null)
                    {
                       t = new Thread(group, command, "Thread-" + threadCount.getAndIncrement() + " (group:" + group.getName() + ")");
                    }
                    else
                    {
                       t = new Thread(command, "Thread-" + threadCount.getAndIncrement());
                    }
              
                    AccessController.doPrivileged(new PrivilegedAction<Object>()
                    {
                       public Object run()
                       {
                          t.setDaemon(daemon);
                          t.setPriority(threadPriority);
                          return null;
                       }
                    });
              
                    try
                    {
                       AccessController.doPrivileged(new PrivilegedAction<Object>()
                       {
                          public Object run()
                          {
                             t.setContextClassLoader(tccl);
                             return null;
                          }
                       });
                    }
                    catch (java.security.AccessControlException e)
                    {
                       log.warn("Missing privileges to set Thread Context Class Loader on Thread Factory. Using current Thread Context Class Loader");
                    }
              
                    return t;
                 }
              
              
              
              • 4. Re: receiveNoWait doesn't work if parent is a daemon
                clebert.suconic

                Can you attach a runnable test? So far I will have to guess the blanks... and it will be much faster if you provide a runnable test:

                 

                 

                http://community.jboss.org/wiki/HowToReportABugIssue

                • 5. Re: receiveNoWait doesn't work if parent is a daemon
                  mainbrain

                  Yes, I've read the FAQ. My original post contains a runnable test. Edit hornetq-jms.xml to contain "queue/test", start JBoss, run the test I've copy pasted.

                   

                  The test will fail as it is. But if you uncomment the first line of code

                  "Thread.currentThread().getThreadGroup().setDaemon(true)"

                  the test will succeed.

                   

                  Edit:

                  I just retried my pasted code to see what your problem might be. Then I realized that I've failed to post two variables.

                   

                      final String JBOSS_HOST = "localhost";
                      final String JBOSS_PORT = "1099";

                  Sorry for the confusion.

                   

                  I can also post the imports, which are:

                   

                   

                  import java.util.Hashtable;
                  
                  import javax.jms.Connection;
                  import javax.jms.ConnectionFactory;
                  import javax.jms.Destination;
                  import javax.jms.MessageConsumer;
                  import javax.jms.MessageProducer;
                  import javax.jms.ObjectMessage;
                  import javax.jms.Session;
                  import javax.naming.Context;
                  import javax.naming.InitialContext;
                  import javax.naming.NamingException;
                  
                  import org.junit.Test;
                  
                  • 6. Re: receiveNoWait doesn't work if parent is a daemon
                    clebert.suconic

                    Why is so difficult to attach a test here instead?

                     

                     

                    Use Advanced editor -> attach your test.

                    • 7. Re: receiveNoWait doesn't work if parent is a daemon
                      mainbrain

                      Sorry, I didn't know it's possible to attach the actual file.

                       

                      For your convenience I've made a maven2 project out of it. The test calls receiveNoWait() from within a daemon ThreadGroup. It takes about 2 minutes to make sure that ThreadGroup.destroy() is called before the second call to receiveNoWait(). Which happens after 1 minute of inactivity on my machine. That could vary on yours.

                       

                      Just start JBoss 6 and run "mvn clean test" in your command line.

                       

                      Also you have to manually comment out the first line of the test and restart everything to see the difference. That line sets the current ThreadGroup to a daemon. I was not able to make two tests as they would influence each other.

                       

                      Hope that helps

                      • 8. Re: receiveNoWait doesn't work if parent is a daemon
                        mainbrain

                        Can I do anything else to make myself heard?

                        • 9. Re: receiveNoWait doesn't work if parent is a daemon
                          clebert.suconic

                          We have limited resources here... first thing first serve.

                           

                           

                          you can open a JIRA, and we will prioritize it accordingly.