25 June 2008

Scheduling OC4J periodic tasks using java.util.concurrent

A question on OTN last week raised the issue of how to fulfill a requirement to have a task run periodically within OC4J that did some form of directory read/processing ativity.

If take out the actual tasks that need to be performed for the moment, then what we are looking to do here is to run some form of activity on a repeated basis.

One way I thought to tackle this was to make use of the new facilities provided by the java.util.concurrent package. Particularly, this package has a ScheduledExecutorService which allows for tasks to be submitted that are configured to run on a repeated basis.

The ScheduledExecutorService has a method that enables tasks to run on a repeated basis with a fixed delay:

http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/ScheduledExecutorService.html#scheduleWithFixedDelay(java.lang.Runnable,%20long,%20long,%20java.util.concurrent.TimeUnit)

This method takes in a java.lang.Runnable, which encapsulates the steps that need to be performed when the periodic task runs.

It then takes in the data for how the task should be run -- initial delay, the fixed delay and the time unit.

Armed with this, we can create our task by implementing the java.lang.Runnable interface and schedule it for running using the ScheduledExecutorService.

A trivial example of a task for the purpose of testing this could look like this.
package sab.demo.susdtest;

import java.text.DateFormat;
import java.util.Date;

public class MyScheduledTask implements Runnable {

private DateFormat DF = DateFormat.getDateTimeInstance();

public MyScheduledTask() {
}

public void run() {
// Do some long winded thing to simulate "processing and
// provide a place to issue a ctrl+c and show what happens
System.out.printf("[%s] MyTask awake, doing something ....\n", DF.format(new Date()));
String ret = "";
int j=0;
for (int i=0;i<15000;i++) {
ret = ret + Integer.valueOf(i).toString();
j=i;
}
System.out.printf("[%s] MyTask finished processing [%s]....\n",
DF.format(new Date()),
j);
}
}
If we now consider that our task may be performing operations that have open handles to resources that will need to be handled carefully, the ScheduledExecutorService also has a method called shutdown() which will nicely shutdown the ScheduledExecutorService by cancelling all the pending tasks and ultimately calling interrupt on any currently executing task.

Now to wire this into OC4J so that the ScheduledExecutorService is created/started when OC4J starts and it shuts down when OC4J itself is stipped, then one readily apparent place to start is the use of the OC4JStartup and OC4JShutdown classes.

Using the OC4JStartup interface, a class can be created that performs the steps needed to create/start/schedule the task. The ScheduledExecutorService is bound into the Context provided to the class so that it can be accessed in other startup and shutdown classes that are called.
package sab.demo.susdtest;

import oracle.j2ee.server.OC4JStartup;
import java.util.Hashtable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.naming.Context;

public class Startup implements OC4JStartup {
private static final int INITIAL_DELAY = 10;
private static final int DELAY = 15;

public Startup() {
}

public String preDeploy(Hashtable hashtable,
Context context) throws Exception {

// Create a ScheduledExecutorService, schedule the task we want to
// regularly run and then bind it into context so it can be accessed
// by other OC4JStartup or OC4JShutdown classes. The delay parameters
// could be provided as config parameters and extracted from the
// hashtable
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleWithFixedDelay(
new MyScheduledTask(), INITIAL_DELAY, DELAY, TimeUnit.SECONDS);
context.bind("task", executor);
return null;
}

public String postDeploy(Hashtable hashtable,
Context context) throws Exception {
return null;
}
}
The OC4JShutdown class implementation class needs to extract the ScheduledExecutorService from the context, issue the shutdown call on it and wait for the service to terminate.
package sab.demo.susdtest;

import java.text.DateFormat;
import java.util.Date;
import java.util.Hashtable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.naming.Context;
import oracle.j2ee.server.OC4JShutdown;

public class Shutdown implements OC4JShutdown {

private static final int MAX_DELAY = 60;

DateFormat DF = DateFormat.getDateTimeInstance();

public Shutdown() {
}

public String preUndeploy(Hashtable hashtable,
Context context) throws Exception {

// Extract the ScheduledExecutorService from the context and ask it to
// shutdown, waiting for a max of 60 seconds for it to complete its task
ScheduledExecutorService executor =
(ScheduledExecutorService)context.lookup("task");

if(executor != null ) {
System.out.printf("[%s] Calling executor service shutdown\n", now());
executor.shutdown();
// The max wait time could be obtained as a parameter via the hashtable
System.out.printf("[%s] Waiting on executor service shutdown\n", now());
executor.awaitTermination(MAX_DELAY, TimeUnit.SECONDS);
System.out.printf("[%s] Done with executor service shutdown \n", now());
} else {
System.out.printf("[%s] No executor service found in context, " +
"no shutdown performed....\n", now());
}
return null;
}

public String postUndeploy(Hashtable hashtable,
Context context) throws Exception {
return null;
}

private String now() {
return DF.format(new Date());
}
}
With those classes created, compiled and packaged into a JAR file, you then configure OC4J to make use of them by adding entries into its server.xml file.
        <init-library path="D:\java\jdev-10133\jdev\mywork\susd-test\test\deploy\susdtest.jar" />
<startup-classes>
<startup-class classname="sab.demo.susdtest.Startup" failure-is-fatal="true" >
<execution-order>0</execution-order>
</startup-class>
</startup-classes>
<shutdown-classes>
<shutdown-class classname="sab.demo.susdtest.Shutdown" >
<execution-order>0</execution-order>
</shutdown-class>
</shutdown-classes>
Once all that has been completed, it's time to give it a field test.

Here we start OC4J, wait until the task begins its execution then shutdown OC4J. Note how the container waits for the task to complete before it completes its shutdown fully.
D:\java\oc4j-10133-prod\j2ee\home>java -jar oc4j.jar
08/06/25 11:42:15 Oracle Containers for J2EE 10g (10.1.3.3.0) initialized
[25/06/2008 11:42:21] MyTask awake, doing something ....
Shutting down OC4J...
[25/06/2008 11:42:32] Calling executor service shutdown
[25/06/2008 11:42:32] Waiting on executor service shutdown
[25/06/2008 11:42:35] MyTask finished processing [14999]....
[25/06/2008 11:42:35] Done with executor service shutdown
Here we start OC4J, allow the task to begin and complete, then signal OC4J to shutdown. Since there are no running tasks, the shutdown completes almost immediately and OC4J completes its full shutdown likewise.

D:\java\oc4j-10133-prod\j2ee\home>java -jar oc4j.jar
08/06/25 11:43:16 Oracle Containers for J2EE 10g (10.1.3.3.0) initialized
08/06/25 11:43:22 [25/06/2008 11:43:22] MyTask awake, doing something ....
[25/06/2008 11:43:37] MyTask finished processing [14999]....
Shutting down OC4J...
[25/06/2008 11:43:40] Calling executor service shutdown
[25/06/2008 11:43:40] Waiting on executor service shutdown
[25/06/2008 11:43:40] Done with executor service shutdown

1 comment:

Tony said...

Great stuff, thanks for posting this. i liked the messages that gave feedback while waiting on full shutdown to complete. When you are bouncing in a production situation, it is useful to have some idea of what is going on.