Did you know WebLogic Server can schedule future tasks similar to a cron job? This even works in a WebLogic cluster. Using a cluster ensures that as long as one of the managed servers in the cluster is available, your task has a high-availability characteristic and the task will execute. This has come up for at least 3 of my local customers. Every once in awhile I see questions about WebLogic timers from my peers, so I thought I’d share a simple example for how this works and some extra tidbits based on personal experience that might not be in the standard documentation. Read on if you want to see how to schedule a simple println() task to execute every 30 seconds in a cluster that can survive server failures in both the WebLogic tier and the database tier (provided you use Oracle RAC).
Background
Oracle (then BEA Systems) and IBM collaborated on a common API for Timers and Work Managers (think multi-threading) that would be portable across application servers. They aptly named this API CommonJ. This API has been supported in WebLogic Server since 9.x and apparently in WebSphere since 6.x. When combined with WebLogic clustering support, the CommonJ Timer API is known as the Job Scheduler. It requires some configuration as described in the docs, but essentially the way it works under the covers is by serializing the class that will execute the task and some related metadata to a database. Using the leasing feature of WebLogic Server, exactly one cluster member will be responsible for JobScheduler (singleton service) which executes the Timer Tasks. The server responsible for this service will check the database every 30 seconds to see if there are new tasks to execute.
Instructions for the System Out Example Timer
These instructions assume starting from scratch with nothing but a basic WebLogic Server installation and a database like Oracle XE. I run all of this easily on my 2+ yr old laptop (I do have 3+ gig of ram). The documentation is more detailed now, so definitely refer to that as well.
- Create a new domain with 1 Admin Server and at least one Managed Server that will be a part of a cluster. Refer to the Oracle By Example page for WLS if you need help with this, but you should be able to do all of this from the Configuration Wizard. I named my cluster “myCluster” and my managed servers “managedServer1” and “managedServer2”.
- Set up the database – Basically run the DDL for your DB, which will create a table called
WEBLOGIC_TIMERS. If you don’t set up database often, here is my simple POC sequence with Oracle XE. In production, I would recommend Oracle RAC with a Multi Data Source, but you can POC this with an XE database or a single database node if you can live with a single point of failure. Open the XE database web console and create a new database user, of course I use weblogic/weblogic for the user name password and give them admin rights. Start sqlplus and login as that user. Navigate to the directory that contains the DDL you want to run and use the @ syntax to execute the DDL like this. You can of course ignore the messages about the comments not being valid commands:
- Keep the sql prompt open, we’ll use it again in a minute. Notice that the script is in the oracle\920 directory, that simply means for 9.2+ of Oracle DB, it works totally fine for me on XE 10g.
- Configure the data source – Create a new data source for this and target it at all the servers in your domain (or at least the ones in your cluster) and specify it as the cluster’s JobScheduler Data Source. I recommend that you test your data source when you are creating it.
- Set up leasing – You can use consensus or database leasing, but since we already using the DB for the Job Scheduler, just do database leasing which creates a table called ACTIVE (at least in the recent releases of WLS, it used to be something like WEBLOGIC_ACTIVE in earlier releases). This means running the appropriate
WL_HOME/server/db/dbname/leasing.ddlas well as configuring the data source, which can be the same one you used before, and configuring the cluster to use that data source for leasing. Make sure you note database based leasing requires a highly-available database. If the connection goes away, the managed servers will fail. For this reason you can use multi data sources with Oracle RAC to get an even more HA architecture, but Oracle XE works fine for a POC. I got consensus leasing to work at one time in the past using WLS 9.2, but if I remember correctly I had to install Node Manager and configure “machines” for my servers for it to work. So DB-based leasing is what I’m using in this example because it’s simpler and I have not had issues with it before.
- Write some code – There is a sample in the docs showing how to look up the job scheduler and schedule a task. I’ll take care of that for you, here is a WAR (Servlet 2.5 based) that has code to register the task via a servlet and a JAR file that contains the class that implements the TimerListener interface. The source code is at the way bottom of this post.
- Deploy the WAR file, target it at the cluster, and make sure the application is started.
- Edit your server classpath to contain the timer implementation class which is in the JAR referenced earlier. You might be asking yourself, why the heck does the timer listener class need to be placed on the system classpath?!! Well if you think about how the classloaders work, the singleton service running the tasks does not have access to the classes from the JEE module that registered the timer. It is running as a WebLogic system thread. This class will be deserialized from the database, potentially on another server. So I recommend not doing anything too sophisticated in the timer listener. If you have lots of logic with multiple classes, just consider having the timer listener drop a message on a distributed JMS queue, which can have an MDB from another application that does all of your heavy lifting. Back to putting the TimerListener implementation on the classpath, I accomplished this by added the following line to my domain’s setDomainEnv.cmd script. There is also the option to use an EJB Timer, which apparently does not have the system classpath requirement, but I haven’t tried that before.
set PRE_CLASSPATH=C:\JDeveloper\mywork\JobSchedulerApp\TimerListener\deploy\TimerListener.jar
- Optional – Enable verbose logging for the related sub-systems, I find this helpful when troubleshooting.
set JAVA_OPTIONS=%JAVA_OPTIONS% -Dweblogic.debug.DebugSingletonServices=true -Dweblogic.JobScheduler=true
- Restart your Admin Server and Managed Server. Make sure you see the JAR in the classpath settings in the log file.
- If your enabled the additional logging, you should see that leasing and job scheduler are creating log entries like the following in the managed server log file (not in standard out!).
- Register the timer by hitting the URL or invoking the web service of one of the managed servers. In my case that is http://localhost:7011/TimerWeb/timerservlet. Note that each time you invoke the URL successfully a new timer listener will be registered with the cluster, so you probably only want to do this once.
- Notice the messages in standard out if this is working correctly.
- Shut down the managed server that has the messages printing to standard out. You should see the messages show up on another managed server in the cluster in standard out.
- Now shut down all of the managed servers and restart at least one.
- You should see the timer task start printing to standard out when one of the managed servers in the cluster is available.
####<Apr 8, 2009 3:10:01 PM CDT> <Debug> <SingletonServices> <jbayer-lap> <managedServer1> <[ACTIVE] ExecuteThread: '1' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1239221401078> <BEA-000000> <SingletonMonitor: Checking Failed Leases>
####<Apr 8, 2009 3:10:11 PM CDT> <Debug> <SingletonServices> <jbayer-lap> <managedServer1> <[ACTIVE] ExecuteThread: '2' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1239221411078> <BEA-000000> <SingletonMonitor: Now checking lease statuses.>
####<Apr 8, 2009 3:10:11 PM CDT> <Debug> <SingletonServices> <jbayer-lap> <managedServer1> <[ACTIVE] ExecuteThread: '2' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1239221411078> <BEA-000000> <SingletonMonitor: Checking for registered Migratable Targets and Singleton Services without a lease>
####<Apr 8, 2009 3:10:11 PM CDT> <Debug> <SingletonServices> <jbayer-lap> <managedServer1> <[ACTIVE] ExecuteThread: '2' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1239221411078> <BEA-000000> <SingletonMonitor: TimerMaster - Found an owner - -7090657857495109585/managedServer1>
####<Apr 8, 2009 3:10:11 PM CDT> <Debug> <SingletonServices> <jbayer-lap> <managedServer1> <[ACTIVE] ExecuteThread: '2' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1239221411078> <BEA-000000> <SingletonMonitor: Checking existant, but expired leases>
####<Apr 8, 2009 3:10:11 PM CDT> <Debug> <SingletonServices> <jbayer-lap> <managedServer1> <[ACTIVE] ExecuteThread: '2' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1239221411078> <BEA-000000> <SingletonMonitor: Checking Failed Leases>
####<Apr 8, 2009 3:10:21 PM CDT> <Debug> <SingletonServices> <jbayer-lap> <managedServer1> <[ACTIVE] ExecuteThread: '0' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1239221421078> <BEA-000000> <SingletonMonitor: Now checking lease statuses.>
####<Apr 8, 2009 3:10:21 PM CDT> <Debug> <SingletonServices> <jbayer-lap> <managedServer1> <[ACTIVE] ExecuteThread: '0' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1239221421078> <BEA-000000> <SingletonMonitor: Checking for registered Migratable Targets and Singleton Services without a lease>
####<Apr 8, 2009 3:10:21 PM CDT> <Debug> <SingletonServices> <jbayer-lap> <managedServer1> <[ACTIVE] ExecuteThread: '0' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1239221421078> <BEA-000000> <SingletonMonitor: TimerMaster - Found an owner - -7090657857495109585/managedServer1>
####<Apr 8, 2009 3:10:21 PM CDT> <Debug> <SingletonServices> <jbayer-lap> <managedServer1> <[ACTIVE] ExecuteThread: '0' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1239221421078> <BEA-000000> <SingletonMonitor: Checking existant, but expired leases>
####<Apr 8, 2009 3:10:21 PM CDT> <Debug> <SingletonServices> <jbayer-lap> <managedServer1> <[ACTIVE] ExecuteThread: '0' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1239221421078> <BEA-000000> <SingletonMonitor: Checking Failed Leases>
timerExpired() called at 4/8/09 3:12 PM
timerExpired() called at 4/8/09 3:13 PM
Summary
So you should have a working timer example in WebLogic Server that survives failures. The debug messages in the managed server log files can be helpful in trouble-shooting in case anything goes wrong. A couple other things to note, you cannot cancel timer tasks via CommonJ API calls when running in the JobScheduler. You have to use the JobRuntimeMBean for that. I’ll show an example of using this bean with WLST in another entry. Here is the source code for the servlet and the timer listener classes. Hopefully this will help you utilize this powerful capability built-in to WebLogic.
package view.jamesbayer;import commonj.timers.TimerManager;import jamesbayer.client.SystemOutTimerListener;import java.io.IOException;import java.io.PrintWriter;import javax.naming.InitialContext;import javax.servlet.*;import javax.servlet.http.*;public class TimerServlet extends HttpServlet {public void init(ServletConfig config) throws ServletException {super.init(config);}public void service(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException {PrintWriter out = response.getWriter();out.println("<html>");out.println("<head><title>TimerServlet</title></head>");try {System.out.println("service() entering try block to intialize the timer from JNDI");InitialContext ic = new InitialContext();TimerManager jobScheduler =(TimerManager)ic.lookup("weblogic.JobScheduler");System.out.println("jobScheduler reference " + jobScheduler);commonj.timers.TimerListener timerListener =new SystemOutTimerListener();System.out.println("timerListener reference " + timerListener);jobScheduler.schedule(timerListener, 0, 30 * 1000);//execute this job every 30 secondsSystem.out.println("service() started the timer");out.write("Started the timer - status:");} catch (Throwable t) {System.out.println("service() error initializing timer manager with JNDI name weblogic.JobScheduler " + t);}out.println("</body></html>");out.close();}}
package jamesbayer.client;import java.io.Serializable;import java.text.SimpleDateFormat;import java.util.Date;import commonj.timers.Timer;import commonj.timers.TimerListener;public class SystemOutTimerListener implements Serializable, TimerListener{private static final long serialVersionUID = 8313912206357147939L;public void timerExpired(Timer timer){SimpleDateFormat sdf = new SimpleDateFormat();System.out.println( "timerExpired() called at " + sdf.format( new Date() ) );}}

Comments (19)
Excellent and useful article !
Thanks James for sharing this :)
Posted by Maxence Button | April 9, 2009 1:16 AM
Posted on April 9, 2009 01:16
great. your entry come in just in time.
i was reading around for a scheduler of higher availability than the standard unix cron when i come across your Dec 2007 posting. in that posting, you mentioned that you will keep the wls cluster version for a later posting, and well, i was so fortunate that you post it finally after over a year.
thanks for the in-depth sharing.
Posted by beng seng | April 9, 2009 1:40 AM
Posted on April 9, 2009 01:40
Max and Beng,
It's been in my blog folder for a long time! I'm glad that I finally made the time for it. Thanks for the feedback because it does take some time to put this together, and knowing that others are benefiting from it is a big motivator.
Cheers,
James
Posted by James Bayer | April 9, 2009 4:40 AM
Posted on April 9, 2009 04:40
Thanks for the great article. Is there another way to start off the job other than manually hitting a URL on only one of the managed servers to invoke the servlet? Also, has anyone seen any good examples (like this one) using EJB Timers?
Posted by Andy H | May 29, 2009 6:29 AM
Posted on May 29, 2009 06:29
Andy,
Regarding initializing a job, you have to be prudent about where this goes because if you think about it, job registration is separate from the long-running job itself. If I put it somewhere like Servlet.init() or when I activate the web application, then every time I restart the server a new job would be registered! Therefore the right approach is probably a separate web application (or ejb etc, something in the container) that is responsible for creating/managing jobs. I just used a servlet as a simple placeholder for something more sophisticated.
I'm not aware of any EJB 3 Timer demos, but I am on the lookout for them.
Thanks,
James
Posted by James Bayer | May 29, 2009 8:01 AM
Posted on May 29, 2009 08:01
James,
thanks for the interesting article. It covers almost exactly what I was looking for.
I tried to make this setup work with an EJB timer and (sort of) succeeded: I used the above configuration, i.p. the two DB tables in the datasource the app server was using already and "MyDataSource" in config.xml (in /domain/cluster). In the EAR I set "Clustered" in weblogic-ear-jar.xml.
Now, on an injected "@Resource private EJBContext ejbCtx;" in a stateless session bean I called ctx.getTimerService().createTimer() to set up the timer which triggers the business method annotated by @Timeout in the same bean every time the timer expires.
All of this works well including failover when taking down one of the cluster nodes ... once it works.
The problem I am experiencing is start up: When I first start the application calling any Timer API methods results in a BEA internal NullPointerException. From the log it is clear the the TimerMaster SingletonService is not yet running. Once I restart the servers (changing state from ADMIN through RESUME to ACTIVE) the TimerMaster is started and the timers work. Unfortunately, after shut down and restart the problem occurs again.
Have you ever encountered this issue? From the log I don't think it is EJB specific but rather related to the way SingletonServices in general and the TimerMaster in particular is configured and started.
Cheers,
Mathias
Posted by Mathias Kegelmann | June 2, 2009 3:38 AM
Posted on June 2, 2009 03:38
Mathias,
I have not encountered this issue before, but that's probably because I haven't tried the EJB Timers. So it sounds like you are using the server's ADMIN state to test your applications on the ADMIN channel or otherwise, yet that mode may be incompatible with running Singleton Services in the cluster, which makes sense to me given some thought. I don't know enough about all the moving parts here, so I suggest working with Oracle Support to see if that theory is correct or if there is another explanation.
Thanks,
James
Posted by James Bayer | June 2, 2009 5:41 AM
Posted on June 2, 2009 05:41
Thanks for the great article. Based on this I have started using commonj for scheduling tasks, but I am using local timers. I am facing one problem. The security credentials associated with the thread which scheduled the TimerListener are somehow passed to TimerListener and is being used for the work done in TimerListener. I have read the API document and it looks like is the expected behavior. Is there a way to avoid this?
Thanks,
Laxman
Posted by Laxman | June 25, 2009 10:52 AM
Posted on June 25, 2009 10:52
Laxman,
My first intuition is that this is working-as-designed with respect to security. I've asked someone on the team.
My 2 seconds of thought ideas is that if you want to separate the user from the timer, you could probably put a layer in-between with JMS and an MDB. So the user request would send a JMS message, the MDB would pick it up without a security context, and initialize a timer without the user information. This is just my 2 seconds of thought idea, not well thought through. Good luck,
James
Posted by James Bayer | June 25, 2009 11:47 AM
Posted on June 25, 2009 11:47
James,
Thanks for the great tutorial. We're considering using the Job Scheduler as well. Do you know if it has the same limitations as EJB Clustered Timers with regards to being intended only for "coarse-grained operations".
My Google searches did not turn up any limitations with Job Scheduler, so is it safe to assume that we can use it to create several thousand timers, each timer associated to a specific entity or object?
Thanks,
Rody
Posted by Rody Bagtes | August 17, 2009 3:02 PM
Posted on August 17, 2009 15:02
Actually, it's my understanding that they (JobScheduler and EJB 3 Clustered timers) use the same underlying framework, so I'm not sure they were designed to support thousands of timers. Perhaps you could send me your use case and I can run it by one of the product managers.
Thanks, James
Posted by james.bayer
|
August 17, 2009 4:21 PM
Posted on August 17, 2009 16:21
Hi James,
Your blog is just superb. I am also implementing Job Scheduler for Weblogic 10.3.
I am facing the below problem while deploying my application:
BEA-000180 Unable to retrieve Job Cluster_MS2_1251204736672 from the data
base. The retrieval failed with java.io.IOException: mydomain.timer.MyTimerListener
at weblogic.scheduler.ObjectPersistenceHelper.getObject(ObjectPersistenceHelper.java:94)
at weblogic.scheduler.ObjectPersistenceHelper.getObject(ObjectPersistenceHelper.java:82)
at weblogic.scheduler.ObjectPersistenceHelper.getObject(ObjectPersistenceHelper.java:227)
at weblogic.scheduler.DBTimerBasisImpl.getTimerState(DBTimerBasisImpl.java:238)
at weblogic.scheduler.TimerExecutor.timerExpired(TimerExecutor.java:164)
at weblogic.timers.internal.TimerImpl.run(TimerImpl.java:273)
at weblogic.work.SelfTuningWorkManagerImpl$WorkAdapterImpl.run(SelfTuningWorkManagerImpl.java:516)
at weblogic.work.ExecuteThread.execute(ExecuteThread.java:201)
at weblogic.work.ExecuteThread.run(ExecuteThread.java:173)
The timer-listener class implements Serializable interface too.
Thanks in advance.
Posted by Ankur Jain | August 25, 2009 5:02 AM
Posted on August 25, 2009 05:02
Ankur, I'm not sure just based on that stack. I would expect a Serializable or NotFound exception if the class was not on the classpath, but just in case validate that the TimerListener class is on the system classpath for all the servers in the cluster. Point 8 in the entry explains the classpath settings in some detail. If you're still stuck, then I suggest a Service Request with support as they can help you troubleshoot and find the issue.
James
Posted by James Bayer | August 25, 2009 6:10 AM
Posted on August 25, 2009 06:10
James,
Just to follow up, we eventually implemented the EJB Clustered Timer which as you know is implemented using the Job Scheduler. We're using the UDD Queue and JMS setDelayTime(). Everything is good, except for the startup. What's the best way to automatically start the EJB Clustered Timer in an EAR?
We're using the ApplicationLifecycleListener's postStart(). It calls an EJB, which in turn calls EJBContext.getTimerService(). But this does not work when starting up the domain, i.e.,
Start Domain
Cluster "Core" starts -> STARTING state
Core Server 1 starts, then RUNNING
Core Server 1 automatically starts App
App looks up Clustered Timer Server
-> Null Pointer exception (Cluster is still in STARTING state)
Core Server 2 Starts, goes to RUNNING.
All members of the cluster RUNNING
Cluster Core set to RUNNING
App state Failed
As you can see above, there is a cyclic dependency between the App and the Cluster Timer Service. The App is automatically started by server instance before the Cluster is RUNNING.
To solve this problem our startup and shutdown script automatically Stop the App before the domain is stopped. The App is then started after the domain starts. This way the Cluster is up before the App starts ensuring cluster services are available.
Is there a better way of doing this? It seems that Weblogic overlooked how the ApplicationLifecycleListener handles startup in a cluster. Maybe a postClusterStart() is needed?
Otherwise pretty cool stuff. I really like the UDD and setDeliveryTime().
Thanks,
Rody
Posted by Rody Bagtes | August 29, 2009 9:44 PM
Posted on August 29, 2009 21:44
Rody,
I'm not sure about an alternative to the scripting approach you took for the circular dependency in startup between clustered timers and the app lifecycle listener. I was thinking that perhaps you could also use the CommonJ WorkManager to schedule a piece of work with a semi-short call to Thread.sleep() that would then invoke the EJB, rescheduling the Work if the cluster was still not available, but that's a bit of a hack.
http://download.oracle.com/docs/cd/E12839_01/web.1111/e13733/toc.htm
I forwarded your comments to the Product Manager who owns the clustered timer to see if he has a comment and inform him about the perceived limitation to see if there are alternatives, etc.
Thanks for the follow-up!
Cheers, James
Posted by james.bayer
|
August 31, 2009 5:44 AM
Posted on August 31, 2009 05:44
Please help how can I configure the scheduler in weblogic 10. the link provided above is broken. I am failing at look up for weblogic.JobScheduler
Thanks
Posted by Dan | September 4, 2009 7:17 AM
Posted on September 4, 2009 07:17
Yes, unfortunately all the edocs documentations links are broken since that site has been decommissioned and all the old BEA documentation has moved to oracle.com domains.
It's pretty easy to find the right links however.
Simply take a doc link like this:
http://e-docs.bea.com/wls/docs103/commonj/commonj.html
And change the prefix before the docs103 portion to be
http://download.oracle.com/docs/cd/E12840_01/wls/
As an example:
http://download.oracle.com/docs/cd/E12840_01/wls/docs103/commonj/commonj.html
Good luck, James
Posted by james.bayer
|
September 4, 2009 7:49 AM
Posted on September 4, 2009 07:49
Great article! However I also experience the
"Null Pointer exception when the Cluster is still in STARTING state" on 10.3 when trying to initialize EJB timers during startup. Any news on this one? Is there an Oracle ticket we could refer to?
Many thanks
Posted by Alec | October 28, 2009 6:20 AM
Posted on October 28, 2009 06:20
Alec, I'm not aware of any tickets on this issue, although you might want to contact support as they have better search tools than I do. One thing you might do is wait for some configurable amount of time after application start-up to use the Job Scheduler, and possibly even explicitly check the status of the cluster before you call the API's. Good luck,
James
Posted by james.bayer
|
October 28, 2009 12:45 PM
Posted on October 28, 2009 12:45