3 Replies Latest reply on Nov 9, 2005 8:23 AM by dimitris

    TimerImpl problems

    espenalb10

      We are having problems with the JBoss TimerService. We upgraded to JBoss 4.0.1sp1 to see if the Timer fixes there solves anything

      Our issues are:
      1 ? Timers are removed when doing a ?nice? shutdown.
      We fixed this by implementing our own ScapEJBTimerServiceImpl, see code below. This seems to work fine.

      Is there a better/more correct way? (I would rather not modify JBoss functionality?.)

      2 ? Timer.cancel in Transaction
      CartBean implements TimedObject, all methods have TransactionType=Required, using ContainerManaged Persistence and Transactions
      addItem restarts idleTimer (Timer.cancel, createNewTimer)
      ejbTimeout is then only called if customer is idle and marks the cart as timed-out. (Note: No rollback is performed)

      The problem is that Timer.cancel put?s the Timer in state CANCEL_IN_TX. The Timer is then never deleted (killTimer is not called, the TIMERS table entry is not removed, and the TimerTask is taking CPU time.) On server reboot, all the cancelled timers are activated, since TimerState is not entered into the TIMERS table.

      One solution is to modify the TimerImpl to call killTimer in TimerTask.run if Timer is cancelled ? this is made cumbersome because all fields in TimerImpl are private, not protected

      Is there another way? (I suspect a better use of Transactions ? my understanding of how they are meant to be used is limited.)

      3 ? Timer without Transaction
      We also have a Status poll timer. After we set TransactionType to Never, it works as we want, and is removed from the database when cancelled.
      The problem is that a Timer without a Transaction gives a very noisy log ? because of the log.warn statement in TimerTaskImpl.run:
      WARN [TimerImpl] Timer was not registered with Tx, reseting state: [id=5target=[target=jboss.j2ee:jndiName=ejb/ScapServerAdminLocal,service=EJB],remaining=9026,periode=10000,in_timeout]

      Again I could simply remove the statement from a derived class, but this is cumbersome because the class is private, not protected

      4 ? Duplicate primary key
      Because the TargetID is only 80 bytes, it is truncated ? easily fixed by modifying ScapEJBTimerServiceImpl, but maybe you have a better solution?

      5 ? Invalid timers not removed
      If I redeploy/undeploy my beans, the Timers are still there, attempting to call invalid references. (May be related to 1 above I guess)
      Would it not be appropriate to kill the Timer if an exception is thrown?

      I hope you can shed some light on this ? we have relied on ejbTimeout several places in the business logic, and do not want to redesign if we can avoid it.

      ScapEJBTimerServiceImpl :
      public class ScapEJBTimerServiceImpl extends EJBTimerServiceImpl
      {
       private Logger log = Logger.getLogger(ScapEJBTimerServiceImpl.class);
      
       /* (non-Javadoc)
       * @see org.jboss.ejb.txtimer.EJBTimerServiceImpl#removeTimerService(javax.management.ObjectName, java.lang.Object)
       */
       public void removeTimerService(ObjectName containerId, Object instancePk)
       {
       if (instancePk!=null)
       super.removeTimerService(containerId, instancePk);
       else
       log.info("Request to remove all Timers ignored.");
       }
      }


        • 1. TimerImpl problems - bug report
          espenalb10

          JBoss 4.0.1sp1 TimerImpl does not properly kill the TimerTask if Timer.cancel is called inside a transaction.
          Test code to reprduce:

          /**
           * @Package: com.scap.servers.scapserver.ejb
           * @CreateDate Apr 15, 2005
           * @Creator eha
           *
           * TODO Add more info
           */
          package com.scap.servers.scapserver.ejb;
          
          import java.io.Serializable;
          import java.rmi.RemoteException;
          import java.util.Collection;
          import java.util.Iterator;
          import java.util.logging.Logger;
          
          import javax.ejb.CreateException;
          import javax.ejb.EJBException;
          import javax.ejb.SessionBean;
          import javax.ejb.SessionContext;
          import javax.ejb.TimedObject;
          import javax.ejb.Timer;
          
          /**
           * XDoclet-based session bean. The class must be declared
           * public according to the EJB specification.
           *
           * To generate the EJB related files to this EJB:
           * - Add Standard EJB module to XDoclet project properties
           * - Customize XDoclet configuration for your appserver
           * - Run XDoclet
           *
           * Below are the xdoclet-related tags needed for this EJB.
           *
           * @ejb.bean name="TimerTest"
           * display-name="Name for TimerTest"
           * description="Description for TimerTest"
           * jndi-name="ejb/TimerTest"
           * type="Stateless"
           * view-type="both"
           *
           * @ejb.transaction type = "NotSupported"
           */
          public class TimerTestBean implements SessionBean, TimedObject
          {
          
           /** The session context */
           private SessionContext m_context;
           private Logger m_log = Logger.getLogger(TimerTestBean.class.getName());
          
           /**
           *
           */
           public TimerTestBean()
           {
           super();
           // TODO Auto-generated constructor stub
           }
          
           /**
           * Set the associated session context. The container calls this method
           * after the instance creation.
           *
           * The enterprise bean instance should store the reference to the context
           * object in an instance variable.
           *
           * This method is called with no transaction context.
           *
           * @throws EJBException Thrown if method fails due to system-level error.
           */
           public void setSessionContext(SessionContext newContext) throws EJBException
           {
           m_context = newContext;
           }
          
           /* (non-Javadoc)
           * @see javax.ejb.SessionBean#ejbRemove()
           */
           public void ejbRemove() throws EJBException, RemoteException
           {
           // TODO Auto-generated method stub
          
           }
          
           /* (non-Javadoc)
           * @see javax.ejb.SessionBean#ejbActivate()
           */
           public void ejbActivate() throws EJBException, RemoteException
           {
           // TODO Auto-generated method stub
          
           }
          
           /* (non-Javadoc)
           * @see javax.ejb.SessionBean#ejbPassivate()
           */
           public void ejbPassivate() throws EJBException, RemoteException
           {
           // TODO Auto-generated method stub
          
           }
          
           /**
           * An ejbCreate method as required by the EJB specification.
           *
           * The container calls the instance?s <code>ejbCreate</code> method whose
           * signature matches the signature of the <code>create</code> method invoked
           * by the client. The input parameters sent from the client are passed to
           * the <code>ejbCreate</code> method. Each session bean class must have at
           * least one <code>ejbCreate</code> method. The number and signatures
           * of a session bean?s <code>create</code> methods are specific to each
           * session bean class.
           *
           * @throws CreateException Thrown if method fails due to system-level error.
           *
           * @ejb.create-method
           *
           */
           public void ejbCreate() throws CreateException
           {
           // TODO Add ejbCreate method implementation
           }
          
           private void startTimer(Serializable info)
           {
           m_log.info("Starting timer. Info="+info);
           m_context.getTimerService().createTimer(10000,info);
           }
          
           /**
           * @ejb.interface-method
           * @ejb.transaction type = "Required"
           * @throws EJBException Thrown if method fails due to system-level error.
           */
           public void startTimerInTxRequired() throws EJBException
           {
           startTimer("Required");
           }
          
           /**
           * @ejb.interface-method
           * @ejb.transaction type = "RequiresNew"
           * @throws EJBException Thrown if method fails due to system-level error.
           */
           public void startTimerInTxRequiresNew() throws EJBException
           {
           startTimer("Required");
           }
           /**
           * @ejb.interface-method
           * @ejb.transaction type = "Never"
           * @throws EJBException Thrown if method fails due to system-level error.
           */
           public void startTimerInTxNever() throws EJBException
           {
           startTimer("Never");
           }
          
           /**
           * @ejb.interface-method
           * @ejb.transaction type = "NotSupported"
           * @throws EJBException Thrown if method fails due to system-level error.
           */
           public void startTimerInTxNotSupported() throws EJBException
           {
           startTimer("NotSupported");
           }
          
           private void cancelTimers()
           {
           Collection timers = m_context.getTimerService().getTimers();
           for (Iterator it = timers.iterator();it.hasNext();)
           {
           Timer t = (Timer)it.next();
           m_log.info("Cancelling timer"+t+" "+t.getInfo());
           t.cancel();
           m_log.info("Timer is now"+t);
           }
           }
          
           /**
           * @ejb.interface-method
           */
           public int listAllTimers()
           {
           Collection timers = m_context.getTimerService().getTimers();
           String s="Timers:";
           for (Iterator it = timers.iterator();it.hasNext();)
           {
           Timer t = (Timer)it.next();
           s = s +t.toString() + " ";
           try
           {
           s += t.getInfo();
           }
           catch (Exception e)
           {
          
           }
           s+="\n";
           }
           m_log.info(s);
           return timers.size();
           }
           /**
           * @ejb.interface-method
           * @ejb.transaction type = "Required"
           * @throws EJBException Thrown if method fails due to system-level error.
           */
           public void cancelTimerInTxRequired() throws EJBException
           {
           cancelTimers();
           }
          
           /**
           * @ejb.interface-method
           * @ejb.transaction type = "RequiresNew"
           * @throws EJBException Thrown if method fails due to system-level error.
           */
           public void cancelTimerInTxRequiresNew() throws EJBException
           {
           cancelTimers();
           }
           /**
           * @ejb.interface-method
           * @ejb.transaction type = "Never"
           * @throws EJBException Thrown if method fails due to system-level error.
           */
           public void cancelTimerInTxNever() throws EJBException
           {
           cancelTimers();
           }
          
           /**
           * @ejb.interface-method
           * @ejb.transaction type = "NotSupported"
           * @throws EJBException Thrown if method fails due to system-level error.
           */
           public void cancelTimerInTxNotSupported() throws EJBException
           {
           cancelTimers();
           }
          
           /* (non-Javadoc)
           * @see javax.ejb.TimedObject#ejbTimeout(javax.ejb.Timer)
           */
           public void ejbTimeout(Timer timer)
           {
           m_log.info("ejbTimeout:"+timer);
           }
          
          }

          JUnit Test:
          /**
           * @Package: com.scap.servers.scapserver.ejb
           * @CreateDate Apr 15, 2005
           * @Creator eha
           *
           * TODO Add more info
           */
          package com.scap.servers.scapserver.ejb;
          
          import java.rmi.RemoteException;
          import java.util.Hashtable;
          
          import javax.naming.Context;
          import javax.naming.InitialContext;
          import javax.rmi.PortableRemoteObject;
          
          import junit.framework.TestCase;
          
          import com.scap.servers.scapserver.interfaces.TimerTest;
          import com.scap.servers.scapserver.interfaces.TimerTestHome;
          
          /**
           * EJB Test Client
           */
          public class TimerTestClient extends TestCase
          {
          
           /**
           *
           */
           public TimerTestClient()
           {
           super();
           // TODO Auto-generated constructor stub
           }
          
           /**
           * @param name
           */
           public TimerTestClient(String name)
           {
           super(name);
           // TODO Auto-generated constructor stub
           }
          
           /** Home interface */
           protected TimerTestHome home;
           private static final int REQUIRED = 0;
           private static final int REQUIRES_NEW = 1;
           private static final int NOT_SUPPORTED = 2;
           private static final int NEVER = 3;
           private static final int FIRST = REQUIRED;
           private static final int LAST = NEVER;
           private static final String[] TRANSTYPE =
           { "Required", "RequiresNew", "NotSupported", "Never" };
          
           /**
           * Get the initial naming context
           */
           protected Context getInitialContext() throws Exception
           {
           Hashtable props = new Hashtable();
           props.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
           props.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
           props.put(Context.PROVIDER_URL, "jnp://localhost:1099");
           Context ctx = new InitialContext(props);
           return ctx;
           }
          
           /**
           * Get the home interface
           */
           protected com.scap.servers.scapserver.interfaces.TimerTestHome getHome() throws Exception
           {
           Context ctx = this.getInitialContext();
           Object o = ctx.lookup("ejb/TimerTest");
           com.scap.servers.scapserver.interfaces.TimerTestHome intf = (com.scap.servers.scapserver.interfaces.TimerTestHome) PortableRemoteObject.narrow(o,
           com.scap.servers.scapserver.interfaces.TimerTestHome.class);
           return intf;
           }
          
           /**
           * Set up the test case
           */
           protected void setUp() throws Exception
           {
           this.home = this.getHome();
           }
          
           private void startTimer(TimerTest instance, int transType) throws RemoteException
           {
           int timersBefore = instance.listAllTimers();
           switch (transType)
           {
           case REQUIRED:
           instance.startTimerInTxRequired();
           break;
           case REQUIRES_NEW:
           instance.startTimerInTxRequiresNew();
           break;
           case NOT_SUPPORTED:
           instance.startTimerInTxNotSupported();
           break;
           case NEVER:
           instance.startTimerInTxNever();
           break;
           }
           int timersAfter = instance.listAllTimers();
           if (timersAfter != timersBefore + 1)
           {
           fail("Did not delete timer!\nBefore:" + timersBefore + "\nAfter:" + timersAfter);
           }
           }
          
           private void cancelTimer(TimerTest instance, int transType)
           {
           try
           {
           int timersBefore = instance.listAllTimers();
           switch (transType)
           {
           case REQUIRED:
           instance.cancelTimerInTxRequired();
           break;
           case REQUIRES_NEW:
           instance.cancelTimerInTxRequiresNew();
           break;
           case NOT_SUPPORTED:
           instance.cancelTimerInTxNotSupported();
           break;
           case NEVER:
           instance.cancelTimerInTxNever();
           break;
           }
           int timersAfter = instance.listAllTimers();
           if (timersBefore != timersAfter + 1)
           {
           fail("Did not delete timer!\nBefore:" + timersBefore + "\nAfter:" + timersAfter);
           }
           }
           catch (Exception e)
           {
           System.out.println("Failed to cancel timer:" + e);
           }
           }
          
           /**
           * Test for com.scap.servers.scapserver.interfaces.TimerTest.startTimerInTxRequired()
           */
           public void testTimer() throws Exception
           {
           com.scap.servers.scapserver.interfaces.TimerTest instance;
          
           // Parameters
          
           // Instance creation
           instance = this.home.create();
           int run = 0;
           for (int start = FIRST; start <= LAST; start++)
           {
           for (int cancel = FIRST; cancel <= LAST; cancel++)
           {
           System.out.println("Run " + run + " start:" + TRANSTYPE[start] + " cancel:" + TRANSTYPE[cancel]);
           startTimer(instance, start);
           cancelTimer(instance, cancel);
           }
          
           }
           }
          }


          • 2. Re: TimerImpl problems
            dimitris

            While working on http://jira.jboss.com/jira/browse/JBAS-1091 I came across most of the issues you've mentioned. Hopefully, most of them will be resolved in the next point/release candidate release.

            • 3. Re: TimerImpl problems
              dimitris

              JBAS-1960, Timer.cancel in a transaction never calls TimerImpl.killTimer has been solved