Web Locks API

W3C 작업 초안,

이 문서에 대한 자세한 정보
이 버전:
https://www.w3.org/TR/2025/WD-web-locks-20250924/
최신 공개 버전:
https://www.w3.org/TR/web-locks/
에디터스 드래프트:
https://w3c.github.io/web-locks/
이전 버전:
히스토리:
https://www.w3.org/standards/history/web-locks/
테스트 스위트:
https://github.com/web-platform-tests/wpt/tree/master/web-locks
피드백:
GitHub
명세 내 인라인
에디터:
(Mozilla)
이전 에디터:
(Google Inc.)

요약

이 문서는 스크립트가 리소스에 대한 잠금을 비동기적으로 획득하고, 작업을 수행하는 동안 이를 유지한 후, 다시 해제할 수 있도록 하는 웹 플랫폼 API를 정의합니다. 잠금이 유지되는 동안, 동일한 오리진의 다른 스크립트는 해당 리소스에 대한 잠금을 획득할 수 없습니다. 이를 통해 웹 애플리케이션 내의 컨텍스트(윈도우, 워커)들이 리소스 사용을 조율할 수 있습니다.

이 문서의 상태

이 섹션은 출판 시점에서 이 문서의 상태를 설명합니다. 현재 W3C의 출판 목록과 최신 기술 보고서 개정본은 W3C 표준 및 초안 색인에서 확인할 수 있습니다.

이 문서는 웹 애플리케이션 작업 그룹에서 작업 초안 형태로 권고안 트랙을 따라 발행되었습니다. 작업 초안으로서의 발행은 W3C와 회원사의 지지를 의미하지 않습니다.

이 문서는 초안이며 언제든지 업데이트, 교체 또는 폐기될 수 있습니다. 진행 중인 작업 외의 용도로 이 문서를 인용하는 것은 적절하지 않습니다.

이 문서는 W3C 특허 정책에 따라 운영되는 그룹에 의해 작성되었습니다. W3C는 그룹 산출물과 관련하여 공개된 특허 공개 목록을 유지합니다. 해당 페이지에는 특허 공개 방법도 포함되어 있습니다. 특정 특허가 필수 청구항을 포함한다고 실제로 알고 있는 개인은 W3C 특허 정책 6절에 따라 정보를 공개해야 합니다.

이 문서는 2025년 8월 18일 W3C 프로세스 문서에 따라 관리됩니다.

1. 소개

이 섹션은 규범적이지 않습니다.

스크립트는 특정 잠금 요청리소스 이름모드에 대해 수행합니다. 스케줄링 알고리즘은 현재와 이전 요청의 상태를 확인하고, 결국 잠금 요청을 승인합니다. 잠금은 승인된 요청이며, 리소스 이름모드를 가집니다. 이는 스크립트에 반환되는 객체로 표현됩니다. 잠금이 유지되는 동안에는(이름과 모드에 따라) 다른 잠금 요청의 승인이 제한될 수 있습니다. 스크립트가 잠금을 해제하면, 다른 잠금 요청이 승인될 수 있습니다.

API는 필요에 따라 사용할 수 있는 선택적 기능을 제공합니다:

협력적 조율은 에이전트스토리지 버킷을 공유하는 범위 내에서 이루어지며, 이는 여러 에이전트 클러스터에 걸쳐 있을 수 있습니다.

참고: 에이전트는 대략적으로 윈도우(탭), iframe, 워커에 해당합니다. 에이전트 클러스터는 일부 사용자 에이전트 구현에서 독립적인 프로세스에 해당합니다.

1.1. 사용 개요

API는 다음과 같이 사용됩니다:

  1. 잠금이 요청됩니다.

  2. 비동기 작업에서 잠금을 유지한 채로 작업을 수행합니다.

  3. 작업이 완료되면 잠금이 자동으로 해제됩니다.

API 기본 사용 예시는 다음과 같습니다:

navigator.locks.request('my_resource', async lock => {
   // 잠금이 획득되었습니다.
   await do_something();
   await do_something_else();
   // 이제 잠금이 해제됩니다.
});

비동기 함수 내에서는 요청 자체를 await할 수 있습니다:

// 잠금 요청 전에.
await navigator.locks.request('my_resource', async lock => {
   // 잠금이 획득되었습니다.
   await do_something();
   // 이제 잠금이 해제됩니다.
});
// 잠금이 해제된 후

1.2. 동기 부여 사례

웹 기반 문서 편집기는 빠른 접근을 위해 메모리에 상태를 저장하고, 복원력 및 오프라인 사용을 위해 Indexed Database API와 같은 스토리지 API에(일련의 레코드로) 변경사항을 영속화하며, 교차 기기 사용을 위해 서버에도 저장합니다. 동일한 문서를 두 개의 탭에서 동시에 편집할 때 작업은 탭 간에 조율되어야 하며, 한 번에 하나의 탭만 문서를 변경하거나 동기화할 수 있어야 합니다. 이는 어떤 탭이 적극적으로 변경(및 메모리 상태를 스토리지 API와 동기화)할지 조율하고, 활성 탭이 사라질 때(네비게이션, 닫힘, 크래시) 다른 탭이 활성화될 수 있도록 해야 합니다.

데이터 동기화 서비스에서는 "주 탭"이 지정됩니다. 이 탭만 일부 작업(예: 네트워크 동기화, 대기 데이터 정리 등)을 수행해야 하며, 잠금을 보유하고 절대 해제하지 않습니다. 다른 탭은 잠금 획득을 시도할 수 있고, 이러한 시도는 큐에 대기됩니다. "주 탭"이 크래시나 종료되면, 다른 탭 중 하나가 잠금을 얻어 새로운 주 탭이 됩니다.

Indexed Database API는 오리진 내 여러 이름이 지정된 스토리지 파티션 간에 공유 읽기 및 단독 쓰기 접근을 허용하는 트랜잭션 모델을 정의합니다. 이 개념을 프리미티브로 노출함으로써, 어떤 웹 플랫폼 활동도 리소스 사용 가능성에 따라 스케줄링할 수 있으며, 예를 들어 다른 스토리지 타입(캐시 [Service-Workers])에 대해 트랜잭션을 합성하거나, 스토리지 타입을 넘어서거나, 심지어 스토리지가 아닌 API(예: 네트워크 fetch)에 적용할 수 있습니다.

2. 개념

이 명세의 목적상:

사용자 에이전트잠금 작업 큐를 가지며, 이는 새로운 병렬 큐 시작의 결과입니다.

아래의 작업 소스단계 대기열화에 대해 웹 잠금 작업 소스입니다.

2.1. 리소스 이름

리소스 이름은 웹 애플리케이션이 추상적 리소스를 나타내기 위해 선택한 JavaScript 문자열입니다.

리소스 이름은 스케줄링 알고리즘 외부에는 의미가 없으나, 에이전트스토리지 버킷을 공유할 때 전역적입니다. 웹 애플리케이션은 어떤 리소스 명명 규칙도 자유롭게 사용할 수 있습니다.

[IndexedDB-2]의 이름이 지정된 데이터베이스 내 이름이 지정된 스토어에 대해 트랜잭션 잠금 기능을 모방하려면, 스크립트가 리소스 이름을 다음과 같이 합성할 수 있습니다:
encodeURIComponent(db_name) + '/' + encodeURIComponent(store_name)

U+002D 하이픈 마이너스(-)로 시작하는 리소스 이름은 예약되어 있습니다. 이를 요청하면 예외가 발생합니다.

2.2. 잠금 관리자

잠금 관리자잠금잠금 요청의 상태를 캡슐화합니다. 각 스토리지 버킷은 웹 잠금 API를 위한 연결된 스토리지 병을 통해 하나의 잠금 관리자를 포함합니다.

참고: 동일한 사용자 에이전트에서 동일한 스토리지 버킷을 공유하는 페이지와 워커(에이전트)는 서로 관련 없는 브라우징 컨텍스트에 있더라도 잠금 관리자를 공유합니다.

잠금 관리자 획득을 위해 환경 설정 객체 environment가 주어지면 다음 단계를 수행합니다:
  1. map로컬 스토리지 병 맵 획득의 결과로 설정합니다. 인자로는 environment와 "web-locks"를 전달합니다.

  2. map이 실패라면, 실패를 반환합니다.

  3. bottlemap의 연결된 스토리지 병으로 설정합니다.

  4. bottle의 연결된 잠금 관리자를 반환합니다.

여기서 [Storage]와의 통합을 다듬고, 주어진 환경에서 잠금 관리자를 올바르게 가져오는 방법을 포함해야 합니다.

2.3. 모드 및 스케줄링

모드는 "exclusive" 또는 "shared"입니다. 모드는 일반적인 reader-writer lock 패턴을 모델링할 수 있습니다. "exclusive" 잠금이 유지되면, 해당 이름의 다른 잠금은 승인될 수 없습니다. "shared" 잠금이 유지되면, 동일 이름의 다른 "shared" 잠금은 승인될 수 있지만, "exclusive" 잠금은 승인되지 않습니다. API의 기본 모드는 "exclusive"입니다.

추가 속성이 스케줄링에 영향을 줄 수 있으며, 예를 들어 타임아웃이나 공정성 등이 있습니다.

2.4. 잠금

잠금은 공유 리소스에 대한 독점적 접근을 나타냅니다.

잠금agent를 가지며, 이는 에이전트입니다.

잠금clientId를 가지며, 이는 불투명한 문자열입니다.

잠금manager를 가지며, 이는 잠금 관리자입니다.

잠금name을 가지며, 이는 리소스 이름입니다.

잠금mode를 가지며, "exclusive" 또는 "shared" 중 하나입니다.

잠금waiting promise를 가지며, 이는 Promise입니다.

잠금released promise를 가지며, 이는 Promise입니다.

잠금의 라이프사이클에는 두 개의 프로미스가 연결되어 있습니다:
  • 잠금이 승인될 때 콜백이 암묵적 또는 명시적으로 제공하는 프로미스로, 잠금이 얼마나 오랫동안 유지되는지 결정합니다. 이 프로미스가 해결되면 잠금이 해제됩니다. 이는 잠금의 waiting promise로 불립니다.

  • LockManagerrequest() 메서드가 반환하는 프로미스로, 잠금이 해제되거나 요청이 중단될 때 해결됩니다. 이는 잠금의 released promise로 불립니다.

const p1 = navigator.locks.request('resource', lock => {
  const p2 = new Promise(r => {
    // 잠금을 사용하고 프로미스를 해결하는 로직...
  });
  return p2;
});

위 예시에서 p1released promise이고, p2waiting promise입니다. 대부분의 코드에서는 콜백이 async 함수로 구현되어 반환 프로미스가 암묵적으로 처리됩니다. 아래 예시처럼:

const p1 = navigator.locks.request('resource', async lock => {
  // 잠금을 사용하는 로직...
});

위 코드에서는 waiting promise가 명시적으로 이름 붙여지지 않았지만, 익명 async 콜백의 반환값으로 존재합니다. 또한 콜백이 async가 아니고 프로미스가 아닌 값을 반환하면, 해당 값은 즉시 해결되는 프로미스로 래핑됩니다. 잠금은 다음 마이크로태스크에서 해제되며, released promise도 이후 마이크로태스크에서 해결됩니다.

잠금 관리자held lock set을 가지며, 이는 set잠금입니다.

잠금 lockwaiting promise가 해결(fulfilled 또는 rejected)될 때, 다음 단계들을 잠금 작업 큐에 대기열화합니다:

  1. 잠금 해제 lock.

  2. Resolve lockreleased promiselockwaiting promise로 해결합니다.

2.5. 잠금 요청

잠금 요청잠금에 대한 보류 중인 요청을 나타냅니다.

잠금 요청구조체이며, 항목 agent, clientId, manager, name, mode, callback, promise, 그리고 signal을 포함합니다.

잠금 요청 큐로, 잠금 요청들이 들어 있습니다.

잠금 관리자잠금 요청 큐 맵을 가지며, 이는 으로, 리소스 이름에서 잠금 요청 큐로 매핑됩니다.

잠금 요청 큐 맵 queueMap에서 리소스 이름 name에 대한 잠금 요청 큐 가져오기 단계는 다음과 같습니다:

  1. queueMap[name]이 존재하지 않으면, 새 빈 잠금 요청 큐queueMap[name]을 설정합니다.

  2. queueMap[name]을 반환합니다.

잠금 요청 requestgrantable로 간주되려면 다음 단계가 true를 반환해야 합니다:

  1. managerrequestmanager로 설정합니다.

  2. queueMapmanager잠금 요청 큐 맵으로 설정합니다.

  3. namerequestname으로 설정합니다.

  4. queue잠금 요청 큐 가져오기의 결과로 queueMap에서 name에 대해 얻습니다.

  5. heldmanagerheld lock set으로 설정합니다>

  6. moderequestmode로 설정합니다>

  7. queue비어 있지 않고 requestqueue의 첫 번째 항목이 아니면, false를 반환합니다.

  8. mode가 "exclusive"이면, held잠금namename과 같은 잠금이 없으면 true를, 그렇지 않으면 false를 반환합니다.

  9. 그 외의 경우, mode가 "shared"이면, held잠금mode가 "exclusive"이고 namename과 같은 잠금이 없으면 true를, 그렇지 않으면 false를 반환합니다.

2.6. 잠금 종료

문서 언로드 클린업 단계document로 실행될 때, 해당 에이전트agent에 대해 남아있는 잠금과 요청을 종료합니다.

agent가 종료될 때, 해당 에이전트의 남아있는 잠금과 요청을 종료합니다.

현재 이는 워커에만 적용되며, 워커 종료 시 단계를 실행하는 규범적 방법이 없어 정의가 모호합니다.

남아있는 잠금과 요청 종료agent로 하려면, 다음 단계를 잠금 작업 큐에 대기열화합니다:

  1. 잠금 요청 requestagentagent와 같은 모든 요청에 대해:

    1. 요청을 중단 request.

  2. 잠금 lockagentagent와 같은 모든 잠금에 대해:

    1. 잠금을 해제 lock.

3. API

[SecureContext]
interface mixin NavigatorLocks {
  readonly attribute LockManager locks;
};
Navigator includes NavigatorLocks;
WorkerNavigator includes NavigatorLocks;

환경 설정 객체LockManager 객체를 가집니다.

locks getter의 단계는 this관련 설정 객체LockManager 객체를 반환하는 것입니다.

3.2. LockManager 클래스

[SecureContext, Exposed=(Window,Worker)]
interface LockManager {
  Promise<any> request(DOMString name,
                       LockGrantedCallback callback);
  Promise<any> request(DOMString name,
                       LockOptions options,
                       LockGrantedCallback callback);

  Promise<LockManagerSnapshot> query();
};

callback LockGrantedCallback = Promise<any> (Lock? lock);

enum LockMode { "shared", "exclusive" };

dictionary LockOptions {
  LockMode mode = "exclusive";
  boolean ifAvailable = false;
  boolean steal = false;
  AbortSignal signal;
};

dictionary LockManagerSnapshot {
  sequence<LockInfo> held;
  sequence<LockInfo> pending;
};

dictionary LockInfo {
  DOMString name;
  LockMode mode;
  DOMString clientId;
};

LockManager 인스턴스는 스크립트가 잠금 요청을 하고 잠금 관리자의 상태를 조회할 수 있도록 합니다.

3.2.1. request() 메서드

promise = navigator . locks . request(name, callback)
promise = navigator . locks . request(name, options, callback)

request() 메서드는 lock을 요청할 때 호출됩니다.

name (첫 번째 인자)은 리소스 이름 문자열입니다.

callback (마지막 인자)은 lock이 부여될 때 Lock과 함께 호출되는 콜백 함수입니다. 이 함수는 스크립트에서 지정하며, 일반적으로 async 함수입니다. lock은 콜백 함수가 완료될 때까지 유지됩니다. 비동기 함수가 아닌 콜백이 전달되면 즉시 resolve되는 promise로 자동 래핑되어, lock은 동기 콜백이 실행되는 동안만 유지됩니다.

반환된 promise는 lock이 해제된 후 콜백의 결과로 resolve(또는 reject)되며, 요청이 중단되면 reject됩니다.

예시:

try {
  const result = await navigator.locks.request('resource', async lock => {
    // 여기서 lock이 유지됩니다.
    await do_something();
    await do_something_else();
    return "ok";
    // 이제 lock이 해제됩니다.
  });
  // |result|는 콜백의 반환값입니다.
} catch (ex) {
  // 콜백에서 예외가 발생하면 여기서 잡힙니다.
}

lock은 콜백이 어떤 이유로든 종료될 때 — 코드가 반환되거나 예외가 발생할 때 — 해제됩니다.

options 딕셔너리를 두 번째 인자로 지정할 수 있으며, callback 인자는 항상 마지막입니다.

options . mode

mode 옵션은 "exclusive" (명시하지 않으면 기본값) 또는 "shared"가 될 수 있습니다. 여러 탭/워커가 "shared" 모드에서는 동일 리소스에 대해 lock을 동시에 가질 수 있지만, "exclusive" 모드에서는 오직 하나의 탭/워커만 lock을 가질 수 있습니다.

가장 일반적인 용도는 여러 리더가 동시에 리소스에 접근하도록 허용하되, 변경은 막는 것입니다. 리더 lock이 모두 해제되면 단일 writer가 lock을 획득해 변경을 수행할 수 있고, 이후 또 다른 writer 또는 여러 리더가 lock을 획득할 수 있습니다.

await navigator.locks.request('resource', {mode: 'shared'}, async lock => {
  // 여기서 lock이 유지됩니다. 다른 컨텍스트도 shared 모드에서는 lock을 가질 수 있지만, exclusive 모드에서는 불가능합니다.
});
options . ifAvailable

ifAvailable 옵션이 true이면, lock이 추가 대기 없이 바로 부여될 수 있을 때만 grant됩니다. 단, 이것이 동기적인 것은 아니며, 많은 user agent에서는 lock 가능 여부를 확인하기 위해 프로세스 간 통신이 필요할 수 있습니다. lock을 grant할 수 없으면 콜백이 null로 호출됩니다. (이 경우는 예상된 상황이므로 요청이 reject되지 않습니다.)

await navigator.locks.request('resource', {ifAvailable: true}, async lock => {
  if (!lock) {
    // 획득하지 못함. 적절한 처리를 할 수 있습니다.
    return;
  }
  // 여기서 lock이 유지됩니다.
});
options . signal

signal 옵션은 AbortSignal로 설정할 수 있습니다. 이를 통해 lock 요청을 중단할 수 있습니다. 예를 들어, lock이 적시에 grant되지 않을 경우:

const controller = new AbortController();
setTimeout(() => controller.abort(), 200); // 최대 200ms 대기.

try {
  await navigator.locks.request(
    'resource', {signal: controller.signal}, async lock => {
      // 여기서 lock이 유지됩니다.
  });
  // 여기서 lock 사용 완료.
} catch (ex) {
  // 타이머가 동작하면 |ex|는 "AbortError" 이름의 DOMException입니다.
}

lock이 grant되기 전에 abort가 신호되면, 요청 promise는 AbortError로 reject됩니다. lock이 이미 grant된 후에는 signal은 무시됩니다.

options . steal

steal 옵션이 true이면, 해당 리소스에 대해 이미 유지 중인 lock이 모두 해제되고(해당 lock의 released promiseAbortError로 resolve됨), 요청이 grant되어 대기 중인 요청을 선점합니다.

웹 애플리케이션이 복구 불가능한 상태를 감지한 경우 — 예를 들어 Service Worker가 lock을 유지 중인 탭이 더 이상 응답하지 않는다고 판단할 때 — 이 옵션을 사용해 lock을 "steal"할 수 있습니다.

steal 옵션은 신중하게 사용하세요. 사용 시, 이전에 lock을 유지하던 코드가 더 이상 리소스에 대한 단독 접근을 보장받지 못한 채 실행될 수 있습니다. 마찬가지로, 옵션을 사용한 코드 역시 다른 컨텍스트가 여전히 리소스에 접근하는 것으로 실행될 수 있음을 보장받지 못합니다. 이 옵션은 애플리케이션 또는 user-agent 결함 등으로 인해 이미 동작이 예측 불가능한 상황에서 복구를 시도해야 하는 웹 애플리케이션을 위한 것입니다.

request(name, callback)request(name, options, callback) 메서드 단계는 다음과 같습니다:

  1. options가 전달되지 않았다면, options를 기본 멤버를 가진 새로운 LockOptions 딕셔너리로 설정합니다.

  2. environmentthisrelevant settings object로 설정합니다.

  3. environmentrelevant global objectassociated Documentfully active가 아니면, "InvalidStateError" DOMException으로 reject된 promise를 반환합니다.

  4. managerenvironmentlock manager 획득의 결과로 설정합니다. 실패를 반환했다면, "SecurityError" DOMException으로 reject된 promise를 반환합니다.

  5. name이 U+002D HYPHEN-MINUS(-)로 시작하면, "NotSupportedError" DOMException으로 reject된 promise를 반환합니다.

  6. options["steal"]와 options["ifAvailable"]가 모두 true이면, "NotSupportedError" DOMException으로 reject된 promise를 반환합니다.

  7. options["steal"]가 true이고 options["mode"] 값이 "exclusive"가 아니면, "NotSupportedError" DOMException으로 reject된 promise를 반환합니다.

  8. options["signal"]가 존재하고, options["steal"] 또는 options["ifAvailable"] 중 하나라도 true이면, "NotSupportedError" DOMException으로 reject된 promise를 반환합니다.

  9. options["signal"]가 존재하고 aborted 상태라면, options["signal"]의 abort reason으로 reject된 promise를 반환합니다.

  10. promise새로운 promise로 설정합니다.

  11. lock 요청promise, 현재 agent, environmentid, manager, callback, name, options["mode"], options["ifAvailable"], options["steal"], options["signal"]로 실행합니다.

  12. promise를 반환합니다.

3.2.2. query() 메서드

state = await navigator . locks . query()

query() 메서드는 오리진의 lock manager 상태 스냅샷을 생성하는 데 사용할 수 있으며, 웹 애플리케이션이 lock 사용 현황을 로깅이나 디버깅 목적으로 확인할 수 있게 해줍니다.

반환된 promise는 아래와 같은 형태의 평범한 데이터 구조(즉, JSON 유사 데이터) state로 resolve됩니다:

{
  held: [
    { name: "resource1", mode: "exclusive",
      clientId: "8b1e730c-7405-47db-9265-6ee7c73ac153" },
    { name: "resource2", mode: "shared",
      clientId: "8b1e730c-7405-47db-9265-6ee7c73ac153" },
    { name: "resource2", mode: "shared",
      clientId: "fad203a5-1f31-472b-a7f7-a3236a1f6d3b" },
  ],
  pending: [
    { name: "resource1", mode: "exclusive",
      clientId: "fad203a5-1f31-472b-a7f7-a3236a1f6d3b" },
    { name: "resource1", mode: "exclusive",
      clientId: "d341a5d0-1d8d-4224-be10-704d1ef92a15" },
  ]
}

clientId 필드는 고유한 컨텍스트(프레임 또는 워커)에 해당하며, Clientid 속성과 동일한 값입니다.

이 데이터는 lock manager 상태의 스냅샷일 뿐입니다. 스크립트에 데이터가 반환될 때 실제 lock 상태는 이미 변경되었을 수 있습니다.

query() 메서드 단계는 다음과 같습니다:

  1. environmentthisrelevant settings object로 설정합니다.

  2. environmentrelevant global objectassociated Documentfully active가 아니면, "InvalidStateError" DOMException으로 reject된 promise를 반환합니다.

  3. managerenvironmentlock manager 획득의 결과로 설정합니다. 실패를 반환했다면, "SecurityError" DOMException으로 reject된 promise를 반환합니다.

  4. promise새로운 promise로 설정합니다.

  5. 다음 단계를 lock task queue에 enqueue하여 lock 상태 스냅샷managerpromise로 실행합니다.

  6. promise를 반환합니다.

3.3. Lock 클래스

[SecureContext, Exposed=(Window,Worker)]
interface Lock {
  readonly attribute DOMString name;
  readonly attribute LockMode mode;
};

Lock 객체는 lock과 연관되어 있습니다.

name getter의 단계는 연관된 lockname을 반환하는 것입니다.

mode getter의 단계는 연관된 lockmode를 반환하는 것입니다.

4. 알고리즘

4.1. lock 요청

lock 요청promise, agent, clientId, manager, callback, name, mode, ifAvailable, steal, signal로 실행:
  1. request를 새로운 lock request (agent, clientId, manager, name, mode, callback, promise, signal)로 설정합니다.

  2. signal이 존재하면, add 알고리즘 요청 중단 신호 requestsignalsignal에 추가합니다.

  3. 다음 단계를 lock task queue에 enqueue:

    1. queueMapmanagerlock request queue map으로 설정합니다.

    2. queuequeueMap에서 name에 대해 lock 요청 큐 가져오기의 결과로 설정합니다.

    3. heldmanagerheld lock set으로 설정합니다.

    4. steal이 true이면 다음 단계를 실행:

      1. held의 각 lock에 대해:

        1. locknamename과 같으면 다음 단계를 실행:

          1. held에서 lock을 제거합니다.

          2. lockreleased promise를 "AbortError" DOMException으로 reject합니다.

      2. queuerequest를 prepend합니다.

    5. 그렇지 않으면 다음 단계를 실행:

      1. ifAvailable이 true이고 requestgrantable이 아니면, 다음 단계를 callbackrelevant settings objectresponsible event loop에 enqueue:

        1. rcallback 함수 호출 결과로 null을 인자로 하여 설정합니다.

        2. promiser로 resolve하고, 이 단계들을 중단합니다.

      2. queuerequest를 enqueue합니다.

    6. lock 요청 큐 처리queue로 실행합니다.

  4. request를 반환합니다.

4.2. 잠금 해제

잠금 해제 lock을 수행하려면:
  1. Assert: 이 단계들은 lock 작업 큐에서 실행 중임을 확인한다.

  2. managerlockmanager로 설정한다.

  3. queueMapmanagerlock 요청 큐 맵으로 설정한다.

  4. namelock리소스 이름으로 설정한다.

  5. queuequeueMap에서 name에 대해 lock 요청 큐 가져오기의 결과로 설정한다.

  6. Remove lockmanagerheld lock set에서 제거한다.

  7. lock 요청 큐 처리queue로 실행한다.

4.3. 요청 중단

요청 중단 request를 수행하려면:
  1. Assert: 이 단계들은 lock 작업 큐에서 실행 중임을 확인한다.

  2. managerrequestmanager로 설정한다.

  3. namerequestname으로 설정한다.

  4. queueMapmanagerlock 요청 큐 맵으로 설정한다.

  5. queuequeueMap에서 name에 대해 lock 요청 큐 가져오기의 결과로 설정한다.

  6. Remove requestqueue에서 제거한다.

  7. lock 요청 큐 처리queue로 실행한다.

요청 중단 신호 requestsignal을 수행하려면:
  1. 다음 단계를 요청 중단 requestlock 작업 큐에 enqueue한다.

  2. Reject requestpromisesignalabort reason으로 reject한다.

4.4. 특정 리소스 이름에 대한 lock 요청 큐 처리

lock 요청 큐 처리 queue를 수행하려면:
  1. Assert: 이 단계들은 lock 작업 큐에서 실행 중임을 확인한다.

  2. queuerequest에 대해 반복한다:

    1. requestgrantable이 아니면 return한다.

      참고: 큐의 첫 번째 항목만 grantable이다. 따라서 grantable이 아닌 항목이 있으면 그 뒤의 모든 항목도 자동으로 grantable이 아니다.

    2. Remove requestqueue에서 제거한다.

    3. agentrequestagent로 설정한다.

    4. managerrequestmanager로 설정한다.

    5. clientIdrequestclientId로 설정한다.

    6. namerequestname으로 설정한다.

    7. moderequestmode로 설정한다.

    8. callbackrequestcallback으로 설정한다.

    9. prequestpromise로 설정한다.

    10. signalrequestsignal로 설정한다.

    11. waiting새로운 promise로 설정한다.

    12. locklock으로 새로 생성하며, agent agent, clientId clientId, manager manager, mode mode, name name, released promise p, waiting promise waiting을 포함한다.

    13. Append lockmanagerheld lock set에 추가한다.

    14. 다음 단계를 callbackrelevant settings objectresponsible event loop에 enqueue:

      1. signal이 존재하면 다음 단계를 실행:

        1. signalaborted 상태라면 다음 단계를 실행:

          1. 다음 단계를 lock 작업 큐에 enqueue:

            1. 잠금 해제 lock을 실행한다.

          2. Return.

        2. Remove 알고리즘 요청 중단 신호 requestsignal에서 제거한다.

      2. rcallback 함수 호출 결과로, lock과 연관된 새로운 Lock 객체를 유일한 인자로 하여 설정한다.

      3. waitingr로 resolve한다.

4.5. 잠금 상태 스냅샷

잠금 상태 스냅샷managerpromise로 수행하려면:
  1. Assert: 이 단계들은 lock task queue에서 실행 중임을 확인한다.

  2. pending을 새로운 list로 설정한다.

  3. queue에 대해 managerlock request queue mapvalues를 반복한다:

    1. queuerequest에 대해 반복한다:

      1. 추가 «[ "name" → request’s name, "mode" → request’s mode, "clientId" → request’s clientId ]»를 pending에 추가한다.

  4. held를 새로운 list로 설정한다.

  5. managerheld lock setlock에 대해 반복한다:

    1. 추가 «[ "name" → lock’s name, "mode" → lock’s mode, "clientId" → lock’s clientId ]»를 held에 추가한다.

  6. promise를 «[ "held" → held, "pending" → pending ]»로 resolve한다.

특정 리소스에 대해 pending 잠금 요청의 스냅샷은 요청이 생성된 순서대로 반환된다. 하지만 서로 다른 리소스 간의 요청 순서에 대해서는 어떠한 보장도 하지 않는다. 예를 들어, 리소스 A에 대해 pending 잠금 요청 A1과 A2가 그 순서대로 생성되고, 리소스 B에 대해 pending 잠금 요청 B1과 B2가 그 순서대로 생성된 경우, 스냅샷의 pending 리스트에는 «A1, A2, B1, B2» 또는 «A1, B1, A2, B2»와 같은 순서가 모두 가능하다.

held 잠금 상태의 스냅샷에 대해서는 순서 보장이 존재하지 않는다.

5. 사용 시 고려사항

이 섹션은 비규범적입니다.

5.1. 교착 상태(Deadlocks)

교착 상태(Deadlocks)는 동시성 컴퓨팅의 개념이며, 특정 lock manager 범위 내에서 이 API를 통해 교착 상태가 발생할 수 있다.

이 API를 통해 교착 상태가 발생할 수 있는 예시는 다음과 같다.

스크립트 1:

navigator.locks.request('A', async a => {
  await navigator.locks.request('B', async b => {
    // A와 B를 사용하여 작업 수행
  });
});

스크립트 2:

navigator.locks.request('B', async b => {
  await navigator.locks.request('A', async a => {
    // A와 B를 사용하여 작업 수행
  });
});

스크립트 1과 2가 거의 동시에 실행되면, 스크립트 1이 잠금 A를, 스크립트 2가 잠금 B를 점유하고 서로 더 이상 진행할 수 없는 교착 상태가 발생할 수 있다. 이는 전체 사용자 에이전트에 영향을 주거나 탭을 멈추거나 동일 오리진의 다른 스크립트에 영향을 주지는 않지만, 해당 기능은 차단된다.

교착 상태를 방지하려면 주의가 필요하다. 한 가지 방법은 여러 잠금을 항상 엄격한 순서로 획득하는 것이다.

다음과 같은 헬퍼 함수를 사용하여 여러 잠금을 일관된 순서로 요청할 수 있다.

async function requestMultiple(resources, callback) {
  const sortedResources = [...resources];
  sortedResources.sort(); // 항상 동일한 순서로 요청
  async function requestNext(locks) {
    return await navigator.locks.request(sortedResources.shift(), async lock => {
      // 현재 이 잠금과 이전에 요청한 모든 잠금 점유
      locks.push(lock);
      // 필요 시 다음 잠금을 재귀적으로 요청
      if (sortedResources.length > 0)
        return await requestNext(locks);
      // 그렇지 않으면 콜백 실행
      return await callback(locks);
      // 콜백이 반환(또는 throw)되면 모든 잠금 해제
    });
  }
  return await requestNext([]);
}

실제로는 여러 잠금의 사용이 이처럼 단순하지 않은 경우가 많으며, 라이브러리나 기타 유틸리티가 잠금 사용을 의도치 않게 복잡하게 만들 수 있다.

6. 보안 및 프라이버시 고려사항

6.1. 잠금 범위

lock manager의 범위 정의는 프라이버시 경계를 결정하므로 중요하다. 잠금은 일시적 상태 유지 메커니즘으로 사용될 수 있으며, 저장소 API와 마찬가지로 통신 메커니즘으로도 사용될 수 있으므로 저장소 기능보다 더 높은 권한을 가져서는 안 된다. 사용자 에이전트가 이러한 서비스 중 하나에 더 세분화된 접근을 적용한다면, 다른 서비스에도 동일하게 적용해야 한다. 예를 들어, 사용자 에이전트가 프라이버시를 위해 동일 오리진 내에서 최상위 페이지(퍼스트파티)와 교차 오리진 iframe(서드파티)에 대해 서로 다른 저장소 파티션을 노출한다면, 잠금도 동일하게 분할해야 한다.

이는 웹 애플리케이션 작성자에게도 합리적인 기대를 제공한다. 저장소 리소스에 대해 잠금이 획득되면, 동일 오리진의 모든 브라우징 컨텍스트는 동일한 상태를 관찰해야 한다.

6.2. 프라이빗 브라우징

모든 프라이빗 모드 브라우징 세션은 이 API의 목적상 별도의 사용자 에이전트로 간주된다. 즉, 해당 세션 외부에서 요청/점유된 잠금은 내부에 영향을 주지 않으며, 반대도 마찬가지다. 이는 웹사이트가 세션이 "시크릿 모드"임을 판별하는 것을 방지하며, 세션 간 통신 메커니즘도 허용하지 않는다.

6.3. 구현 위험

구현체는 잠금이 오리진을 넘지 않도록 반드시 보장해야 한다. 이를 실패할 경우, 서로 다른 오리진에서 실행되는 스크립트 간의 사이드 채널 통신이 가능해지거나, 한 오리진의 스크립트가 다른 오리진의 동작을 방해(예: 서비스 거부)할 수 있다.

6.4. 체크리스트

W3C TAG는 명세 편집자가 참고할 수 있도록 보안 및 프라이버시 셀프 리뷰 질문지를 개발했다. 여기서 질문을 다시 살펴보면:

7. 감사의 글

이 제안서 작성에 도움을 준 Alex Russell, Andreas Butler, Anne van Kesteren, Boris Zbarsky, Chris Messina, Darin Fisher, Domenic Denicola, Gus Caplan, Harald Alvestrand, Jake Archibald, Kagami Sascha Rosylight, L. David Baron, Luciano Pacheco, Marcos Caceres, Ralph Chelala, Raymond Toy, Ryan Fioravanti, 그리고 Victor Costan 에게 깊은 감사를 드립니다.

명세 작성 도구 Bikeshed를 만들고 유지관리해준 Tab Atkins, Jr.에게도 특별히 감사드리며, 전반적인 작성 조언에도 감사드립니다.

적합성(Conformance)

문서 규칙

준수 요구사항은 설명적 단언과 RFC 2119 용어의 조합으로 표현된다. 규범적 부분에서 “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, “OPTIONAL” 등의 키워드는 RFC 2119에서 설명한 대로 해석해야 한다. 단, 가독성을 위해 이 명세에서는 해당 단어들이 모두 대문자로 표기되지는 않는다.

이 명세의 모든 텍스트는 명시적으로 비규범적, 예시, 노트로 표시된 섹션을 제외하고는 규범적이다. [RFC2119]

이 명세의 예시는 “for example”이라는 문구로 시작하거나, class="example"와 같이 규범적 텍스트와 구분되어 있다:

이것은 정보 제공용 예시입니다.

정보 제공용 노트는 “Note”라는 단어로 시작하며, class="note"로 규범적 텍스트와 구분되어 있습니다:

Note, 이것은 정보 제공용 노트입니다.

적합 알고리즘

알고리즘의 일부로 명령형으로 표현된 요구사항(예: "strip any leading space characters" 또는 "return false and abort these steps")은 알고리즘을 소개할 때 사용된 키워드("must", "should", "may" 등)의 의미로 해석해야 한다.

알고리즘이나 특정 단계로 표현된 준수 요구사항은 결과가 동등하다면 어떤 방식으로든 구현할 수 있다. 특히, 이 명세의 알고리즘은 이해하기 쉽도록 작성된 것이며, 성능을 고려한 것이 아니다. 구현자는 최적화를 권장한다.

색인(Index)

이 명세에서 정의된 용어

참조로 정의된 용어

참고 문헌

규범적 참고 문헌

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. 1997년 3월. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[Storage]
Anne van Kesteren. Storage Standard. Living Standard. URL: https://storage.spec.whatwg.org/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

비규범적 참고 문헌

[IndexedDB-2]
Ali Alabbas; Joshua Bell. Indexed Database API 2.0. 2018년 1월 30일. REC. URL: https://www.w3.org/TR/IndexedDB-2/
[Service-Workers]
Yoshisato Yanagisawa; Monica CHINTALA. Service Workers. 2025년 3월 6일. CRD. URL: https://www.w3.org/TR/service-workers/

IDL 색인

[SecureContext]
interface mixin NavigatorLocks {
  readonly attribute LockManager locks;
};
Navigator includes NavigatorLocks;
WorkerNavigator includes NavigatorLocks;

[SecureContext, Exposed=(Window,Worker)]
interface LockManager {
  Promise<any> request(DOMString name,
                       LockGrantedCallback callback);
  Promise<any> request(DOMString name,
                       LockOptions options,
                       LockGrantedCallback callback);

  Promise<LockManagerSnapshot> query();
};

callback LockGrantedCallback = Promise<any> (Lock? lock);

enum LockMode { "shared", "exclusive" };

dictionary LockOptions {
  LockMode mode = "exclusive";
  boolean ifAvailable = false;
  boolean steal = false;
  AbortSignal signal;
};

dictionary LockManagerSnapshot {
  sequence<LockInfo> held;
  sequence<LockInfo> pending;
};

dictionary LockInfo {
  DOMString name;
  LockMode mode;
  DOMString clientId;
};

[SecureContext, Exposed=(Window,Worker)]
interface Lock {
  readonly attribute DOMString name;
  readonly attribute LockMode mode;
};

이슈 색인

[Storage]와의 통합을 여기서 더 다듬고, 주어진 환경에서 lock manager를 올바르게 얻는 방법을 포함해야 합니다.
현재는 워커에만 해당하며, 워커 종료 시 단계를 실행하는 규범적 방법이 없기 때문에 정의가 모호합니다.
MDN

Lock/mode

In all current engines.

Firefox96+Safari15.4+Chrome69+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

Lock/name

In all current engines.

Firefox96+Safari15.4+Chrome69+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

Lock

In all current engines.

Firefox96+Safari15.4+Chrome69+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

LockManager/query

In all current engines.

Firefox96+Safari15.4+Chrome69+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

LockManager/request

In all current engines.

Firefox96+Safari15.4+Chrome69+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

LockManager

In all current engines.

Firefox96+Safari15.4+Chrome69+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

Navigator/locks

In all current engines.

Firefox96+Safari15.4+Chrome69+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?

WorkerNavigator/locks

In all current engines.

Firefox96+Safari15.4+Chrome69+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?