views:

453

answers:

7

A team member has run into an issue with an old in-house system where a user double-clicking on a link on a web page can cause two requests to be sent from the browser resulting in two database inserts of the same record in a race condition; the last one to run fails with a primary key violation. Several solutions and hacks have been proposed and discussed:

  1. Use Javascript on the web page to mitigate the second click by disabling the link on the first click. This is a quick and easy way to reduce the occurrences of the problem, but not entirely eliminate it.

  2. Wrap the request execution on the sever side in a transaction. This has been deemed too expensive of an operation due to server load and lock levels on the table in question.

  3. Catch the primary key exception thrown by the failed insert, identify it as such, and eat it. This has the disadvantages of (a) vendor lock-in, having to know the nuances of the database-specific exceptions, and (b) potentially not logging/dealing with legitimate database failures.

  4. An extension of #3 by attempting to update the record if the insert fails and checking the result of the update to ensure it returns 1 record affected.

Are the other options that haven't been considered? Are there pros and cons of the options presented that were overlooked? Which is the lesser of all evils?

+5  A: 

Put a unique identifier on the page in a hidden field. Only accept one response with a given unique identifier.

Paul Tomblin
That adds a lot of overhead on the server side - in a farm of servers they would have all to be aware of this identifier and they'd all have to sync and lock on it so there was no race condition if the two requests were balanced across two different servers.
Teflon Ted
If the unique identifier is the primary key for the database insert, then your database integrity will take care of it.
Paul Tomblin
A: 

It seems you already replied to your own question there; #1 seems to be the only viable option.

Otherwise, you should really do all three steps -- data integrity should be handled at the database level, but extra checks (such as the explicit transaction) in the code to avoid roundtrips to the databse could be good for performance.

Mikael Jansson
+2  A: 

It sounds like you might be misusing a GET request to modify server state (although this is not necessarily the case). While it may not be appropriate for you situation, it should be stated that you should consider converting the link into a form POST.

Craig Walker
A: 

Looking to avoid this problem on a <button>.

Tried

document.GetElementBuId('btn').disabled = true;

NO Result, button still responds to double click or two click.

I have added an event parameter to the function call and used it on the first available line.

event.target.disabled = true;

NO Result, button still responds to double click or two click.

Adding

ondblclick="false" 
ondblclick="return false;" 
ondblclick="alert('dblclick'); return false;"

Still not working.

Conclusion : cannot disable double click or two click on FireFox.

UNLESS YOU KNOW BETTER.

Regards John

John Griffiths
+1  A: 

You need to implement the Synchronizer Token pattern.

How it works is: a value (the token) is generated on the server for each request. This same token must then be included in your form submission. On receipt of the request the server token and client token are compared and if they are the same you may continue to add your record. The server side token is then regenerated, so subsequent requests containing the old token will fail.

There's a more thorough explanation about half-way down this page.

I'm not sure what technology you're using, but Struts provides framework level support for this pattern. See example here

Harry Lime
Although this seems to be the best-worded and most-thought-out response, there's still a race condition between the token checking and new token generation; that has to be synchronized somehow.
Teflon Ted
Not if the new token is re-generated on each page view, and stored in a database of some sort that can be LOCKED without performance degradation. Now you can LOCK the database, check if the value has already been used, and then neatly error out, if it has not been used, flag it used and call it done
X-Istence
A: 

REF You need to implement the Synchronizer Token pattern.

This is for Javascript/HTML not JAVA

Sorry John

John Griffiths
A: 

Hi John,

I have the same problem with an classic ASP system, we had to implement a button disable on forms, which works well. I saw your code had "GetElementBuId", is this a typo? Should it have been "getElementById"? Lower case g and By, not Bu.

The problem I have now is if you click back in firefox the buttons are disabled from the previous post of the form. Does any one have any ideas on how to get around this?

Cheers!

campo