Concurrency occurs when different processes or applications need the same resource to continue to work. To understand when this happens and how to deal with these issues, we need to talk about execution contexts, racing conditions, inconsistent reads, deadlocks and transactions.
Processing always occurs in some kind of context, usually more than one. An enterprise application will usually have Requests and Sessions both as a server to clients ans as a client to other applications like a DB server or a 3rd party application API.
A single short term call from the outside world to the application, which may result in it returning a response.
A long term interaction between client and server, usually consisting of a series of requests.
A heavyweight context that provides isolation to the data it works on.
A lightweight context that is set up so that several threads are ran in a single process. They support multiple requests in a single process, helping to make good usage of resources. However, because they run in a single process, they share memory, which leads to concurrency problems.
A transaction groups together several Requests that the application wants to treat as a single Request. We can have system transactions (ie. connecting to a DB) or business transactions (ie. a user interacting with the application)
Concurrency problems occur when more than one active agent modifies the same data at the same time. These are called racing conditions.
By only allowing one active agent to modify a specific portion of data at any given time, we avoid concurrency issues. This can be accomplished by using a lock to inform the application that a specific set of data only be opened for reading.
By identifying and flagging data sets that can not be changed we reduce the scope where concurrency issues might occur.
- Pessimistic concurrency control
When 2 users want to edit a file at the same time, only the one who opens the file first can edit the file. The second user can only edit the file when the first user is finished with it. This prevents concurrency problems.
- Optimistic concurrency control
In the same situation as above, both the users would be able to edit the file, but only the first one to finish would be able to save it immediately. The second user would have to merge his changes with the new version of the file before he can save his work.
An inconsistent read happens when a user reads some data and that data is changed by another user while he is still using the data. The data he read and is still using, is then inconsistent.
- Pessimistic lock solution
Using a pessimistic lock we need to acquire a shared lock for reading and an exclusive lock for writing. However, the rules to acquire a lock are:
- Any number of shared locks can be acquired at any given time, as long as there is no exclusive lock acquired.
- An exclusive lock can be acquired at any given time, as long as no other lock (shared or exclusive) has been acquired.
- Optimistic lock solution
With an optimistic lock, every piece of data has a version tag on it. When making an update, the system checks for differences in data versions. If a difference is detected, a conflict is raised.
A deadlock happens when two processes have locks on different resources and suddenly they both require each others locked resources in order to finish their job and releasing their lock. This means they would both be waiting for each others locks to be released.
- Victim through detection
When we detect a deadlock, we can choose one of the processes to be forces tu terminate so the other can continue. This means the victim process would stop and lose his state.
- Victim through timed process
Detecting a deadlock is very difficult. An easier solution is to give a time linit to a process, after which the process will be killed. This means the victim process would stop and lose his state even though maybe there was no deadlock.
- Preventive lock
To prevent deadlocks we can force processes to acquire all necessary locks in the beginning and prevent them acquiring more.
- Automatic victim
We can also make it so that when a process tries to acquire a lock on an already locked resource, it automatically becomes a victim.
- Mixed strategies
We can adopt an extreme solution, by mixing different strategies. For example using preventive locks and also timed processes.
A transaction is what guarantees that a sequence of actions are all successfully and fully executed, or none is.
All actions in a transaction are guaranteed to complete successfully or none is executed
A system resources must be in a consistent, non corrupt state at the start and end of a transaction.
The result of a transaction is only visible when the whole transaction is finished.
The result of a transaction must be permanent.
- Long transaction
Spans several requests
- Request transaction
Always starts in the beginning of a request and finishes in the end of the request.
- Late transaction
As a request transaction, but it is open as late as possible, allowing the reads to be made outside of the transaction.
- System transaction
A transaction that operates at the infrastructure level, usually when communicating to the DB.
- Business transaction
A transaction that operates at a use case level, ie. encompasses several user screens from the beginning of the use case flow until its end.