GM_lock

A lightweight, dependency-free mutex for Userscripts that ensures **only one tab / context** runs a critical section at a time. It coordinates through `GM.setValue` + `GM_addValueChangeListener`, so it works across multiple tabs, iframes, and even separate scripts that share the same @name/@namespace storage.

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/554436/1692608/GM_lock.js

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

作者
𝖢𝖸 𝖥𝗎𝗇𝗀
版本
0.0.1.20251110045248
建立日期
2025-11-01
更新日期
2025-11-10
尺寸
5.3 KB
授權條款
未知

GM_lock — a tiny cross-tab async lock for userscripts

A lightweight, dependency-free mutex for Userscripts that coordinates via GM.setValue + GM_addValueChangeListener, so only one tab/context runs a critical section at a time. Works across multiple tabs and iframes that share the same userscript storage.

Install / @require This is a library, not a standalone script. Include it from Greasy Fork with:

// @require https://update.greasyfork.org/scripts/554436/1692608/GM_lock.js

(Use the latest build number from the Code tab.)


API

// JavaScript
var GM_lock: (tag: string, func: () => (void | Promise<void>)) => Promise<void>;
  • tag: String that identifies the lock scope. Same tag ⇒ same lock.
  • func: Your critical section (sync or async).
  • returns: A Promise<void> that resolves when func completes.

    • Errors inside func are caught and logged to the console; they do not reject the promise.

Note: The current implementation does not forward a return value from func (it always resolves with void). If you need a result, assign it to outer scope or storage inside func.


Usage

// Only one instance across all tabs will enter this block at a time
await GM_lock('sync-cache', async () => {
  const res = await fetch('https://api.example.com/data');
  const data = await res.json();
  // Do something with data; persist as needed
  await GM.setValue('cache:data', data);
});

Use short, stable tags per feature: e.g. 'queue:upload', 'cache:refresh'.


Manager compatibility & required grants

Requires a userscript manager that supports:

  • GM.setValue
  • GM_addValueChangeListener
  • GM_removeValueChangeListener

Optional/conditional (only if cleanup is enabled; see Options):

  • GM.deleteValues (or the legacy GM_deleteValues alias)

Tested with ScriptCat, Tampermonkey and Violentmonkey; should also work wherever the above APIs are available.


How it works (high level)

  • Each contender constructs a lock id id = <bigTimestamp>_<randomBase36> and uses per-tag keys with the prefix:

    • ack key: GM_lock::<tag>::ack
    • nxt key: GM_lock::<tag>::nxt
  • A contender announces itself by writing to ack and then relays through nxt.

  • Peers collect contenders with the same ack time and, after a small collision interval (default ~500 ms), deterministically elect a winner by sorting the collected ids and choosing the smallest. The winner runs func.

  • After running, the winner updates ack again to release the lock and allow the next election round if needed.

This event-driven design minimizes polling and coordinates cleanly across tabs/frames.


Options (internal constants in the source)

  • COLLISION_INTERVAL = 500 — wait before electing a winner to reduce race windows.
  • CLEANUP_VALUES = false — when true, remove helper keys after use (requires GM.deleteValues).
  • SHOW_LOG = false — when true, emits timing markers via GM.setValue (debug).

Notes & caveats

  • Don’t block the event loop for long inside func; prefer await-based work. (Other contenders coordinate on events and short timers.)
  • If a tab crashes mid-lock, the next election cycle resumes progress thanks to the ack/nxt refresh and interval.
  • Current implementation logs errors instead of rejecting; handle error reporting inside your func if necessary.

License

Public domain — The Unlicense. See the header in the source.