One way to handle concurrency in a multi-user web app

Legend has it that there is more than one way to skin a cat. I’m not personally familiar with any of them but, thanks to Lorna Mitchell, I do know of one technique to cope with concurrency in a multi-user web app.

Concurrent user activity on the web can take many forms.  For example, two online shoppers may simultaneously try to buy the last pair of ‘gotta-have-em’ shoes in stock.  Presumably one potential outcome in this scenario is to place the shoes on back-order for the slower shopper.  The concurrency challenge I faced recently, however, was a bit different…

Imagine Sarah, who displays an edit form in her browser. At the very same moment (despite rolling her eyes and sighing loudly), she gets sucked into a long and, erm, interesting “conversation” with Bob who sits next to her.  When Sarah finally manages to extricate herself from Bob’s extended monologue on the murky world of his best mate’s online gambling addiction, she remembers a critical change she needs to make to the record still on display. Unfortunately, however, whilst Sarah was practising her ‘Can’t you see I’m busy?!’ body language techniques on Bob, another user accessed and edited the same record.  Sarah’s critical edit shouldn’t be allowed to happen now.

Sarah won’t know that the record has changed unless she refreshes the page and re-checks the record.  In a real world situation, she’s not going to do that, is she?  Well, no. But, if Sarah goes ahead and makes her critical change, without knowledge of the interim edit, she’s going to inadvertently cause big problems!  We need a way to let Sarah know, before her update, that the record has changed.

I will describe the technique as implemented in my Symfony 1.4 application.  The framework isn’t important, it should be fairly easy to replicate in any PHP environment.   First, we need to make sure that any forms we generate include a hidden field containing the record’s latest updated_at value in the form of a timestamp.  In Symfony 1.4, this is as easy as doing:

//lib/form/MyFormClass.class.php

public function configure()
{
$this-..>setWidgets(array(
// other form fields here
'updated_at' =>new sfWidgetFormInputHidden()
));

$this->setValidators(array(
// otherformfieldvalidatorshere
'updated_at' =>new sfValidatorString(array('required' =>false)),
));
}

Through trial and error we discovered that, in Symfony 1.4, it was necessary to set a validator for the hidden ‘updated_at’ field (even though it felt a bit, well… wrong somehow).

Next, we need a method we can call whenever a form is submitted and before it is actually processed (validated, etc). This method should compare the updated_at value stored in the form’s hidden field with the updated_at value in the object we retrieve from the database just prior to processing the form. If they do not match, the record was updated by another user while the form was on display in Sarah’s browser.

public function hasBeenModified($object, $updatedAtFormField)
{
if ( $object['updated_at'] != $updatedAtFormField )
{
return true;
} else {
return false;
}
}

Symfony 1.4 provides an editable BaseForm class that extends sfFormSymfony (lib/form/BaseForm.class.php). This is the perfect place for our method, as we can pass it any object/ form values, regardless of model.

Now, before we process our submitted form, we first call our method to ensure that no interim edits have taken place:

//apps/frontend/modules/myModel/actions/actions.class.php

public function executeUpdate(sfWebRequest $request)
{
$this->myObject = Doctrine::getTable('myModel')->find(array($request->getParameter('id')));

// ....more code here

$formFields = $request->getPostParameter('myModel');

if ($this->form->hasBeenModified($this->myObject, $formFields['updated_at'])) {
// do not process the form
} else {
// process the form
}

// ....more code here

}

Voila! Of course, we could go further and implement a similar technique to prevent a form from being generated if the current object has been modified since it was last displayed. Another challenge is how to deal with simultaneous updates on a batch of objects where one or more may have been modified by other users in the interim.

Concurrency in a multi-user web application poses an interesting challenge. How do you handle it?

Tagged with: ,
Posted in Articles, php
2 comments on “One way to handle concurrency in a multi-user web app
  1. TaoOfPHP says:

    A better way in my eyes would be to run a JS-powered-interval (using setInterval()) which calls back to the server every-so-often to see if the file was updated, and then alert the user before she even finishes modifying the document, perhaps even displaying the new document, and perhaps even highlighting differences (lots more work in that one). That way she doesn’t waste more of her precious time making irrelevant modifications. Of course, I would also have the “onsubmit” handler also, as mentioned above.
    The down-side is the harassment of the server (it doesn’t have body language to say how busy it is), but if the “returnFileTime” file on the server was a compiled script or even a PHP script with it’s bytecode cashed, perhaps this wouldn’t be so bad. Of course if you’re talking MySQL, then there’s another hit on the server’s time.

  2. preinheimer says:

    Continuing with the many ways to skin a cat…

    The application could store a copy of the critical record (or records if there’s a join in play) when the data is presented to the user. Then during an UPDATE the where clause would ensure that every field is as it was when the data was presented. This would save us from adding an extra row to our tables, and avoid a possible issue where someone could update the table in the moment between the application checking the updated_at field, and actually changing the data.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>