[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,313 @@
|
||||
---
|
||||
id: web-web-locks-api
|
||||
title: Web Locks API — tab 간 mutex
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [web, lock, vibe-coding]
|
||||
tech_stack: { language: "TS", applicable_to: ["Frontend"] }
|
||||
applied_in: []
|
||||
aliases: [Web Locks, navigator.locks, tab synchronization, broadcast channel, mutex, exclusive lock]
|
||||
---
|
||||
|
||||
# Web Locks API
|
||||
|
||||
> 같은 origin 의 여러 tab 가 race condition. **`navigator.locks` 가 mutex**. Sync between tabs / workers. Modern browsers.
|
||||
|
||||
## 📖 핵심 개념
|
||||
- 같은 origin 의 다른 tab / worker / iframe 사이.
|
||||
- Exclusive (1) 또는 shared (read).
|
||||
- 매 lock 가 자동 release (tab close).
|
||||
- `BroadcastChannel` 와 함께 사용.
|
||||
|
||||
## 💻 코드 패턴
|
||||
|
||||
### Exclusive lock
|
||||
```ts
|
||||
await navigator.locks.request('my-resource', async (lock) => {
|
||||
// 매 origin 에서 1 곳 만.
|
||||
await criticalSection();
|
||||
});
|
||||
// → Lock 자동 release.
|
||||
```
|
||||
|
||||
### Shared lock (read)
|
||||
```ts
|
||||
await navigator.locks.request('config', { mode: 'shared' }, async (lock) => {
|
||||
// 여러 tab 가 동시 read.
|
||||
});
|
||||
|
||||
await navigator.locks.request('config', { mode: 'exclusive' }, async (lock) => {
|
||||
// Write — shared 가 wait.
|
||||
});
|
||||
```
|
||||
|
||||
→ Reader / writer.
|
||||
|
||||
### Use case: leader election
|
||||
```ts
|
||||
// 1 tab 만 가 background work
|
||||
let isLeader = false;
|
||||
|
||||
navigator.locks.request('leader', { mode: 'exclusive' }, async () => {
|
||||
isLeader = true;
|
||||
while (isLeader) {
|
||||
await pollServer();
|
||||
await new Promise(r => setTimeout(r, 5000));
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('beforeunload', () => {
|
||||
isLeader = false;
|
||||
});
|
||||
```
|
||||
|
||||
→ 새 tab 가 lock = leader.
|
||||
|
||||
→ "Service Worker 가 leader" 의 alternative.
|
||||
|
||||
### TryLock (안 wait)
|
||||
```ts
|
||||
const result = await navigator.locks.request('resource', { ifAvailable: true }, async (lock) => {
|
||||
if (!lock) return 'busy';
|
||||
// ...
|
||||
return 'done';
|
||||
});
|
||||
```
|
||||
|
||||
→ `ifAvailable: true` = 잠겼으면 lock=null + callback 즉시.
|
||||
|
||||
### Timeout (own logic)
|
||||
```ts
|
||||
async function lockWithTimeout(name: string, ms: number, fn: () => Promise<void>) {
|
||||
const ac = new AbortController();
|
||||
setTimeout(() => ac.abort(), ms);
|
||||
|
||||
await navigator.locks.request(name, { signal: ac.signal }, fn);
|
||||
}
|
||||
```
|
||||
|
||||
→ N ms 안 lock 못 = AbortError.
|
||||
|
||||
### Query state
|
||||
```ts
|
||||
const { held, pending } = await navigator.locks.query();
|
||||
console.log('Held locks:', held.map(l => l.name));
|
||||
console.log('Pending:', pending.map(l => l.name));
|
||||
```
|
||||
|
||||
→ Debug.
|
||||
|
||||
### vs SharedWorker
|
||||
```
|
||||
SharedWorker: 같은 origin tab 가 공유 worker.
|
||||
Web Locks: 작은 mutex.
|
||||
|
||||
→ 둘 다 사용 가능.
|
||||
SharedWorker = 큰 logic 공유.
|
||||
Web Locks = small lock.
|
||||
```
|
||||
|
||||
### vs BroadcastChannel
|
||||
```
|
||||
BroadcastChannel: tab 간 message.
|
||||
Web Locks: tab 간 lock.
|
||||
|
||||
→ 함께 사용 흔함.
|
||||
```
|
||||
|
||||
```ts
|
||||
const channel = new BroadcastChannel('updates');
|
||||
channel.postMessage({ type: 'config-changed' });
|
||||
|
||||
// Other tab
|
||||
channel.onmessage = (e) => {
|
||||
if (e.data.type === 'config-changed') {
|
||||
reloadConfig();
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Use case: IndexedDB write coordinate
|
||||
```ts
|
||||
// 여러 tab 가 같은 data 변경 → conflict.
|
||||
async function updateConfig(newValue) {
|
||||
await navigator.locks.request('config-write', async () => {
|
||||
const tx = db.transaction('config', 'readwrite');
|
||||
await tx.objectStore('config').put(newValue);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Service Worker + Web Locks
|
||||
```ts
|
||||
// Service worker
|
||||
self.addEventListener('fetch', async (event) => {
|
||||
await navigator.locks.request('cache-write', async () => {
|
||||
const cache = await caches.open('v1');
|
||||
cache.put(event.request, response);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
→ Cache 가 tab 간 + worker 사이 안전.
|
||||
|
||||
### 함정
|
||||
```
|
||||
- Tab 간 만 (cross-origin X).
|
||||
- Worker 도 같은 origin.
|
||||
- Indexed DB transaction 가 별도 lock (혼동 X).
|
||||
```
|
||||
|
||||
### Browser support
|
||||
```
|
||||
Chrome 69+, Edge 79+, FF 96+, Safari 15.4+.
|
||||
→ 모든 modern.
|
||||
```
|
||||
|
||||
### Polyfill 가 어려움
|
||||
```
|
||||
Cross-tab lock 가 native 만 신뢰.
|
||||
Polyfill = localStorage 로 가능 (race condition 위험).
|
||||
```
|
||||
|
||||
### Use case 모음
|
||||
```
|
||||
1. Leader election (single tab background work).
|
||||
2. IndexedDB write serialize.
|
||||
3. Cache 정리 (1 tab 만).
|
||||
4. WebSocket 1 connection share.
|
||||
5. Background sync coordinate.
|
||||
```
|
||||
|
||||
### Single WebSocket pattern
|
||||
```ts
|
||||
async function connectIfLeader() {
|
||||
await navigator.locks.request('ws-connection', async () => {
|
||||
const ws = new WebSocket('wss://...');
|
||||
// ws → BroadcastChannel 가 다른 tab 에 전달
|
||||
ws.onmessage = (e) => {
|
||||
bc.postMessage({ type: 'message', data: e.data });
|
||||
};
|
||||
|
||||
// Hold lock — tab close = release
|
||||
await new Promise(() => {});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
→ 1 WS, N tab 공유.
|
||||
|
||||
### Lock 의 ordering
|
||||
```
|
||||
First-come, first-served (queue).
|
||||
- Tab A request → wait.
|
||||
- Tab B request → wait B.
|
||||
- Tab C request → wait C.
|
||||
- A 끝 → B 시작 → 등.
|
||||
```
|
||||
|
||||
### Re-entrancy 안 됨
|
||||
```ts
|
||||
await navigator.locks.request('x', async () => {
|
||||
// ❌ 재 lock — deadlock.
|
||||
await navigator.locks.request('x', async () => {});
|
||||
});
|
||||
```
|
||||
|
||||
→ Promise pending forever.
|
||||
|
||||
### 일반 mutex 와 차이
|
||||
```
|
||||
일반 mutex: 같은 process.
|
||||
Web Locks: cross-tab (다른 process).
|
||||
|
||||
→ Browser 가 origin-scoped lock manager.
|
||||
```
|
||||
|
||||
### Auto-release
|
||||
```
|
||||
Tab close, navigation, crash:
|
||||
→ Lock 자동 release.
|
||||
|
||||
⚠️ 매 lock 가 finite life — 무한 hang X.
|
||||
```
|
||||
|
||||
### Production tip
|
||||
```
|
||||
- Lock 안 work 가 빠름 (다른 tab block).
|
||||
- Long-running = SharedWorker 가 더 좋음.
|
||||
- Critical path 만 lock.
|
||||
- Debug 로 query() 활용.
|
||||
```
|
||||
|
||||
### LiveBlocks / PartyKit (alternative)
|
||||
```
|
||||
Cross-tab sync = Web Locks 의 idea + cross-device.
|
||||
|
||||
→ Different problem (tab 가 아닌 user).
|
||||
```
|
||||
|
||||
### 함정: Worker 안 사용
|
||||
```ts
|
||||
// Worker 안
|
||||
self.onmessage = async (e) => {
|
||||
await navigator.locks.request('x', async () => {
|
||||
// OK — worker 도 same-origin lock 참여
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### Same-site / origin / Tab 이해
|
||||
```
|
||||
example.com / about.html / 가 lock 공유.
|
||||
example.com / sub.html / 도 공유.
|
||||
sub.example.com 는 안 (다른 origin).
|
||||
```
|
||||
|
||||
### Storage event (옛 방법)
|
||||
```ts
|
||||
// localStorage 변경 = 다른 tab 가 'storage' event.
|
||||
// → Pseudo-lock (race condition 위험).
|
||||
|
||||
window.addEventListener('storage', (e) => {
|
||||
if (e.key === 'lock-X' && e.newValue === 'taken') {
|
||||
// ...
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
→ Web Locks 가 modern + 안전.
|
||||
|
||||
## 🤔 의사결정 기준
|
||||
| 작업 | 추천 |
|
||||
|---|---|
|
||||
| Tab 간 mutex | Web Locks |
|
||||
| 1 tab 만 background | Leader election |
|
||||
| Tab 간 message | BroadcastChannel |
|
||||
| Logic 공유 | SharedWorker |
|
||||
| Try lock | `ifAvailable: true` |
|
||||
| Timeout | AbortController + signal |
|
||||
| Read/write | shared / exclusive mode |
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **localStorage 로 fake lock**: race.
|
||||
- **Cross-origin lock 가정**: 안 됨.
|
||||
- **Re-entrant lock**: deadlock.
|
||||
- **Long work in lock**: 다른 tab freeze.
|
||||
- **Auto-release 무시**: leak 가정.
|
||||
- **Web Locks 만 + sync 없음**: BroadcastChannel 함께.
|
||||
|
||||
## 🤖 LLM 활용 힌트
|
||||
- Web Locks 가 cross-tab mutex 의 답.
|
||||
- Leader election + BroadcastChannel 가 SharedWorker 의 작은 alternative.
|
||||
- Auto-release 가 큰 강점.
|
||||
- Browser support 가 좋음 (modern).
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[Web_Service_Worker_Patterns]]
|
||||
- [[Web_PWA_Service_Worker]]
|
||||
- [[CS_Distributed_Locks]]
|
||||
Reference in New Issue
Block a user