LiquidOffice Suicide Loops (Timeouts)

Applies To: LiquidOffice

If you’ve ever created a semi-complex process in LiquidOffice you might have run into a common problem – zombie processes.  Zombie Processes are processes that never completed but they aren’t being actively used either. Generally these have to be manually killed using the Management Console.

This isn’t LiquidOffice’s fault, it’s doing what it’s supposed to, but depending on your environment this can be a big problem and a huge irritation.  Fortunately, we can use LiquidOffice to solve this problem quickly and simply with just a few additions to your process.

The most common cause of zombie processes is having a “choose your own adventure” type process with multiple forms.  Consider the following process:

In the above generic process we have two forms and two script tasks.  The script tasks are just there to serve as placeholders for whatever tasks you might be doing between and after forms.  Often in a more complex process you would have some sort of branch task that changed what the second form was or altered some fields depending on the result of the first form.  But to keep things simple this is our sample process.

Note – We have checked the “Automatically Display Next Form on Submit” property in the Form section of the first form task’s properties.  At this point our process also does not take advantage of the Initial Form property (we will cover that further down).

In our common BeanShell script area we have the following generic function:

//Writes a message to the Log
void writeToLog(String Message) {
  Log log = LogFactory.getLog();
  log.info(Message);
}

This is a simple helper function we use to save time when we write to the log.  Using this in the More Stuff task (final script task) we have:

void enteredActive(State state) {
  writeToLog("All forms processed!");
}

So if everything goes well, the final thing our process does is to write to the event log to let us know it’s all done.  But what happens if when a user gets to the second process they realize they messed up the first form so they close the window and start over?  A good solution is to provide a back button (See our Multiple Submit Buttons in LiquidOffice article).  However, not everyone will use a back button no matter how convenient and it still doesn’t account for people who either accidentally or purposely closed the window for whatever reason.

When this happens as far as the user is concerned the process is gone, but LiquidOffice holds onto the instance, all of it’s data, the versions of the form(s) and the process that were used, and the current state of the process.  All of this will need to be cleaned out manually using Management Console and in some versions of LiquidOffice the process isn’t fully cleaned up on manual deletion.

Fixed Lifespan:

The Fixed Lifespan solution is very easy to implement.  Here is our example from above with a Fixed Lifespan:

Only two controls were added to the process.  An Or block and a Delay task with no parent tasks (no preceding connections).  The Delay time you set is equal to the lifespan of the entire process.  The Or block needs to be the last item in your process and allows us to say,“however you get here, end”. If the tasks all complete before the delay expires then the process performs as expected and the delay task is ignored.  However, if the delay expires, the Or block at the end cancels out the rest of the process tree and the process gracefully dies.

This works because both Form 1 and the Delay task are started at the same time (process instantiation).  So it’s a race.

This makes for a very simple solution but should be used with extreme caution.  This is especially true for large processes which require approval, manual operations, or special routing.  You will need to take into account all reasonable time frames for each task to complete and make sure your Delay task is set much higher.

Estimating an appropriate lifespan and maintaining adjustments to that lifespan as the process changes can be difficult and error prone.  Many times you only want specific tasks to timeout and you’d like that timeout to be relative to each task.  For example, once a user has submitted the form(s) you probably don’t want an automatic timeout.  So what do you do when you only want to target certain tasks and you’d like those timeouts to be relative to the task they are timing out?

A Better Solution:

With the help of Autonomy tech support (Thanks Simon!) we have come up with a solution that fits that criteria and that we add to nearly every process we publish.  We call it a suicide loop because that sounds cool (right??), but that might lead you to believe that we are just forcing a delete on the process from within the process.  In actuality, we are still gracefully completing the process allowing for standard cleanup and for statistics to maintain their integrity.  In addition, you can add whatever code you want when this happens (send an email, write to the log, etc.).

The solution involves adding an Escalate task, some other task(s), and an Or block:

The escalation task needs to be attached to the form causing the problem (Form 2 above). Ensure your Delay until escalation property is set to a relatively high number to allow the user the chance to fill it out.  For testing we set ours to 1 minute (0:01:00.000), but a value of 24-72 hours is generally more appropriate.  To be clear, this is the amount of time the form (NOT the entire process) can be left open (zombie or not) before the process is sent to heaven – so you will need to pick it carefully.

If the form is submitted before the escalation delay then the process continues on as expected and the escalation task is cancelled.  However, if the escalate occurs, the Or block at the end cancels out the rest of the process tree and the process gracefully dies.

Wait a second, what about that extra script task between the escalate task and the Or block? This is REQUIRED.  This can be a blank script task (does nothing) or something else like an email task (or whatever other business logic you want to insert), but you must have something here.  This is because although the escalate task is cancelled, it “shows” as completed which signals the Or block to be followed – cancelling the process immediately.  Placing something here changes that.  In our process above, when the escalate task is cancelled the Script task is set to Passive which prevents it from executing and therefore stops the path.

Our script task does this:

void enteredActive(State state) {
  writeToLog("Goodbye Cruel World!");
}

If you run this process, close the window on (don’t submit) the second form and you’ll see the “Goodbye Cruel World!” message in the System:EventLog within the Management Console (after the escalation delay).  You’ll also find the instance is marked as complete.  The process can also be completed normally (all forms submitted) and the suicide loop will never be entered.

Congratulations, it was really that simple!  Unless of course, it wasn’t…

Multiple Suicide Loops:

In most cases the above solution should have taken care of your problem.  But if any of the following are true, you will need to add additional loops:

  • Multiple forms where a user could stall out
  • Using the process Initial Form setting set to Instantiate Process Before Display Form

Using our same process from above we have now set the process Initial Form property to Use Initial Form and set it to Instantiate Process Before Displaying Form:

This effectively means our process has multiple forms where a user could stall out.  So this means we need a way to kill the process on either form 1 or form 2 being abandoned.  This really just means we have to add another suicide loop (one for each stall point).  This isn’t that complicated but it’s a little annoying.

LiquidOffice doesn’t allow the escalate task to be attached to more than one task (the pink line).  This means you need a separate escalate task for each stall point.  This creates a new path for each stall point that can be joined back together for one final suicide task(s) as needed, but ultimately every path in your process needs to end in that final Or block.

Unfortunately, you can’t just have each escalate task point to the same inbetween task (the “Goodbye Cruel World” script in our example).  Because then the process will stall out at this task since it will be waiting for the other escalate task(s) to also reach that point before continuing.  You also can’t simply add an Or block before the imbetween task or you will have the same problem as before with the Or block being activated when the escalate task is cancelled.

So you need to add the “full” suicide loop for each stall point like this:

This is a pretty simple implementation.  But what if you wanted to send an email (or some other logic) every time a process stalled out?  You could just cut and paste and replace the imbetween script tasks with these tasks, but maintenance would be a nightmare and your process tree could quickly get huge.  So do something like this:

As you can see we still have a generic script task after our escalate tasks, but these do nothing.  We have moved our Finally logic between the Or blocks.

Although the suicide loop is more complex than the fixed lifespan, the benefits are worth it. Regardless of exact implementation, the prevention of zombie processes is an important part of your design for EACH process. These are our methods, what do you guys do?  Any other process maintenance tips? Let us know in the comments!

Originally Published on WireBear.com/blog on November 22, 2010

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s