--- 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) { 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]]