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:
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
Zapp Brannigansends an email in order to inform about his marriage with
Kif Kroker. Therefore he wants to update his last name to
leiareads the email and opens the Customer screen from
Zapp Brannigan. Directly after that her boss -
Jabba the Huttcalls her in for an urgent task. She leaves the customer details screen open.
Zappcalls 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
Wongbecause they are in love. User
lukeaccepts the request as he does not know anything about the previous email, opens the Customer screen and changes the name to
leiais back on her desk and wants to finish her task of updating
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:
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
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.
Here is an overview of the functionality of the different examples for the different locking solutions within a CUBA application:
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 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:
Zapp Brannigan- version
leiareads the customer with version
lukereads the customer with version
1and stores the customer. The version attribute is updated to value
leiawants to write the customer with the one she read at
1. On storing the new data for the Customer CUBA will throw a
OptimisticLockException, because the version in the DB is already
10:35) and now
leiawants to store information with a current version of
1. This prevents
leiafrom storing the values based on old data.
The resulting UI for an optimistic locking exception looks like this:
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:
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:
The resulting UI of the automatic pessimistic locking approach for the customer editor looks like this:
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.
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
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
The resulting UI in case of simultaneous access looks like this:
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.