Barracuda Event Model - Long Running Events
barracuda.gif (11456 bytes)The purpose of this document is to describe how Barracuda makes it very easy to handle long running tasks.

Defining the Problem

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.


Seeing it in Action

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

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.


More Advanced Configuration

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.

How does all this work?

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!

For all the latest information on Barracuda, please refer to http://barracudamvc.org
Questions, comments, feedback? Let us know...