Code Locking
Locking is an essential piece of software engineering. There are occasions where shared resources must be locked in order to write to them or read from them. This process of locking can be very simple or extremely complex. Sometimes it can lead to deadlocks and serious concurrency issues. Further to say, we will only cover basic usage of the locking constructs in BoxLang.
You can also find great knowledge in the Java Synchronization tutorial: https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html
lock
BoxLang gives you the lock
tag/construct which you can use to ensure the integrity of shared data and it allows you to have two types of locks:
Exclusive - Allows single-thread access to the BoxLang constructs in its body. The tag body can be executed by one request at a time. No other requests can start executing code within the tag while a request has an exclusive lock. BoxLang issues exclusive locks on a first-come, first-served basis.
ReadOnly - Allows multiple requests to access BoxLang constructs within the tag body concurrently. Use a read-only lock only when shared data is read and not modified. If another request has an exclusive lock on shared data, the new request waits for the exclusive lock to be released
Apart from the type of lock, you can also have two different locking strategies:
Named Locking : Where a name is used to identify the locking construct
Scoped Locking: Where you will lock access to a specific BoxLang scope.
Attributes
Here are the attributes to the lock
construct
Attribute
Type
Default
Description
timeout
numeric
required
Max length in seconds to wait to obtain the lock. If lock is obtained, tag execution continues. Otherwise, behavior depends on throwOnTimeout attribute value.
scope
string
Lock scope. Mutually exclusive with the name
attribute. Only one request in the specified scope can execute the code within this tag (or within any other lock tag with the same lock scope scope) at a time. Values are: application, request, server, session
name
string
Lock name. Mutually exclusive with the scope attribute. Only one request can execute the code within a lock
tag with a given name at a time. Cannot be a blank string.
throwOnTimeout
boolean
true
If true and a timeout is reached an exception is thrown, else it is ignored.
type
string
exclusive
readOnly: lets more than one request read shared data. exclusive: lets one request read or write shared data.
Important: Please note that when using named locks, the name is shared across the entire BoxLang server, no matter the application
it is under. Please be aware of it and use unique enough names. Lock names are global to a BoxLang server. They are shared among applications and user sessions, but not clustered servers.
Named Locking
Named locking is the easiest, where access to the construct is by name. Lock names are global to a BoxLang server. They are shared among applications and user sessions, but not clustered servers.
Scoped Locking
Scoped locking will allow you to lock access to a specific BoxLang scope like: application, server, session and request.
You usually do this to synchronize access to variables placed within those scopes. In all essence, scope locking is expensive as it is a BIG lock around the entire scope access. I would suggest to stick to named locks so you can have pin-point accuracy when dealing with code synchronization. Remember that locking can be expensive.
I highly discourage the use of scope locks as it throws a huge locking mechanism around the entire scope access. Tread with caution.
Deadlocks
A deadlock is a state in which no request can execute the locked construct. After a deadlock occurs, neither thread can break it, because all requests to the protected section of the lock are blocked until the deadlock can be resolved by a lock time-out.
The lock
tag/construct uses kernel level synchronization objects that are released automatically upon time out and/or the abnormal termination of the thread that owns them. Therefore, while processing a lock
, the server never deadlocks for an infinite period. However, large time-outs can block request threads for long periods, and radically decrease throughput.
To. prevent this, always use the minimum time-out value. Another cause of blocked request threads is inconsistent nesting of locks
and inconsistent naming of locks. If you nest locks, everyone accessing the locked variables must consistently nest locks
in the same order. Otherwise, a deadlock can occur.
Race Conditions: Double Locking
There will be cases where race conditions will exist and multiple threads will be waiting for access into a lock body construct. Furthermore, you will want that only ONE thread enters the body construct and does something and the rest get ignored. This is a race condition and it must be treated with a double lock approach. What this does is that it evaluated the condition of your body (business logic) and if available then enters the lock.
Let's do a simple example where you only want ONE thread to ever populate the cache with data and return it from a function. Let's see how NOT to do it first:
I am sure that you see this nice function and you are like, well yep it is correct! We check for the existence of the cached data, if null, we load it up and return it. WROOONG!!! This can lead to race conditions where if multiple threads are ALREADY waiting within the lock area, multiple threads can set and re-set the cached data. If we really ONLY want one thread to set the data, we must do a double lock approach:
Last updated