1. 소개
이 섹션은 규범적이지 않습니다.
스크립트는 특정 잠금 요청을 리소스 이름과 모드에 대해 수행합니다. 스케줄링 알고리즘은 현재와 이전 요청의 상태를 확인하고, 결국 잠금 요청을 승인합니다. 잠금은 승인된 요청이며, 리소스 이름과 모드를 가집니다. 이는 스크립트에 반환되는 객체로 표현됩니다. 잠금이 유지되는 동안에는(이름과 모드에 따라) 다른 잠금 요청의 승인이 제한될 수 있습니다. 스크립트가 잠금을 해제하면, 다른 잠금 요청이 승인될 수 있습니다.
API는 필요에 따라 사용할 수 있는 선택적 기능을 제공합니다:
-
비동기 작업에서 값을 반환,
-
공유 및 단독 잠금 모드,
-
조건부 획득,
-
잠금 상태를 조회하는 진단 기능,
-
교착 상태 방지를 위한 이스케이프 해치.
협력적 조율은 에이전트가 스토리지 버킷을 공유하는 범위 내에서 이루어지며, 이는 여러 에이전트 클러스터에 걸쳐 있을 수 있습니다.
참고: 에이전트는 대략적으로 윈도우(탭), iframe, 워커에 해당합니다. 에이전트 클러스터는 일부 사용자 에이전트 구현에서 독립적인 프로세스에 해당합니다.
1.1. 사용 개요
API는 다음과 같이 사용됩니다:
-
잠금이 요청됩니다.
-
비동기 작업에서 잠금을 유지한 채로 작업을 수행합니다.
-
작업이 완료되면 잠금이 자동으로 해제됩니다.
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 문자열입니다.
리소스 이름은 스케줄링 알고리즘 외부에는 의미가 없으나, 에이전트가 스토리지 버킷을 공유할 때 전역적입니다. 웹 애플리케이션은 어떤 리소스 명명 규칙도 자유롭게 사용할 수 있습니다.
encodeURIComponent( db_name) + '/' + encodeURIComponent( store_name)
U+002D 하이픈 마이너스(-)로 시작하는 리소스 이름은 예약되어 있습니다. 이를 요청하면 예외가 발생합니다.
2.2. 잠금 관리자
잠금 관리자는 잠금과 잠금 요청의 상태를 캡슐화합니다. 각 스토리지 버킷은 웹 잠금 API를 위한 연결된 스토리지 병을 통해 하나의 잠금 관리자를 포함합니다.
참고: 동일한 사용자 에이전트에서 동일한 스토리지 버킷을 공유하는 페이지와 워커(에이전트)는 서로 관련 없는 브라우징 컨텍스트에 있더라도 잠금 관리자를 공유합니다.
-
map을 로컬 스토리지 병 맵 획득의 결과로 설정합니다. 인자로는 environment와 "
web-locks
"를 전달합니다. -
map이 실패라면, 실패를 반환합니다.
-
bottle을 map의 연결된 스토리지 병으로 설정합니다.
-
bottle의 연결된 잠금 관리자를 반환합니다.
여기서 [Storage]와의 통합을 다듬고, 주어진 환경에서 잠금 관리자를 올바르게 가져오는 방법을 포함해야 합니다.
2.3. 모드 및 스케줄링
모드는 "exclusive
"
또는 "shared
"입니다.
모드는 일반적인 reader-writer lock 패턴을 모델링할
수 있습니다. "exclusive
"
잠금이 유지되면, 해당 이름의 다른 잠금은 승인될 수 없습니다. "shared
"
잠금이 유지되면, 동일 이름의 다른 "shared
"
잠금은 승인될 수 있지만, "exclusive
"
잠금은 승인되지 않습니다. API의 기본 모드는 "exclusive
"입니다.
추가 속성이 스케줄링에 영향을 줄 수 있으며, 예를 들어 타임아웃이나 공정성 등이 있습니다.
2.4. 잠금
잠금은 공유 리소스에 대한 독점적 접근을 나타냅니다.
잠금은 clientId를 가지며, 이는 불투명한 문자열입니다.
잠금은 manager를 가지며, 이는 잠금 관리자입니다.
잠금은 mode를
가지며, "exclusive
"
또는 "shared
"
중 하나입니다.
잠금은 waiting promise를 가지며, 이는 Promise입니다.
잠금은 released promise를 가지며, 이는 Promise입니다.
-
잠금이 승인될 때 콜백이 암묵적 또는 명시적으로 제공하는 프로미스로, 잠금이 얼마나 오랫동안 유지되는지 결정합니다. 이 프로미스가 해결되면 잠금이 해제됩니다. 이는 잠금의 waiting promise로 불립니다.
-
LockManager
의request()
메서드가 반환하는 프로미스로, 잠금이 해제되거나 요청이 중단될 때 해결됩니다. 이는 잠금의 released promise로 불립니다.
const p1= navigator. locks. request( 'resource' , lock=> { const p2= new Promise( r=> { // 잠금을 사용하고 프로미스를 해결하는 로직... }); return p2; });
위 예시에서 p1
은 released
promise이고, p2
는 waiting
promise입니다.
대부분의 코드에서는 콜백이 async
함수로 구현되어 반환 프로미스가 암묵적으로 처리됩니다. 아래 예시처럼:
const p1= navigator. locks. request( 'resource' , async lock=> { // 잠금을 사용하는 로직... });
위 코드에서는 waiting promise가 명시적으로 이름 붙여지지 않았지만, 익명
async
콜백의 반환값으로 존재합니다.
또한 콜백이 async
가 아니고 프로미스가 아닌 값을 반환하면, 해당 값은 즉시 해결되는 프로미스로 래핑됩니다. 잠금은 다음 마이크로태스크에서 해제되며,
released promise도 이후 마이크로태스크에서 해결됩니다.
각 잠금 관리자는 held lock set을 가지며, 이는 set의 잠금입니다.
잠금 lock의 waiting promise가 해결(fulfilled 또는 rejected)될 때, 다음 단계들을 잠금 작업 큐에 대기열화합니다:
-
잠금 해제 lock.
-
Resolve lock의 released promise를 lock의 waiting promise로 해결합니다.
2.5. 잠금 요청
잠금 요청은 잠금에 대한 보류 중인 요청을 나타냅니다.
각 잠금 관리자는 잠금 요청 큐 맵을 가지며, 이는 맵으로, 리소스 이름에서 잠금 요청 큐로 매핑됩니다.
잠금 요청 request가 grantable로 간주되려면 다음 단계가 true를 반환해야 합니다:
-
manager를 request의 manager로 설정합니다.
-
queueMap을 manager의 잠금 요청 큐 맵으로 설정합니다.
-
name을 request의 name으로 설정합니다.
-
queue를 잠금 요청 큐 가져오기의 결과로 queueMap에서 name에 대해 얻습니다.
-
held를 manager의 held lock set으로 설정합니다>
-
mode를 request의 mode로 설정합니다>
-
mode가 "
exclusive
"이면, held에 잠금 중 name이 name과 같은 잠금이 없으면 true를, 그렇지 않으면 false를 반환합니다. -
그 외의 경우, mode가 "
shared
"이면, held에 잠금 중 mode가 "exclusive
"이고 name이 name과 같은 잠금이 없으면 true를, 그렇지 않으면 false를 반환합니다.
2.6. 잠금 종료
문서 언로드 클린업 단계가 document로 실행될 때, 해당 에이전트의 agent에 대해 남아있는 잠금과 요청을 종료합니다.
agent가 종료될 때, 해당 에이전트의 남아있는 잠금과 요청을 종료합니다.
현재 이는 워커에만 적용되며, 워커 종료 시 단계를 실행하는 규범적 방법이 없어 정의가 모호합니다.
남아있는 잠금과 요청 종료를 agent로 하려면, 다음 단계를 잠금 작업 큐에 대기열화합니다:
3. API
3.1. Navigator 믹스인
[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 = "exclusive";
mode 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) - promise = navigator . locks .
-
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 promise가AbortError
로 resolve됨), 요청이 grant되어 대기 중인 요청을 선점합니다.웹 애플리케이션이 복구 불가능한 상태를 감지한 경우 — 예를 들어 Service Worker가 lock을 유지 중인 탭이 더 이상 응답하지 않는다고 판단할 때 — 이 옵션을 사용해 lock을 "steal"할 수 있습니다.
steal
옵션은 신중하게 사용하세요.
사용 시, 이전에 lock을 유지하던 코드가 더 이상 리소스에 대한 단독 접근을 보장받지 못한 채 실행될 수 있습니다.
마찬가지로, 옵션을 사용한 코드 역시 다른 컨텍스트가 여전히 리소스에 접근하는 것으로 실행될 수 있음을 보장받지 못합니다.
이 옵션은 애플리케이션 또는 user-agent 결함 등으로 인해 이미 동작이 예측 불가능한 상황에서 복구를 시도해야 하는 웹 애플리케이션을 위한 것입니다.
request(name, callback)
및
request(name, options, callback)
메서드 단계는 다음과 같습니다:
-
options가 전달되지 않았다면, options를 기본 멤버를 가진 새로운
LockOptions
딕셔너리로 설정합니다. -
environment를 this의 relevant settings object로 설정합니다.
-
environment의 relevant global object의 associated Document가 fully active가 아니면, "
InvalidStateError
"DOMException
으로 reject된 promise를 반환합니다. -
manager를 environment로 lock manager 획득의 결과로 설정합니다. 실패를 반환했다면, "
SecurityError
"DOMException
으로 reject된 promise를 반환합니다. -
name이 U+002D HYPHEN-MINUS(-)로 시작하면, "
NotSupportedError
"DOMException
으로 reject된 promise를 반환합니다. -
options["
steal
"]와 options["ifAvailable
"]가 모두 true이면, "NotSupportedError
"DOMException
으로 reject된 promise를 반환합니다. -
options["
steal
"]가 true이고 options["mode
"] 값이 "exclusive
"가 아니면, "NotSupportedError
"DOMException
으로 reject된 promise를 반환합니다. -
options["
signal
"]가 존재하고, options["steal
"] 또는 options["ifAvailable
"] 중 하나라도 true이면, "NotSupportedError
"DOMException
으로 reject된 promise를 반환합니다. -
options["
signal
"]가 존재하고 aborted 상태라면, options["signal
"]의 abort reason으로 reject된 promise를 반환합니다. -
promise를 새로운 promise로 설정합니다.
-
lock 요청을 promise, 현재 agent, environment의 id, manager, callback, name, options["
mode
"], options["ifAvailable
"], options["steal
"], options["signal
"]로 실행합니다. -
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
필드는 고유한 컨텍스트(프레임 또는 워커)에 해당하며, Client
의
id
속성과 동일한 값입니다.
query()
메서드 단계는 다음과 같습니다:
-
environment를 this의 relevant settings object로 설정합니다.
-
environment의 relevant global object의 associated Document가 fully active가 아니면, "
InvalidStateError
"DOMException
으로 reject된 promise를 반환합니다. -
manager를 environment로 lock manager 획득의 결과로 설정합니다. 실패를 반환했다면, "
SecurityError
"DOMException
으로 reject된 promise를 반환합니다. -
promise를 새로운 promise로 설정합니다.
-
다음 단계를 lock task queue에 enqueue하여 lock 상태 스냅샷을 manager와 promise로 실행합니다.
-
promise를 반환합니다.
3.3. Lock
클래스
[SecureContext ,Exposed =(Window ,Worker )]interface {
Lock readonly attribute DOMString name ;readonly attribute LockMode mode ; };
name
getter의 단계는 연관된 lock의 name을 반환하는 것입니다.
mode
getter의 단계는 연관된 lock의 mode를 반환하는 것입니다.
4. 알고리즘
4.1. lock 요청
-
request를 새로운 lock request (agent, clientId, manager, name, mode, callback, promise, signal)로 설정합니다.
-
signal이 존재하면, add 알고리즘 요청 중단 신호 request와 signal을 signal에 추가합니다.
-
다음 단계를 lock task queue에 enqueue:
-
queueMap을 manager의 lock request queue map으로 설정합니다.
-
queue를 queueMap에서 name에 대해 lock 요청 큐 가져오기의 결과로 설정합니다.
-
held를 manager의 held lock set으로 설정합니다.
-
steal이 true이면 다음 단계를 실행:
-
held의 각 lock에 대해:
-
lock의 name이 name과 같으면 다음 단계를 실행:
-
lock의 released promise를 "
AbortError
"DOMException
으로 reject합니다.
-
-
queue에 request를 prepend합니다.
-
-
그렇지 않으면 다음 단계를 실행:
-
ifAvailable이 true이고 request가 grantable이 아니면, 다음 단계를 callback의 relevant settings object의 responsible event loop에 enqueue:
-
r을 callback 함수 호출 결과로
null
을 인자로 하여 설정합니다. -
promise를 r로 resolve하고, 이 단계들을 중단합니다.
-
-
queue에 request를 enqueue합니다.
-
-
lock 요청 큐 처리를 queue로 실행합니다.
-
-
request를 반환합니다.
4.2. 잠금 해제
-
manager를 lock의 manager로 설정한다.
-
queueMap을 manager의 lock 요청 큐 맵으로 설정한다.
-
name을 lock의 리소스 이름으로 설정한다.
-
queue를 queueMap에서 name에 대해 lock 요청 큐 가져오기의 결과로 설정한다.
-
Remove lock을 manager의 held lock set에서 제거한다.
-
lock 요청 큐 처리를 queue로 실행한다.
4.3. 요청 중단
-
manager를 request의 manager로 설정한다.
-
name을 request의 name으로 설정한다.
-
queueMap을 manager의 lock 요청 큐 맵으로 설정한다.
-
queue를 queueMap에서 name에 대해 lock 요청 큐 가져오기의 결과로 설정한다.
-
Remove request를 queue에서 제거한다.
-
lock 요청 큐 처리를 queue로 실행한다.
4.4. 특정 리소스 이름에 대한 lock 요청 큐 처리
-
각 queue의 request에 대해 반복한다:
-
request가 grantable이 아니면 return한다.
참고: 큐의 첫 번째 항목만 grantable이다. 따라서 grantable이 아닌 항목이 있으면 그 뒤의 모든 항목도 자동으로 grantable이 아니다.
-
Remove request를 queue에서 제거한다.
-
agent를 request의 agent로 설정한다.
-
manager를 request의 manager로 설정한다.
-
clientId를 request의 clientId로 설정한다.
-
name을 request의 name으로 설정한다.
-
mode를 request의 mode로 설정한다.
-
callback을 request의 callback으로 설정한다.
-
p를 request의 promise로 설정한다.
-
signal을 request의 signal로 설정한다.
-
waiting을 새로운 promise로 설정한다.
-
lock을 lock으로 새로 생성하며, agent agent, clientId clientId, manager manager, mode mode, name name, released promise p, waiting promise waiting을 포함한다.
-
Append lock을 manager의 held lock set에 추가한다.
-
다음 단계를 callback의 relevant settings object의 responsible event loop에 enqueue:
-
4.5. 잠금 상태 스냅샷
-
Assert: 이 단계들은 lock task queue에서 실행 중임을 확인한다.
-
pending을 새로운 list로 설정한다.
-
각 queue에 대해 manager의 lock request queue map의 values를 반복한다:
-
held를 새로운 list로 설정한다.
-
각 manager의 held lock set의 lock에 대해 반복한다:
-
promise를 «[ "held" → held, "pending" → pending ]»로 resolve한다.
held 잠금 상태의 스냅샷에 대해서는 순서 보장이 존재하지 않는다.
5. 사용 시 고려사항
이 섹션은 비규범적입니다.
5.1. 교착 상태(Deadlocks)
교착 상태(Deadlocks)는 동시성 컴퓨팅의 개념이며, 특정 lock manager 범위 내에서 이 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는 명세 편집자가 참고할 수 있도록 보안 및 프라이버시 셀프 리뷰 질문지를 개발했다. 여기서 질문을 다시 살펴보면:
-
이 명세는 개인 식별 정보 또는 고가치 데이터와 관련되지 않는다.
-
오리진에 대해 브라우징 세션 간에 지속되는 새로운 상태는 도입되지 않는다.
-
웹에 새로운 영속적, 교차 오리진 상태가 노출되지 않는다.
-
오리진이 현재 접근할 수 없는 새로운 데이터가 노출되지 않는다(예: [IndexedDB-2]를 통한 폴링 등).
-
새로운 스크립트 실행/로딩 메커니즘이 활성화되지 않는다.
-
이 명세는 오리진이 다음 항목에 접근하는 것을 허용하지 않는다:
-
사용자의 위치
-
사용자 기기의 센서
-
사용자 로컬 컴퓨팅 환경의 측면
-
다른 기기 접근
-
사용자 에이전트의 네이티브 UI에 대한 제어 권한
-
-
웹에 임시 식별자가 노출되지 않는다. 모든 resource name은 웹 애플리케이션이 직접 제공한다.
-
스토리지가 구분되는 경우, 사용자 에이전트 내에서 퍼스트파티와 서드파티 컨텍스트의 동작도 구분된다. § 6.1 잠금 범위 참고.
-
사용자 에이전트의 "시크릿 모드" 컨텍스트에서의 동작은 § 6.2 프라이빗 브라우징에 기술되어 있다.
-
이 API는 사용자의 로컬 디바이스에 데이터를 영구 저장하지 않는다.
-
이 API는 기본 보안 특성을 다운그레이드하는 것을 허용하지 않는다.
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.에게도 특별히 감사드리며, 전반적인 작성 조언에도 감사드립니다.