The Deferred Execution Service


J2EE has had a long-standing constraint that applications should not spawn their own threads. The reasons for this are generally well established – the presence of unmanaged threads in the application server can seriously undermine the ability of the application server to ensure a stable, optimized, and scalable execution environment. (See Chapter 2 for more discussions on multi-threading in an application server).

Prior versions of J2EE did not have a good mechanism for enforcing this constraint, and so it was left to the application developer to apply some diligence. With J2EE 1.3, WebSphere is now able to enforce this constraint by denying applications permission to the threads library (java.util.Thread – specifically, for those operations that can be used to start and terminate threads).

Nonetheless, WebSphere extends some leniency by allowing you to request additional permissions. Application administrators can audit this and decide whether to allow applications that request this additional permission to be deployed. The installation will still suffer the consequences of diminished management in the container, if it is deployed with these permissions.

Generally we do not encourage the use of multi-threading your EJBs. Nevertheless, and notwithstanding these constraints, as the sophistication of your application grows so will the need for multi-threading within your application. The most common need for multi-threading stems from scenarios where you need a degree of parallelism in your business processes – for example, performing a risk analysis while you gather product data for a customer account.

The Deferred Execution Service is a way to avoid the pitfalls of spawning your own threads with the threads library. The deferred execution service allows you the same benefits of spawning parallel threads of execution, but in a safe fashion – one that can be managed fully by the application server and, by extension, restoring the benefits of managed component application development and execution to applications that need to spawn parallel work.

Deferred execution may sound like a strange way of saying multi-threading. Think of it as a way of deferring the management of work to be executed outside of the main thread to the application server. Work scheduled through the deferred execution service can be executed in parallel (synchronized through the supported rendezvous mechanism – described later in Joining the Completion of Work), or entirely asynchronously (without any rendezvous).

The most important point about the deferred execution service, and the thing that differentiates it from the threads library, which is the source of so much concern for J2EE application services, is that the deferred work is managed. That is, the deferred work will execute in a full J2EE execution context under container control, and with context state derived from your main thread of work. You can program the deferred work using the fullness of the J2EE programming model. WebSphere will establish an execution context for the parallel work, monitor any threads created for it, and enable rendezvousing with the results of the work. The deferred work will execute on threads taken from the WebSphere thread pool, and thus avoid the overhead of having to create threads on the fly. Most importantly, WebSphere will account for the work that is performed in these threads in its workload distribution decisions, allowing for better utilization of the application server resources.

About the Deferred Execution Example

The example used in this section is composed of four parts:

  • DefexClient – is the main deferred execution client that drives the MainThread to perform various operations that demonstrate the use of the deferred execution service.

  • MainThread – this is a stateful session bean and represents the main thread of execution for the example. This bean is used to create work requests with the work manager, to create execution contexts for later execution, create a WorkListener and register those work requests, and to terminate outstanding work requests. The MainThread maintains a stack of outstanding work objects (one per asynchronous work request), and a vector of all requested work items (each representing a specific request for asynchronous work). These are used for book keeping and to demonstrate the benefits of monitoring work with a WorkListener.

  • MyDeferredWork – is an implementation of Work that is used in the entire asynchronous work request examples used here. The Work is implemented to simply write the current time out to the System.out stream each second for up to 20 seconds. You will have to look in the sysout log to see the results of this work.

  • MySynchWorkListener – is an implementation of WorkListener that is used to monitor the asynchronous work requests in some cases. This listener is implemented to listen only for work-completion requests and to remove the corresponding the Work from the outstanding work stack.

To run the example, you should install the Chapter11Sample application in the application server, and then invoke the client from a command line. From a command prompt:

  • Make sure that <WAS_INSTALL_ROOT>/bin is in your path

  • Invoke the following command:

     launchClient <WAS_INSTALL_ROOT>/installedapps/<nodename>/Chapter11Sample.ear               –CCjar=Chapter11SampleDefexClient.jar 

You will need to substitute <WAS_INSTALL_ROOT> with the path to where you've installed the WebSphere Application Server and <nodename> with the nodename you specified during installation (usually taken from your host name, by default).

Main Deferred Execution Components

The deferred execution service is composed of the following components:

  • WorkManager
    This forms the heart of the deferred execution service. It is through the work manager that you will dispatch asynchronous work. The WorkManager supports the following operations:

    • startWork() – is used to initiate asynchronous work. This operation is heavily overloaded to enable a variety of different ways of controlling the asynchronous work – including with the current execution state or execution state that was captured previously (see the create operation below), with or without a WorkListener, and with or without setting a timeout for when the asynchronous work must be started. If a timeout value is set, the work will be terminated if it has not been started within the specified timeout limit (in milliseconds).

    • doWork() – is used to initiate synchronous work. This operation is similarly overloaded to enable a variety of different ways of controlling the work. The primary use for this operation is to execute a piece of work under an execution context that was captured earlier – perhaps at a different point in your application.

    • create() – can be used to create a WorkWithExecutionContext; capturing the current execution context from the calling thread of execution and combining that with the Work that you want associated with that context. The captured WorkWithExecutionContext can be used later with either the startWork() or doWork() operation to initiate that work and the corresponding context in which it was captured.

    • join() – can be used to block until either any or all of the outstanding asynchronous work has completed. This can be used to idle the main thread while parallel work is completing.

  • Work
    This component represents the work that you want to execute asynchronously. The Work class is derived from java.lang.Runnable, which introduces a run operation. You must implement the Work class and run() method with whatever function you want to execute on the asynchronous thread. The work manager will call the run() method on your Work object when it spawns the thread – having already set the execution context for this thread. The run() method should be implemented as though it is a J2EE client.

    In addition, the Work interface introduces the release() method. The work manager will call this method when it wants to terminate the thread of your Work. You should implement this method to terminate any work that you are performing in the run() method – the run() method should return because of the release() method having been called.

  • WorkItem
    This represents a specific instance of work in some state of execution. An instance of WorkItem will be created and returned from the startWork() method on the work manager to represent the specific thread of asynchronous work created with that operation. The WorkItem supports the following operations:

    • getEventTrigger() – can be used to get a proxy to your Work instance for publishing events to it from your application. This operation takes the name of a Java interface. If your Work implements that interface, then this operation will return a proxy to your Work with that interface. You can then invoke any operation supported by the interface and it will be communicated to your Work instance.

    • getResult() – can be used to get access to your Work instance to gather the results of its work. You should only invoke this operation after you know the asynchronous Work it is performing has completed – accessing the Work results at any time prior to its completion could yield unpredictable results.

    • getStatus() – returns the status of your Work; one of:

    • WorkEvent.WORK_ACCEPTED – the startWork() request has been accepted and the Work has been queued for execution.

    • WorkEvent.WORK_COMPLETED – the asynchronous work has completed; the run() method on your Work object has returned.

    • WorkEvent.WORK_REJECTED – the startWork() request has been denied; the Work will not be executed.

    • WorkEvent.WORK_STARTED – the Work is running and is at some point of incompletion.

  • WorkWithExecutionContext
    This component represents a specific context under which the asynchronous work will be executed. The WorkWithExecutionContext can be created with the create() method on the work manager. The work manager will capture a snapshot of the current execution context at the point the create call is invoked, and record that along with the supplied Work instance in the WorkWithExecutionContext instance. This can later be included in a startWork() or doWork() operation to initiate the contained work asynchronously. The WorkWithExecutionContext object supports the following operation:

    • getWork() – this returns the Work instance that was captured with this execution context.

  • WorkListener
    This is an object that can listen for work events. You can implement your own work listener that supports this interface, and then register an instance of it with the work manager when you start an asynchronous (or synchronous) work request. Your WorkListener instance will be notified when the associated Work goes through various state transitions. Each state transition is represented with an operation on the WorkListener interface:

    • workAccepted() – called when the Work request has been accepted and queued for execution

    • workRejected() – called if the Work request is rejected

    • workStarted() – called when the Work is dispatched on a work thread

    • workCompleted() – called when the Work completes; when the run() method returns

In all cases, these operations are passed a WorkEvent that, in addition to repeating the nature of the state transition, can be used to gain access to more information about the Work that is being reported on.

  • WorkEvent
    This represents transitions in the deferred work state. An instance of WorkEvent is created and passed on each WorkListener call and contains information about the Work in question. The WorkEvent supports the following operations:

    • getException() – can be use to get any exception that was raised by the Work in the run() method during its execution

    • getStartDuration() – is not supported in the current WebSphere implementation

    • getType() – identifies the type of the event – WorkItem.getStatus operation above for the complete list

    • getWork() – returns the Work object in question

These objects are part of a larger package of functions referred to as AsynchBeans (or Asynchronous Beans). The AsynchBeans package covers other services such as a scheduler function, monitoring functions, and an alarm manager. While these functions are useful in complex applications, they are not covered further in this book.

Enabling the Deferred Execution Service

Before you can use the deferred execution service you will need a work manager in the application server. The work manager is the point of control for the deferred execution service. WAS-E comes pre-configured with a default work manager. You can use this, or can create as many work managers as you need in your cell. The primary reason for introducing a different work manager is to configure it differently for your specific needs.

Each work manager can be configured with the minimum and maximum threads that will be allocated in the thread pool for asynchronous work, whether the number of threads can be temporarily grown beyond the maximum, which service contexts will be propagated to the asynchronous work, and the JNDI resource name for the work manager, and so on.

You can create a work manager in the admin console under the Resources list:

click to expand

Press New to create a new work manager:

click to expand

The work manager is a resource, and therefore you will refer to it in your application through a resource-ref. That is, you can use whatever JNDI "java:comp/env" name you want in your application to refer to your work manager, and then you will map that name to the actual JNDI name of the work manager during deployment. To create a resource-ref for the work manager in WSAD-IE you will have to know that the work manager type is com.ibm.websphere.asynchbeans.WorkManager. The rest you will derive from the JNDI name of the actual work manager instance you want to use, and the JNDI reference name you used in your application to refer to the work manager. In the example application for this chapter the reference name used in the example application and the actual work manager instance that will be used in the application server are both referred to as wm/default.

In WSAD-IE, add a resource reference for your EJB:

click to expand

Fill in the reference name you use in your application code (less the "java:comp/env" prefix), the type of the resource (com.ibm.websphere.asynchbeans.WorkManager), along with the settings for source of authentication to the work manager (the Container), and the sharing scope for the resource (Shareable):

click to expand

When you have finished creating the reference, select it and fill in the JNDI name of the work manager resource you will be using (you can do this an the AAT, or at application installation time as well):

click to expand

Defining Deferred Work

Any work that you want to defer must be created as an instance of Work. In addition to the release() method introduced by the Work interface, you must implement the run() method of the Runnable interface.

The run() method is where you should implement your work. This method will be called by the work manager on its thread of execution with a J2EE execution context – that is, with a principal identify under which you can make EJB method requests, a java:comp JNDI name space that can be used to look up EJB references, session and RAS information that can be used to record and track problems with your code or correlate your work to other activities in the system, and so on. You should program this method as though you are in a J2EE client. You should not assume the method would contain a transaction context. However, you can begin a transaction, do a JNDI java:comp lookup, find an EJB home, make calls on EJBs – anything that you would normally be allowed to do in a J2EE client application.

The run() method does not take any arguments – you cannot specify any arguments to the work when you spawn it. Therefore, if you need your work to operate on parameters that are set in your main thread, then you must design the work to include settable fields that will capture these parameters as part of the state of the work instance. Your main thread will then have to set these fields in the work before spawning it with the work manager. This will also affect the decision of whether you will spawn a single instance or multiple instances of your work if you want more than one thread executing on it.

For example, if you were to design a Work object that simply initiates a periodic sweep of your customer accounts, but for which you are not collecting any information in the Work state per se, then you could create a single instance of this Work and reuse it on multiple requests to start it asynchronously. If each request takes a long time to complete and you run the risk of having multiple outstanding asynchronous work activities operating with the same instance of Work, then you should be OK – local variables declared in the run operation will be scoped to their respective thread stacks. In effect, your single Work object instance is re-entrant.

If, on the other hand, you want each asynchronous work request to operate on a specific but different subset of customer accounts, and you want to accumulate statistical information about the accounts that were processed by each asynchronous work thread, then you will need a distinct instance of the Work object for each asynchronous request. The Work object should be implemented with attributes that you can set or get outside of the scope of the run() method – the main thread will use these to set the range values before initiating the asynchronous work request, and later will use other attributes to get the statistical information collected during its run after the work has completed.

The release() method will be called to shut down your work if your application or the application server is stopped. This is particularly important if you write a work function that will be long running, for example, if it enters into an unbounded loop. The release() method will be invoked when the server needs you to stop your thread of work. If your main thread does not return within a certain amount of time after the release() method is invoked, the server will abort your thread.

Obtaining a Work Manager

The work manager is used to defer your work. You can obtain a work manager from JNDI with the name you gave it when you created it in the system management facilities:

 public void ejbCreate() throws javax.ejb.CreateException {   try {     jndi = new InitialContext();     workMgr = (WorkManager) jndi.lookup("java:comp/env/wm/default");     ...   } catch (NamingException e) {     System.out.println("Exception looking up WorkManager: "                        + e.toString());     e.printStackTrace();   } } 

Dispatching Parallel Work

The most straightforward way of using the deferred execution service is to spawn an asynchronous thread for your work.

You begin by creating an instance of work that you want to spawn. You can spawn many threads of asynchronous work. Whether you do this with individual instances or with a single instance of your work will depend on the design of your work. If your work is inherently stateless, you can spawn multiple threads of execution on the same Work instance. You will have to be careful that your implementation is fully re-entrant. If you have to synchronize to accomplish this, then it will end up serializing the execution of your parallel work, hence undermining the efficiency of parallel processing.

You then register your work with the work manager. It will then spawn an asynchronous thread to run your work:

 try {   Work myWork = new MyDeferredWork();   WorkItem thisWork = workMgr.startWork(myWork); } catch (WorkException e) {   //The work could not be registered. } 

The WorkItem handed back when you register work with the work manager, represents that specific instance of work. You can use that WorkItem to find out more information about that work as it proceeds.

The getStatus() method can be used to determine what state the work is in – states include WORK_ACCEPTED, WORK_REJECTED, WORK_STARTED, and WORK_COMPLETED as defined in the WorkEvent object.

The getResult() method can be used to get back an instance of the Work object that you registered. You can then acquire any state contained within this work through any interfaces that you designed for the Work object.

Dispatching Synchronous Work

There may be cases where you want the deferred work to execute synchronously. As before, you can create a Work instance and register it with the work manager to be dispatched in a synchronous fashion:

 workMgr.doWork(myWork); 

Since synchronous work executes in a blocking fashion there is no WorkItem returned from the doWork() method – control is simply returned to you when the synchronous work has completed.

Capturing Context

When the work manager dispatches the run() method on the Work object, it will do so within a J2EE context. That context is obtained from your main thread in one of two ways described.

At the time you register your work with the work manager, it will capture the J2EE context of the thread of your execution, serialize it, and re-establish that context with the deferred work when it is dispatched:

 WorkItem thisWork = workMgr.startWork(myWork); 

Alternatively, you can capture a snapshot of the J2EE execution context on your thread of execution (along with the work that you will be registering), and then register this combination of work and context later:

 WorkWithExecutionContext executionSnapshot = workMgr.create(myWork); ... //Sometime later, including on some subsequent method request ... WorkItem thisWork = workMgr.startWork(executionSnapshot); 

Snapshot execution context can be used with any of the styles of execution described above. If, in either case, the J2EE context that was captured cannot be established on the thread where the work will be executed, then the work will be rejected. You can register to be notified of this with a work listener.

Listening for Work Events

You can register a work listener for any of the work that you spawn. The work listener will be notified when the work transitions go through various states – including, when the work is accepted, rejected, started, or completed. Your work listener must implement the WorkListener interface. You can then create an instance and register that with the work manager along with the work:

 WorkListener myWorkListener = new MySynchWorkListener(); workMgr.doWork(myWork, myWorkListener); 

The work listener will be invoked on the workAccepted(), workRejected(), workStarted(), and workCompleted() methods depending on the transition for the work. These methods are passed a WorkEvent object that captures information about the event.

The getType() method on the WorkEvent can be used to find what kind of event is being notified. This information is redundant with the method on which the event was presented.

The getWork() method can be used to get the Work instance for the event. The Work instance will contain whatever state information you have designed into the work.

The getException() method can be used to retrieve any exceptions that may have been raised by the work during its execution.

Joining the Completion of Work

You can join the completion of any work through the work manager. The join() method on the work manager is a blocking call that will not return to you until either the work is joined (one or all of the work items have completed, depending on the conjunction used for the join), or after a specified timeout. If you have spawned more than one thread of work, you can join on any one, or on all of them by including each of the WorkItems for the work that you want to join in the work list.

The timeout is specified in milliseconds, and indicates how long the join will wait for work to complete. If the timeout is 0, then the join will simply peek at the current work state and immediately return. If the timeout is any negative number, then the join will block indefinitely for work to complete.

The following is an example of a join on any of the work items – the join will return when any of the work items listed have completed:

 public void waitForSomeWorkToEnd(int timeout) {   ArrayList outstandingWorkList = new ArrayList();   for (int i=0; i < initiatedWorkItems.size(); i++) {     if (((WorkItem)initiatedWorkItems.get(i)).getStatus() ==            WorkEvent.WORK_STARTED)       outstandingWorkList.add(initiatedWorkItems.get(i));   }   if (workMgr.join(outstandingWorkList, WorkManager.JOIN_OR, timeout)) {     System.out.println("Some work has completed.");   } else {     System.out.println("Wait timed out.");   } } 

The following is an example of a join on all of the work items – the join will return when all of the listed work items have completed:

 public void waitForAllWorkToEnd(int timeout) {   ArrayList outstandingWorkList = new ArrayList();   for (int i=0; i < initiatedWorkItems.size(); i++) {     if (((WorkItem)initiatedWorkItems.get(i)).getStatus() ==            WorkEvent.WORK_STARTED)       outstandingWorkList.add(initiatedWorkItems.get(i));   }   if (workMgr.join(outstandingWorkList, WorkManager.JOIN_AND, timeout)) {     System.out.println("All work has completed.");   } else {     System.out.println("Wait timed out.");   } } 

Collecting the Work Results

Once work has completed, you can collect the results from the Work object. There are no return values from the run() method and therefore you have to provide a means for acquiring the work results through operations that you design on your Work object. You might include, for example, attributes on your Work object that allow you to gather statistical information gathered during its run. When the work completes, you can then call the Work object getter-methods to get the statistical information. You should be careful to re-acquire the Work instance from the WorkItem returned to you when you initiated the startWork() request (or through any of the other avenues for getting the Work object from the work manager – for example, from the WorkEvent that is published when the work completes). The Work instance that you created at the time that you initiated the startWork() request may or may not still be the same instance that the work manager ended up using to do the work – all of the state will be the same, but it may have been serialized and reconstituted in between.




Professional IBM WebSphere 5. 0 Applicationa Server
Professional IBM WebSphere 5. 0 Applicationa Server
ISBN: N/A
EAN: N/A
Year: 2001
Pages: 135

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net