The purpose of this document is to describe how Barracuda makes it very easy to
handle long running tasks.
In many cases, the pages you generate will take only a few seconds to create and return to the user - the user
submits a request...bang!...they get the response and life is good.
Unfortunately, however, there are often situations (like generating large reports) which not only take a long
time to complete, but also place a heavy load on the server. In cases like this, its quite common for the user to
submit a request and wait a few seconds/minutes. If the request seems to be taking longer than it should...(yes that
progress icon up in the right hand corner says the page is loading, but its not here yet!!!!)...they re-submit the
request again. And again, and again, and again. And your server which was already churning to handle this one really
big task now has thirty more requests for the exact same huge report. Ugh!
What is really needed is a way to easily give the user some kind of progress indicator, to show them "hey,
the server is doing something...here's how long its probably going to take, and here's where we are in the process.
Not only that, but it should also make it really easy for the user to Cancel that request (and actually stop the
running process on the server, so it doesn't consume valuable resources). Finally, a feature like this should be
very easy for developers to implement - as an afterthought, almost; it shouldn't require any sophisticated
pre-planning on the part of the developer, because often you won't know how long tasks are going to take until you
roll them out to testing. So we need an easy way to say..."hmm, that report is taking a while to run...let's
configure it to show the user a progress dialog."
Enter the new Barracuda Long Running task support.
Ok, want to see what it looks like?
- first, login into the Simple Login App (user=foo, pwd=bar).
Once you do this it will automatically take you to the main screen,
which gives you a couple of additional links.
- next, click the Get Main Screen (slowly) link. This
simply takes you back to the main screen, but the request gets piped through a control handler which has a
30-second delay built into it - so it'll take a little bit before the screen is ready to render (just like a
long running report!).
What you should see here is a temporary page that looks something like this:
The screen will refresh automatically every 5 seconds, but the user can refresh anytime simply by clicking the
Refresh link. The user can cancel the process by clicking Cancel. When the process completes, the screen will reload
with the generated result (in this case the same page we started from).
Wow. Pretty slick. So what's it take to make it happen?
Making it happen is a piece of cake. All we have to do flag our event as implementing a new Barracuda interface
called LongRunning, and the way we do this is through the events.xml
definition. Here's what the event declaration file looks like for our sample app:
<build-events pkg="org.barracudamvc.examples.ex1.events">
<control-events>
<event name="SomeSpecialEvent"/>
</control-events>
<req-events>
<event name="LocalRequestEvent">
<event name="LoginScreenEvent">
<event name="GetLoginScreen"/>
<event name="AttemptLogin"/>
</event>
<event name="MainScreenEvent">
<event name="GetMainScreen"/>
<event name="GetMainScreenSlowly"
template="LongRunning.template"
on_cancel="new GetMainScreen()"
eta="30" />
<event name="ExpireSession"/>
</event>
</event>
</req-events>
<resp-events>
<event name="LocalResponseEvent">
<event name="RenderEvent">
<event name="RenderLoginScreen"/>
<event name="RenderMainScreen"/>
</event>
<event name="ErrorEvent">
<event name="BadUsername"/>
<event name="BadPassword"/>
<event name="UnknownLoginError"/>
</event>
</event>
</resp-events>
</build-events>
Basically, all we have to do is add define a couple of additional parameters and recompile:
- template="LongRunning.template" - this forces the GetMainScreenSlowly event to use an
alternate template which implements the new LongRunning interface. When ApplicationGateway sees
events that implement LongRunning, it will automatically show the user a progress dialog and dispatch
the original request in a separate background process.
- on_cancel="new GetMainScreen()" - this configures this particular LongRunning event to tell
it where to redirect on a Cancel event. [Note: if you look at the EventBuilder taskdef, you will see that
on_cancel is a custom attribute; its not defined in EventBuilder.dtd. Consequently, that taskdef converts
this attribute to @on_cancel@ an then replaces all instances of this tag in the template with the
value specified. You can use this to great effect with any custom tags (ie. if you create your own custom
templates)]
- eta="30" - similar to on_cancel, this custom attribute configures the LongRunning
interface, saying "this event will complete in 30 secs".
At a minimum, you need these 3 attributes to mark an event as LongRunning and provide the necessary information
for it to be dispatched in the background.
Ok, so we can see that basic configuration is really easy; what if we want to do more advanced configuration?
That's actually quite straightforward as well...
System-wide Configuration
There are several settings which you can configure on a system wide basis. LongRunningEventGateway has
several static constants which can be configured through the ObjectRepositoryAssembler:
- DEFAULT_TEMPLATE - defaults to org.barracudamvc.core.event.events.xmlc.LongRunningHTML
- DEFAULT_ETA - defaults to 180 secs
- DEFAULT_REFRESH_RATE - defaults to 3 secs
The most important of these is the DEFAULT_TEMPLATE setting. Since your app may have a distinct "Look and
Feel" (L&F), you may want the progress dialog to look like it "fits" with your app. For instance,
in our applications, our progress dialog looks like this:

The way we do this is by overriding the DEFAULT_TEMPLATE setting to point our our template, rather than the
default Barracuda template. Here's how you do this through the object-repository.xml file:
<object class="org.barracudamvc.core.event.events.LongRunningEventGateway">
<prop name="DEFAULT_TEMPLATE">com.atmr.atmreports.xmlc.LongRunningHTML</prop>
</object>
Now, the LongRunningEventGateway will process this template using a standard BTemplate model; here are the
directives available for you to access from your template:
- CheckLongRunning - BAction (for link or button)
- CancelLongRunning - BAction (for link or button)
- TimeETA - String describing ETA in hours, minutes, and secs
- TimeElapsed - String describing time elapsed in hours, minutes, and secs
- TimeRemaining - String describing time remaining in hours, minutes, and secs
- PercentComplete - String describing percent complete
- PercentRemaining - String describing percent remaining
- StateMapValue - custom statemap value (you can set these programatically, and then access them from the
template - for instance, you might want to use this to show a description of what step in the long running
task the process is currently executing)
- RefreshRate - int value (primarily used for setting the refresh header in your template
For an example of these, refer to the sample template and see how it uses these directives. Your template will do
something very similar.
[Note: if you are using a form with buttons, the way ours does up above, you MUST specify the form to use the
POST method.]
Per-Event Configuration
Many times, you will want to configure LongRunning behavior on a per-event basis. For instance, the
redirect-on-cancel event and ETA may well vary for each individual event. We have already seen how you can do basic
configuration on these settings through the event.xml declaration.
But what if you need greater granularity? For instance, sometimes you may want configure an event differently
based on who the user is, what the request is, etc.
As an example, there are certain times when a LongRunning event may really not take very long to run; you
can tell Barracuda to ignore the fact that the event implements the LongRunning interface by including
ApplicationGateway.LR_OVERRIDE_KEY in the event link. For instance, try clicking
this link to fire the GetMainScreenSlowly.event while ignoring the fact that it implements the LongRunning
interface.
Let's say you actually want to programmatically configure the LongRunning interface? From within your event
handler, you can get the LongRunning event simply by casting the event from the context:
//get the LongRunning object so we can update it
LongRunning lr = (LongRunning) context.getEvent();
lr.setRedirectEvent(new GetMainScreen());
lr.setETA(120);
The LongRunning interface provides a number of methods that you might want to use to configure the LongRunning
event (ie. to write information _in_ to the LongRunning object):
- public void setRedirectEvent(BaseEvent be) - Specify the redirect event to be fired if the LongRunning process is cancelled
- public void setRefreshRate(int secs) - set the refresh rate (in secs). -1 indicates the DEFAULT_REFRESH_RATE will be used
- public void setETA(int secs) - set ETA (in secs). -1 indicates the DEFAULT_ETA will be used
- public void setElapsed(int secs) - set elapsed (in secs). This can be called by the developer's event handler code if desired to manually specify how much time has elapsed. If you don't call this method, elapsed time be determined dynamically by comparing the amount of time elapsed from object creation.
- public void setPercentComplete(int percent) - set the percent complete (int between 0 and 100). This can be called by the developer's event handler code if desired to manually specify completion percent. If you don't call this method, percent completed will be determined dynamically by comparing the amount of time elapsed with ETA.
- public StateMap getStateMap() - get the statemap (may be used for storing key/val info, which can then be retrieved from the template). Typically called by the developer's event handling code to set some kind of value, and then by LongRunningEventGateway to retrieve that value when requested by the template.
- public void setAdditionalModels(List imodels) - provide a List of additional BTemplate models to make available during to the LongRunningEventGateway template. This method would be called by the developers event handler code to provide additional models for the template to access.
- public void setTemplateClass(Class cl) - provide a different Template class instead of the default Barracuda template, This will typically be used by developers who want to override L&F of the progress screen.
- public void reset() - resets the start time. Typically called by ApplicationGateway prior to dispatching.
It also provides a number of other methods, but these are primarily used to LongRunningEventGateway to update the
progress screen (ie. it uses these to read information _out_ of the LongRunning object)
- public BaseEvent getRedirectEvent() - get the redirect event for this LongRunning process. Typically called by
LongRunningEventGateway.
- public int getRefreshRate() - get the refresh rate (in secs). If the refresh rate has not been set, DEFAULT_REFRESH_RATE will be returned.
- public int getETA() - get ETA (in secs). If the ETA has not been set, DEFAULT_ETA will be returned.
- public int getElapsed() - get elapsed (in secs). If a elapsed time has not been programatically specified, it will be calulated dynamically. Typically called by
LongRunningEventGateway.
- public int getPercentComplete() - get the percent complete (int between 0 and 100). If a percentage complete has not been programatically specified, it will be calulated dynamically. Typically called by
LongRunningEventGateway.
- public List getAdditionalModels() - get any additional BTemplate models. Typically called by
LongRunningEventGateway.
- public Class getTemplateClass() - get the custom template class. Typically called by LongRunningEventGateway.
Ok, so the actual mechanics of making all this happen are quite complex. If you really want to understand what is
going on, you are going to need to take a close look at the code in ApplicationGateway, LongRunningEventGateway, and
the Simple Login example. In the meanwhile, here's a brief overview of the process:
- pass 1 - when a LongRunning event comes in, ApplicationGateway caclulates a unique id (lrid), and then
effectively splits the initial request into 2 separate parts by writing back a frameset that consists of two
subframe requests:
- the main frame requests CheckLongRunningEvent for the target lrid
- a secondary frame that actually invokes the long running request
- pass 2a - the first frame sits and spins, checking the progress and handling a cancel request
- pass 2b - the second frame executes the request, writing the response into the user's session; when the long
running task is actually complete, it then writes a response to the browser redirecting the browser one more
time (using the original event url + lrid)
- pass 3 - get the response out of the session and write it back to the client
As I said, this is all actually quite complex, because frames effectively turn everything into GET requests. So
we need to cache incoming parameters, then reconstitute later. We also need to do quite a few other sophisticated
gyrations to make everything work.
One final note: sometimes the user may not press Cancel like they should - instead they simply hit the stop
button (case 1), or they simply press CTRL+R on the browser, which kicks off the request again (case 2). In both of
these cases, Barracuda tries to catch this event, warn the user they should be pressing Cancel first, and then it
actually tries to stop the running request before continuing with the action they invoked (either a stop or a
reload). This seems to be working in all major browser, except we are not able to get an event notification for case
2 in IE 6 (which means the request just loads twice).
That about covers it. If you have further questions or comments, email the list! |