7.0 KiB
7.0 KiB
id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
| id | title | category | status | source_trust_level | verification_status | created_at | updated_at | tags | tech_stack | applied_in | aliases | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| web-web-locks-api | Web Locks API — tab 간 mutex | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
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
await navigator.locks.request('my-resource', async (lock) => {
// 매 origin 에서 1 곳 만.
await criticalSection();
});
// → Lock 자동 release.
Shared lock (read)
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
// 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)
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)
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
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.
→ 함께 사용 흔함.
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
// 여러 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
// 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
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 안 됨
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 안 사용
// 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 (옛 방법)
// 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).