Bug Fixing, A Tale of Two Halves
My task today has been to fix a bug in a workflow system. Finding the fix was one of those real eureka moments which you just have to share. Hopefully the way I solved it will offer an idea you might also use one day. So, hopefully, without upsetting the client or divulging any sensitive information, I will try and describe the problem/solution now.
The bug was in a view where you could select one or more documents and pass them to the next stage of the workflow. The same principle as I described in this article. For each of the documents selected the code loops them all and works out the next workflow stage it needs to be at. It also appends the action it performed to the end of the document's audit history. The problem (which, to make matters worse, was intermittent) was that selecting more than one document meant they ended up at the wrong stage. It also led to multiple entries in the audit history. The odd thing about it was that there was no real consistency to the behaviour of the bug.
My first line of attack was the code in the WebQuerySave agent that processed the documents. My assumption was that the problem must have been in there. After trawling for obvious logic errors I could see nothing that might cause the behaviour. I then spent what seems like forever adding print statements to the code before re-creating documents which I could test the code on. This painfully fruitless task went on until I'd tried just about everything I could think of and was beginning to wonder if I'd ever solve it.
The decisive moment was when I submitted three documents for processing from the browser and happened to have HttpWatch running. This is what I saw:
Notice the three POST requests; two aborted and one successful. This is when alarm bells rang, albeit quietly. It was strange to see three requests for the same URL and I had no idea what was going on, having never seen an aborted request before. Although I did think it could explain a lot about the bug and its symptoms.
My next task was to examine the HTML of the view (similar to this), which I suspected had a form element for each document in the view. Although that wouldn't explain why all the forms were being submitted at once. I founf the cause in the JavaScript code behind the button that sent the documents off for processing. It looked like this:
len = document.forms[1].elements.length;
for( i=0 ; i<len ; i++) {
if (dml.elements[i].name=='batchdocuments') {
if (dml.elements[i].checked==true) {
document.forms[1].submit();
}
}
}
This code checks for there being at least one document selected before it submits the form. Can you see the problem with it? It works, in that it will only submit the form if a document is selected, but it then carries on submitting the form each time it finds another ticked checkbox! As soon as I saw the submit call inside the for() loop I knew I was on to something.
What the code needs is a break statement in the for() loop. As soon as the form has been submitted stop looping. The code would work better like this:
with (document.forms[1]){
for( i=0 ; i < elements.length ; i++) {
if (elements[i].name=='batchdocuments' && elements[i].checked) {
submit();
break;
}
}
}
I've seen similar bugs before, whereby the developer assumes that once the form is submitted everything is over and the code is complete. Not always the case, as this demonstrates.
The funny thing with bugs like this is their intermittent nature. It was hard for me to reproduce the bug, whereas it happened to the client almost without fail. This was probably due to our different browser versions and the different way they handle multiple calls to submit the same form. More likely it was down to browser and/or connection speeds. A slower browser on a slower machine is more likely to get the chance to send the whole form to Domino before aborting the request. If Domino receives the request more than once it will process the code more than once. Beware!
If I've learnt anything from this exercise it's how useful HttpWatch can be in times of desperation. Since writing this I've noticed they've released version 3.2 which adds some really nice features to the version I've been using. It's a tad pricey but it's often worth its weight in gold! I know there are free alternatives out there and I've tried lots of them. But you get what you pay for and this one's the slickest I've seen so far.
I'm slightly confused. Was the view a series of individual forms or one form where the rows are rendered as inputs on the single form?
As for a way around the multiple submit processing, if you post instead of get this doesn't seem to be a problem, at least on the form I'm testing now. Not sure of the mechanics behind it just yet though.
"you get what you pay for" ?
I don't think that's true in the software world at all, in any way, shape or form.
Jerry. All the documents rendered as check boxes inside one form. It was using POST and there was a problem. Not sure how browser-specific it is.
Andrew. You're right. I should probably re-phrase that to "You're more likely to get quality if you pay for it". There's great free stuff out there and there's really awful freeware out there. But then I suppose there's awful software that we have to pay for. I retract my statement ;-)
"It wasn't me."