Concurrent Usage prevention with Locks

Concurrent Usage prevention with Locks

In this blog post we will go over an example that deals with preventing concurrent usage of particular resources like entities through the different CUBA mechanisms of locking.

The complete example can be found at Github:

mariodavid/cuba-example-concurrent-usage-prevention

Sometimes certain resources of an application like a particular entity or even more general a particular part of the application should only be accessed by one particular user at a time in order to prevent the following situation:

Scenario: Lost update of Customer data for Zapp Brannigan

  1. 10:30: Customer Zapp Brannigan sends an email in order to inform about his marriage with Kif Kroker. Therefore he wants to update his last name to Kroker-Brannigan
  2. 10:35: User leia reads the email and opens the Customer screen from Zapp Brannigan. Directly after that her boss - Jabba the Hutt calls her in for an urgent task. She leaves the customer details screen open.
  3. 10:40: Zapp calls the hotline, because the marriage did not last long and is already gone. But as he found this lovely girl Amy Wong - he now wants to update his name to Wong because they are in love. User luke accepts the request as he does not know anything about the previous email, opens the Customer screen and changes the name to Zapp Wong.
  4. 10:50: User leia is back on her desk and wants to finish her task of updating Zapp Brannigan to Bareny Kroker-Brannigan. As she has the customer screen already open, she just changes the name and saves it.

Let’s look at the data that has been stored in the system:

  1. 10:30 - Customer Zapp Brannigan
  2. 10:40 - Customer Zapp Wong
  3. 10:50 - Customer Zapp Kroker-Brannigan

As we have seen from the interactions with Zapp, what is finally stored in the system is wrong and does not reflect the real world as the final name should be Zapp Wong instead of Zapp Kroker-Brannigan.

In order to solve this scenario, there are some established techniques. CUBA offers the following options:

  • optimistic locking
  • pessimistic locking
  • entity log

In this example we will go through the different locking options.

Example Overview

Here is an overview of the functionality of the different examples for the different locking solutions within a CUBA application:

Example overview

Optimistic locking

Optimistic locking is enabled by default for all entities in CUBA and is the standard solution to this problem. The way it works looks like this:

In this example it is implemented for the entity: Product. Product contains an attribute called version which is a counter. It is incremented each time a particular entity is created / updated.

When a user opens the product details screen in order to update a particular product, the current value of the version attribute is also received in the screen. In case of the above example with the Customer entity, an optimistic locking would look like this:

  1. before 10:30: Customer Zapp Brannigan - version 1
  2. at 10:35: leia reads the customer with version 1
  3. at 10:40: luke reads the customer with version 1 and stores the customer. The version attribute is updated to value 2.
  4. at 10:50: leia wants to write the customer with the one she read at 10:35 with version 1. On storing the new data for the Customer CUBA will throw a OptimisticLockException, because the version in the DB is already 2 (from 10:35) and now leia wants to store information with a current version of 1. This prevents leia from storing the values based on old data.

The resulting UI for an optimistic locking exception looks like this:

Automatic optimistic entity lock

The user has to manually reload the customer and based on the new data decide which action to take and ultimately which data to persist.

The reason why it is called optimistic is because this strategy is optimistic in the sense that concurrent reads are treated as a normal interaction. Only at the last possible point it throws an exception to prevent potential wrong data storage.

The difference to the pessimistic locking is that optimistic locking allows to open different reads of the resource and also leads the user to believe that concurrent updates would be possible by the fact that CUBA shows the normal screen editor for the entity without any restrictions.

Only if the second user actually does a concurrent change, the system will prevent this. For the user though, it oftentimes feels like Bender in this gif.

The problem is that once the system returns this error, the user is not expecting such behavior and now is in the situation to figure out which person has already done another change without having more detailed information about it.

This is why optimistic locking sometimes can be an undesired behavior.

More information on optimistic locking can be found here:

Pessimistic locking

Sometimes the optimistic locking approach is not appropriate because the system should not lead the user to believe that such operations are possible. In this case the pessimistic locking approach is better suited.

The user is informed upfront that another user is currently doing the wished operation (like changing the customer). This enforces the user to deal with a potential problem upfront.

In the above Zapp Brannigan scenario, luke would have been informed, when opening the Customer editor screen, with the information that leia is already trying to change the customer, and would prevent luke from changing the customer all together.

This approach is a little more “secure” in the sense that it notifies the user upfront. But also leads to a lot of “false negatives”.

This oftentimes leads to the situation where you have a lot of frustrated users that actually just wanted to look at the data and then decide if something needs to be changed. But for all cases, the system informs them about the read-only mode. So it is wise to use pessimistic locking where necessary in order to not let your users feel like Fry here.

Automatic pessimistic locking for entity editors

CUBA offers automatic pessimistic locking for entity editors. It sets the editor in a read-only mode so that no changes to the entity are possible once one user opened the editor.

In order to activate this behavior, a runtime configuration needs to be in place on a per-entity basis. CUBA offers a management UI under Administration > Locks > Setup:

Current locks

The resulting UI of the automatic pessimistic locking approach for the customer editor looks like this:

Automatic pessimistic entity lock screen

Overview of existing locks

Normally locks are released once the operation is done. For the automatic pessimistic locking of entity editors in CUBA happens when the user closes the editor screen. Unfortunately releasing the lock is not always possible, because the user does not close the editor and instead e.g. closes the laptop completely. This leads to open locks, that are not released.

Therefore the lock configuration has to have a timeout configured. After that the system will automatically resolve the locks in order to prevent locking of particular data forever.

CUBA additionally allows the administrator of the system to look at the current locks and also release them manually in case such scenario occurs. This is useful when the timeout of the lock is not yet over, but the user wants to get access to the data.

Currently active locks

Custom pessimistic locking

It is also possible to use custom pessimistic locks programmatically. This is sometimes necessary when a lock should not be based on an entity instance. It also requires to configure a lock via the CUBA management UI. In this example it is configured for the name customer-support-ticket.

Lock configuration

For programmatically accessing the pessimistic locking functionality of CUBA, there are two interaction points: LockManagerAPI for the backend as well as LockService for the web layer (e.g. UI controllers). In the CustomerSupportTicket screen, the LockService is used in order to prevent creating support tickets for the same customer. It is done via the custom pessimistic lock with the name customer-support-ticket: lockService.lock(CUSTOMER_SUPPORT_LOCK_NAME, customerId(customer)).

public class CustomerSupportTicket extends AbstractWindow {

  private final String CUSTOMER_SUPPORT_LOCK_NAME = "customer-support-ticket";

  @Inject
  protected LockService lockService;

  @WindowParam
  Customer customer;


  @Override
  public void ready() {

    LockInfo customerSupportTicketLock = tryToAcquireCustomerSupportTicketLock(customer);

    if (customerSupportTicketIsAlreadyLocked(customerSupportTicketLock)) {
      showCustomerSupportTicketLockedWarning(customerSupportTicketLock);
      close(CLOSE_ACTION_ID, true);
    }
  }

  private void showCustomerSupportTicketLockedWarning(LockInfo lockInfo) {
    String currentlyUserHavingLock = lockInfo.getUser()
        .getInstanceName();
    String message = "Customer Support Ticket is already in use by: " + currentlyUserHavingLock;
    showNotification(message, NotificationType.WARNING);
  }

  private boolean customerSupportTicketIsAlreadyLocked(LockInfo customerLock) {
    return customerLock != null;
  }

  private LockInfo tryToAcquireCustomerSupportTicketLock(Customer customer) {
    return lockService.lock(CUSTOMER_SUPPORT_LOCK_NAME, customerId(customer));
  }

  private void unlockCustomerSupportTicket(Customer customer) {
    lockService.unlock(CUSTOMER_SUPPORT_LOCK_NAME, customerId(customer));
  }

  @Override
  protected boolean preClose(String actionId) {
    unlockCustomerSupportTicket(customer);

    return true;
  }


  public void printInteraction() {
    showNotification("Ticket send to printer...");
  }

}

The resulting UI in case of simultaneous access looks like this:

Custom lock error message

Summary

This example should show the different options that are available when it comes to preventing the concurrent use of resources. CUBA supports both optimistic and pessimistic locking.

Optimistic locking is the default behavior and will notify the user once an actual attempt to write the concurrent changes by multiple users occurs.

For pessimistic locking, a configuration has to be set. After that the default CUBA editor screen will go into a read-only mode in case multiple users are opening the same entity editor concurrently.

CUBA also offers API to programmatically use pessimistic locking so that it can be used in non-entity scenarios.

Which one of these two solutions to use highly depends on the use case, how the data is structured, how the UI looks like etc. With that article, you hopefully have enough information about the pros and cons of both approaches.

Mario David

Mario David
Software developer with passion on agile, web and fast development, blogger, father, family guy

CUBA 7 Release Party

In this blog post let's celebrate the long awaited big major release 7.0 of CUBA which brings great polished APIs and updates of the underlying libraries. Continue reading

CUBA on Kubernetes - Part 2

Published on December 28, 2018

CUBA on Kubernetes - Part 1

Published on December 19, 2018