- Web Locks API: Cross-tab Resource Synchronization
- By Mahdhi Rezvi
- The Nuggets translation Project
- Permanent link to this article: github.com/xitu/gold-m…
- Translator: Raoul1996
- Proofreader: PlusMultiply0, JohnieXu
Web Locks API: Cross-tab resource synchronization
What are Locks?
Computers are becoming more powerful and can use multiple CPU threads to process data. Multiple threads accessing a single resource can suffer from synchronization problems, which raises new issues about resource sharing.
If you’re familiar with threads, you probably know the concept of locking as well. A lock is a synchronization method that enforces data access restrictions on threads, preventing multiple threads from accessing a single resource at the same time. There is also a variant of locking that allows multiple threads to access a single resource at the same time, but still restricts access to read-only. I strongly encourage you to consult the literature to understand the concept of locking in operating systems.
What is the Web Locks API?
The Web Locks API applies the Locks mentioned above to Web applications. This API allows a script to asynchronously hold a lock on a resource until its processing is complete. When a lock is held, other scripts in the same domain cannot acquire the lock on the same resource, except in one special case. So let’s talk about this particular case.
What does the execution process look like?
- To apply for the lock.
- Complete the work when locked in an asynchronous task.
- The lock is automatically released when the task is complete.
- Allow other scripts to apply for locks.
When there is a lock on a resource, if the same execution context or another Tab/Worker script requests the lock on the same resource, the lock request will be queued. When the lock is released, the first request in the queue is granted the lock and can access the resource.
Locks and their scope
The scope of the Web Locks API can be confusing. This is just a summary for you to understand better.
- According to the documentation, the scope of locks also has homologous restrictions.
The locks obtained by Tab from example.com have no effect on the locks obtained by Tab from example.org because they are of different sources.
- A single user configuration in the browser is treated as a separate user agent and is considered outside of scope. Therefore, even though they are homologous, they do not share the lock manager.
- Private mode browsing sessions (incognito mode) are treated as separate user agents and are considered outside of scope. Therefore, even though they are homologous, they do not share the lock manager.
- Scripts in the same origin and context are considered scoped and share the lock manager. For example, two functions on a web page try to acquire a lock on the same resource.
- Open the same origin page and Workers (Agents) sharing the lock manager in the same user agent, even if they are not in the relevant browsing context.
Suppose that script A belongs to lock manager A and script B belongs to lock manager B. Script A attempts to acquire the lock on resource X, successfully acquires the lock and performs the asynchronous task. At the same time, script B also tries to acquire the lock on resource X, which will succeed because the two scripts belong to different lock managers. But if they belong to the same lock manager, there will be a queue to acquire the lock on resource X. I hope I’ve made that point clear.
What are these resources mentioned above?
Well, they represent an abstract resource. It’s just a name we came up with to refer to the resource we want to keep. They have no meaning outside of the scheduling algorithm.
In other words, in the example above, we can think of resource X as the database that stores my data, or as localStorage.
Why is resource coordination important?
Resource coordination is rarely required in simple Web applications. However, complex Web applications that use JavaScript heavily may require resource coordination.
If you are using an application that spans multiple tabs and can perform CRUD operations, you will have to keep tabs in sync to avoid problems. If a user opens a text-editing Web application on one Tab and forgets that the same application is opened on the other Tab. He now has two tabs of the same application running. If he performs an operation on one Tab and tries to perform a completely different operation on another Tab, conflicts may occur on the server when two different processes operate on the same resource. In this case, it is recommended to obtain a lock on the resource and synchronize it.
In addition, there may be a case where the user has two tabs open for the Stock Investing Web application. If a user uses one of the open tabs to buy a certain number of shares, the two tabs must be in sync to avoid the possibility that the customer mistakenly trades again. A simple option is to allow only one Tab or window of the application at a time. Note, however, that you can get around this limitation by using stealth mode.
While you can use apis such as SharedWorker, BroadcastChannel, localStorage, sessionStorage, postMessage, Unload Handler to manage option messages and synchronization, But each has its drawbacks and requires workarounds that reduce the maintainability of the code. The Web Locks API tries to simplify this process by introducing a more standardized solution.
Use the Web Locks API
This API is fairly straightforward to use, but you have to make sure your browser supports it. The request() method is often used to request a lock on a resource.
This method takes three arguments.
- Resource name (the first argument that must be passed in) – string
- Callback (the last parameter that must be passed in) – a callback that will be called when the request succeeds. Suggest transfer
async
Callback, so that it returns a Promise. Even if you don’t pass in an asynchronous callback, it’s wrapped in a Promise. - Option (optional second argument passed before the callback) – an object with a specific property, which we will discuss later.
This method returns a promise that will resolve after the resource is obtained. You can use then.. catch .. Method, or select the await method to write asynchronous code synchronously.
const requestForResource1 = async() = > {if(! navigator.locks) { alert("Your browser does not support the Web Locks API");
return;
}
try {
const result = await navigator.locks.request('resource_1'.async lock => {
// Get the lock
await doSomethingHere();
await doSomethingElseHere();
// Optional return value
return "ok";
// Now the lock is released
});
} catch (exception) {
console.log(exception); }}Copy the code
I believe the code snippet above is self-explanatory. But there are other arguments you can pass to the request() function. Let’s take a look at them.
Optional parameters
model
There are two modes to choose from when requesting locks.
- Mutual exclusion (default)
- Shared
When a mutex is requested on a resource, the lock will not be granted if the resource already holds a “mutex” or “shared” lock. However, if a “shared” lock is requested on a resource that holds a “shared” lock, the request will be approved. However, this is not the case when the locks held are “mutually exclusive” locks. The request will be queued by the lock manager. The following table summarizes this.
signal
The signal property passes in an abort signal. This allows a lock request in the queue to be aborted. A timeout can be used to abort a lock request if it is not approved within a specific time period.
const controller = new AbortController();
setTimeout(() = > controller.abort(), 400); // wait up to 400ms
try {
await navigator.locks.request('resource1', {signal: controller.signal}, async lock => {
await doSomethingHere();
});
} catch (ex) {
/ / | ex | if the timer is triggered, would be a mistake in DOMException, name for "AbortError".
}
Copy the code
ifAvailable
This property is a Boolean value that defaults to false. If true, lock requests are granted only when no queuing is required. In other words, the lock request will only be granted if there is no other wait, otherwise NULL will be returned.
Note, however, that when null is returned, this function will not execute synchronously. Instead, the callback will receive the value NULL, which can be processed by the developer.
await navigator.locks.request('resource', {ifAvailable: true}, async lock => {
if(! lock) {// Do not obtain the lock
return;
}
// If you get the lock, go here
});
Copy the code
The risks of using locks
There are many risks associated with applying locks. These are more or less due to the concept of locking itself, not to any errors in the API.
A deadlock
The concept of deadlocks is associated with concurrency. A deadlock occurs when a process cannot continue because each part is waiting for an unfulfilled request.
To avoid deadlocks, we must follow a strict pattern when acquiring locks. There are a number of techniques you can use, such as avoiding nested locks, ensuring that locks are in the correct order, and even using the optional parameter Signal to timeout lock requests. After all, a well-designed API would be the best solution to avoid deadlocks. You can read more about deadlock prevention here.
Tabs no response
In some cases, you may find that one of the tabs becomes unresponsive. If this happens while the TAB is locked, it can be very tricky. You can introduce a Steal Boolean as part of the optional arguments we talked about earlier. Although the default value is false, if passed to true, the lock on any resource is immediately released, regardless of the number of lock requests in the resource queue, and the new lock request is granted immediately.
Keep in mind, however, that this feature in question should only be used in exceptional circumstances. You can find it at [here] (github.com/WICG/web-lo…
The Debug difficulties
Because there may be hidden states, there may be debugging related issues. To solve this problem, the Query method was introduced. This method returns the state of the lock manager at that particular time. The result will contain details of the wait and currently held locks. It also contains the lock type, the resource held/requested to the lock, and the requested clientId details.
ClientId is simply the value corresponding to the only context (frame/worker) for which the lock is requested. The same values are available in Service Workers.
{
"held": [{"clientId": "da2deeaa-8bac-4d1d-97e7-6b1ee46b6730"."mode": "exclusive"."name": "resource_1"}]."pending": [{"clientId": "da2deeaa-8bac-4d1d-97e7-6b1ee46b6730"."mode": "shared"."name": "resource_1"
},
{
"clientId": "76384678-c5b6-452e-84f0-00c2ef65109e"."mode": "exclusive"."name": "resource_1"
},
{
"clientId": "76384678-c5b6-452e-84f0-00c2ef65109e"."mode": "shared"."name": "resource_1"}}]Copy the code
You must also note that the application should not use this method to determine whether the lock is held/requested, because this output contains the lock manager state for the specific moment I mentioned earlier. This means that locks can be released when your code makes a decision.
Browser compatibility
One of the disadvantages of this API is browser compatibility. Although there was polyfill for unsupported browsers, it was later removed.
The Web Locks API is a very useful feature with multiple usage scenarios, which makes it a very important example. However, its limited support can discourage developers from learning and using it. However, given the impact this API will have on modern Web applications, I personally think it is important for Web developers to understand the use of this new capability. Also, since the API is experimental, you can expect it to change in the future.
Go to this simple demo to get hands-on experience on how it works. You can view the source code here.
Resources
- MDN Docs
- Web Locks Explainer
If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.
The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.