1

In my system, a user has a number of bookings (nodes of content type 'booking') associated. Each of these bookings has a 'field_price' field and a 'field_customer_user' field (for which I am using the Entity Reference module) that links the booking to its customer. The user also has a 'field_bill' field containing the sum of all his bookings' prices.

Each time a booking gets deleted, I want to subtract its price from its user's bill. I'm doing it the following way, using hook_node_delete:

function mysite_node_delete($node) {
   if ($node->type == 'booking') {
     if ($account = user_load($node->field_customer_user[LANGUAGE_NONE][0]['target_id'])) {
       $bill = $account->field_bill[LANGUAGE_NONE][0]['value'];
       $bill -= $node->field_price[LANGUAGE_NONE][0]['value'];
       $account->field_bill[LANGUAGE_NONE][0]['value'] = $bill;
       user_save($account);
     }
   }
}

However, sometimes this does not work as expected. Given the following case:

User booking #1: 100$

User booking #2: 200$

User booking #3: 50$

Total bill: 350$

An administrator logs in and deletes all three bookings for this user sequentially, starting from booking #1 and finishing with booking #3. The user's total bill should be 0 now, but it ends up being

Total bill: 300$.

I guess that this means that when user_save($account) was called for booking #3, the user bill had not been properly updated by the first two calls to hook_node_delete. Therefore, the old value (350$) gets fetched and subtracted the price of booking #3, and end ups being 300$, which is not correct.

Is there anyway of making it work?

3
  • There seems to be an error in your code first time the value of $bill is retrieved from $account->field_total_bill[LANGUAGE_NONE][0]['value']; Where as in the line before the user_save the updated value is assigned to $account->field_bill[LANGUAGE_NONE][0]['value'] Commented Feb 6, 2014 at 12:16
  • Edited, that was not the issue. I just forgot to change its name when copy-pasting. The problem still persists.
    – tomurillo
    Commented Feb 6, 2014 at 12:27
  • FWIW, you should not be using [LANGUAGE_NONE][0]['value'], but field_get_items()
    – Letharion
    Commented Feb 6, 2014 at 17:18

1 Answer 1

3

user_save() itself is likely to be guaranteed atomic. You're running innodb or equivalent, right?

However, you have no guarantees that the user isn't altered between your user_load() and user_save(), which can cause a race condition. There lock.inc for that.

A database-mediated implementation of a locking mechanism.

This is very unlikely though, and if this happens to you often and regularly, you likely have a problem elsewhere in your code.

However, not even that can protect you from other parts of the code, in a parallel request, loading up the user, modifying it, and saving it back, in between your calls. For better or worse, you can't reasonably guarantee that no other piece of code, such as contributed modules, modifies your users.

Suggested options:

  1. Modify the field value directly like UPDATE [table] SET [field] = [field] +/- [value]. Then you can rely on the DB's underlying mechanisms for atomicity.
  2. Accept a small risk of failure. Run a hourly cron job to recalculate values, correcting for mistakes.
  3. Don't use a user field for this data. Either use a custom entity, or directly manage a custom table. Force all updates through a lock mechanism.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.