Greasy Fork

Greasy Fork is available in English.

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.

当前为 2025-11-01 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/554436/1687617/GM_lock.js

作者
𝖢𝖸 𝖥𝗎𝗇𝗀
版本
0.0.1.20251101174333
创建于
2025-11-01
更新于
2025-11-01
大小
2.7 KB
许可证
暂无

GM_lock — a tiny cross-tab async lock for userscripts

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.


Why?

Userscripts often run in several places at once (multiple tabs, iframes, reruns). If you have code that must not run concurrently (e.g., rate-limited API calls, queue processing, cache writes), GM_lock(tag, fn) makes that section execute exclusively.


Install

Just copy the function into your script (or @require it, if you publish as a library). Requires a manager that supports:

  • GM.setValue, GM.listValues, GM.deleteValue
  • GM_addValueChangeListener, GM_removeValueChangeListener

Tested with Tampermonkey, Violentmonkey and ScriptCat. (Greasemonkey 4+ may require adapting API names.)


API

await GM_lock(tag: string, func: () => (Promise<any> | any)): Promise<any>
  • tag: A string identifying the lock scope. Same tag ⇒ same lock.
  • func: Your critical section (sync or async).
  • returns: Resolves/rejects with func’s result/error.

Usage

Basic

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

How it works (quickly)

  • Each contender writes GM_lock::<tag>::<lockId> and announces via GM_lock_changed::<tag>.
  • Everyone listens with GM_addValueChangeListener.
  • The lexicographically smallest contender key wins the election and runs func.
  • On finish it deletes its key and announces completion.
  • A 1 s recovery timer cleans up stale/older keys if no fresh events arrive (handles old records and missed changes).
  • Tiny delays (50 ms) help reduce race windows during the election.

This design avoids busy-waiting: it’s event-driven and cross-context.


Notes & Tips

  • Use short, stable tags. Different features ⇒ different tags.
  • Your func can throw/reject; that error bubbles out of GM_lock.
  • Don’t block the event loop inside func for long periods; prefer awaiting async work.
  • If you kill a tab mid-lock, the 1 s recovery pass helps other contenders progress.
  • Fairness is best-effort (based on key sort order) but not strictly FIFO under extreme contention.

License

MIT — do whatever, just keep the notice.