Web Locks API

安全なコンテキスト用: この機能は一部またはすべての対応しているブラウザーにおいて、安全なコンテキスト (HTTPS) でのみ利用できます。

Web Locks API を用いると、1 個のタブまたはワーカーで実行されているスクリプトが非同期でロックを獲得し、処理の実行中保持し、その後解放することができます。ロックが保持されている間は、同じオリジンで実行されている他のスクリプトは同じロックを獲得できず、複数のタブやワーカーで実行されているウェブアプリケーションが処理やリソースの使用を協調して行うことができます。

Web Locks の概念と使用法

ロックは共有されうるリソースを表す抽象概念であり、ウェブアプリケーションが設定する名前によって識別されます。たとえば、複数のタブで実行されるウェブアプリケーションが 1 個のタブでのみネットワークと Indexed DB の間のデータ同期を行うことを保証したい場合、それぞれのタブが "my_net_db_sync" ロックの獲得を試みることができますが、1 個のタブのみが獲得に成功するでしょう。(リーダー選出パターン)

この API は、以下の流れで使用します。

  1. ロックを要求します。
  2. ロックを保持し、非同期のタスクで処理を実行します。
  3. タスクが完了すると、ロックが自動的に解放されます。
js
navigator.locks.request("my_resource", async (lock) => {
  // ロックが獲得された
  await do_something();
  await do_something_else();
  // ここでロックが解放される
});

ロックを確保している間は、同じ実行コンテキストや、別のタブやワーカーからの同じロックの要求はキューに投入されます。ロックが解放されたときのみ、最初にキューに投入された要求がロックを獲得します。

この API は、以下を含む必要に応じて使用できるオプションの機能を提供します。

  • 非同期タスクから値を返す
  • 共有および排他ロックモード
  • 条件付き獲得
  • オリジン内のロックの状態を得て診断を行う
  • デッドロック対策用の避難口

ロックのスコープはオリジン内です。https://example.com 由来のタブで獲得されるロックは、別のオリジンである https://example.org:8080 由来のタブで獲得されるロックには影響しません。

主なエントリーポイントは、ロックを要求する navigator.locks.request() です。このメソッドは、ロックの名前、省略可能なオプション一式、コールバックをとります。コールバックは、ロックが獲得されると呼び出されます。ロックはコールバックから帰ると自動的に解放されるので、通常コールバックには 非同期関数 を用います。この場合、ロックは非同期関数の実行が完全に完了するまで解放されません。

request() 関数自体も、ロックが解放されると解決する Promise を返します。非同期関数内では、呼び出しに await を用いることで非同期のコードの流れを直線的にできます。

例:

js
await do_something_without_lock();

// ロックを要求する
await navigator.locks.request("my_resource", async (lock) => {
  // ロックを獲得した
  await do_something_with_lock();
  await do_something_else_with_lock();
  // ここでロックが解放される
});
// ロックが解放された

await do_something_else_without_lock();

オプション

ロックの要求時、いくつかのオプションを渡すことができます。

  • mode: デフォルトのモードは "exclusive" ですが、"shared" を指定することができます。"exclusive" のロックを保持できるのは 1 個だけですが、"shared" で要求された場合は同時に複数のロックが獲得できます。これにより、readers-writer pattern を実装できます。
  • signal: AbortSignal を渡すことができ、ロックの要求を中止できるようにします。これにより、要求のタイムアウトを実装できます。
  • ifAvailable: 指定された場合は、ロックを待たずにすぐ獲得できない場合はロックの要求が失敗します。コールバックは null とともに呼び出されます。

監視

navigator.locks.query() メソッドにより、スクリプトからオリジンのロックマネージャーの状態を観察できます。これは、ロックが獲得できない理由を特定するなどのデバッグに役立つ可能性があります。結果はロックマネージャーの状態のスナップショットであり、スナップショットが取られた時点での保持されているロック、要求されているロック、それぞれについての追加情報 (モードなど) を特定できます。

高度な使用法

ロックを任意の期間保持するなどのより複雑な場合には、コールバックはスクリプトによって明示的に解決される Promise を返すことができます。

js
// Promise の制御関数を取り込む
let resolve, reject;
const p = new Promise((res, rej) => {
  resolve = res;
  reject = rej;
});

// ロックを要求する
navigator.locks.request(
  "my_resource",
  // ロックを獲得した
  (lock) => p, // これで、ロックは resolve() または reject() を呼ぶまで確保される
);

デッドロック

デッドロックは、それぞれの部分が満たされない要求を待機しているため、処理が進まなくなると発生します。これは、複数のロックがバラバラの順番で要求されるなど、この API の複雑な使用例で起こる可能性があります。タブ 1 がロック A を保持し、タブ 2 がロック B を保持し、さらにタブ 1 がロック B を、タブ 2 がロック A を獲得しようとすると、どちらのリクエストも満たすことはできません。ウェブアプリケーションは、複数の戦略でこれを回避できます。たとえば、ロックの要求を入れ子にしないことを保証する、常に決まった順番で要求を行う、タイムアウトを設けるなどです。なお、このようなデッドロックはそれらのロック自身とそれらに依存するコードのみに影響を与えます。ブラウザー・他のタブ・ページ内の他のスクリプトには影響しません。

インターフェイス

Lock

LockManager.request() のコールバックに渡され、要求したロックの名前とモードを提供します。

LockManager

新しい Lock オブジェクトの要求や、既存の Lock オブジェクトの取得を行うメソッドを提供します。LockManager のインスタンスを得るには、navigator.locks を参照します。

仕様書

Specification
Web Locks API

ブラウザーの互換性

api.LockManager

api.Lock