Web Locks API: így ne fusson kétszer ugyanaz a háttérmunka két böngészőfülben

Így akadályozhatod meg, hogy több böngészőfül egyszerre végezze ugyanazt a háttérmunkát a Web Locks API-val.

Ugyanaz a webalkalmazás két fülben nem két külön világ. Mindkettő írhatja az IndexedDB-t, egyszerre ürítheti az offline küldési sort, vagy párhuzamosan futtathat drága inicializálást. Ilyenkor a localStorage-ba tett „én vagyok a vezető” jelző gyorsan versenyhelyzet, lejárati idők és félbehagyott állapotok gyűjteményévé válik.

A Web Locks API erre ad a böngésző által kezelt, originön belüli kizárást. Nem üzenetküldő csatorna, hanem zárolási sor: egy névhez kérünk jogot, a böngésző pedig csak kompatibilis kéréseket enged egyszerre futni.

Két böngészőfül egy központi záron keresztül biztonságosan éri el a közös adatforrást

A lock a callback idejére él

A belépési pont a navigator.locks.request(). A megadott név alkalmazásbeli erőforrást jelöl: lehet app:outbox-sync, app:token-refresh vagy egy dokumentum azonosítója. A névnek érdemes prefixet adni, mert ugyanazon origin minden saját oldala és workere ugyanabban a zárolási névtérben van.

A lockot nem nekünk kell feloldani. A callback által visszaadott promise lezárulásakor oldódik fel; az API specifikációja szerint ez a callback hibájánál is megtörténik. Emiatt a kritikus szakasz legyen kicsi és egyértelmű: csak az legyen benne, amit tényleg egy fülre akarunk korlátozni.

export async function syncOutboxIfLeader(flushOutbox) {
  if (!("locks" in navigator)) return "no-lock-api";

  return navigator.locks.request(
    "app:outbox-sync",
    { ifAvailable: true },
    async (lock) => {
      if (lock === null) return "other-tab-is-syncing";
      await flushOutbox();
      return "synced";
    },
  );
}

A példa szándékosan nem áll sorba. Az ifAvailable: true esetén a callback azonnal null-t kap, ha másik kontextus fogja a lockot. Ez jó periodikus vagy esemény hatására induló outbox-szinkronhoz: a másik fül dolgozik, ezért ez a futás kimarad. Ha minden kérésnek sorra kell kerülnie, hagyjuk el ezt az opciót; a request promise-a akkor csak a callback és a felszabadítás után teljesül.

Fontos, hogy ez valódi aszinkron döntés, még ifAvailable mellett is: a böngészőnek más folyamatban futó fülekkel is egyeztethet. Ne olvassunk rá egy külön jelzőváltozót, és ne próbáljuk az eredményt szinkron módon kikövetkeztetni. A callback lock paramétere az egyetlen megbízható válasz arra, hogy ez a futás beléphet-e a kritikus szakaszba.

Nem csak egyetlen író létezik

Az alapértelmezett mód az exclusive: egy néven egyszerre egy holder lehet. A mode: "shared" viszont több olvasót enged, kizárólagos kérés pedig csak akkor indulhat, amikor ezek már véget értek. Ez a klasszikus olvasó–író minta, csak itt fülek és workerek között működik. A specifikáció azt is rögzíti, hogy a várakozó kérés sorrendjét a lock-kompatibilitás mellett a sor eleje korlátozza, ezért ne építsünk arra, hogy egy későbbi shared kérés „átugorja” az előtte váró exkluzív írót.

Gyakori minta a „vezető fül”: az egyik oldal hosszú ideig fogja a app:sync-leader lockot és végzi a háttérszinkront; a többi várakozik. Ha a vezető bezáródik vagy összeomlik, a környezete megszűnik, a lock felszabadul, és egy várakozó kérés megkaphatja. A Web Locks dolgozik Web Workerben is, így a koordinációt nem köti feltétlenül a felülethez.

Amit ez nem old meg

Ez nem adatbázis-tranzakció és nem elosztott lock. Az azonos originre korlátozott böngészős zárolás nem akadályozza meg, hogy két külön felhasználó, két eszköz vagy két szerverpéldány ugyanazt a szerveroldali adatot írja. Az API csak biztonságos kontextusban érhető el, ezért HTTPS kell hozzá, és éles alkalmazásban feature detection, valamint üzletileg helyes fallback szükséges.

A signal opcióval egy még várakozó kérés eldobható AbortSignal segítségével. Ezt használhatjuk lock-várakozási időkorláthoz. A steal ezzel szemben vészkapcsoló: feloldja a meglévő lockot és megelőzi a sort. A korábbi callback kódját nem állítja le, ezért olyan kritikus szakasznál, ahol a régi futás még írhat, könnyen pont azt az egyidejűséget hozzuk vissza, amit el akartunk kerülni.

A lock tehát koordinációs eszköz, nem integritási garancia. Az outbox tényleges feldolgozása legyen idempotens, a szerveroldali írás pedig továbbra is védje magát. Viszont ha eddig egy törékeny localStorage-protokoll csak azért létezett, hogy két fül ne indítsa el ugyanazt a munkát, a navigator.locks jó eséllyel egyszerűbb és megbízhatóbb alap.

Források

Leave a Reply

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük