withDocumentLock Helper

When a time-driven trigger and a manual run can overlap — or two editors kick the same sync — you need a lock. This helper wraps LockService.getDocumentLock() so unlock always happens in finally.

What you'll need

  • A container-bound script (Document Lock is for the spreadsheet / doc the script is bound to)
  • A critical section that mutates shared state (sheet writes, watermark updates, appends)

For standalone projects that aren't bound to a document, use LockService.getScriptLock() with the same try/finally shape.

Why try/finally

If you tryLock, do work, and only releaseLock on the happy path, a thrown error leaves the lock held until it times out. Other runs then pile up waiting. finally fixes that.

How to use this snippet

function withDocumentLock(fn, waitMs) { waitMs = waitMs || 30000; var lock = LockService.getDocumentLock(); if (!lock.tryLock(waitMs)) { throw new Error('Could not acquire document lock within ' + waitMs + 'ms'); } try { return fn(); } finally { lock.releaseLock(); } }

Call it with a function (or arrow-free anonymous function for older V8 habits):

function appendRowSafely(values) { return withDocumentLock(function () { var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Log'); sheet.appendRow(values); return sheet.getLastRow(); }); } // appendRowSafely([new Date(), 'synced', 42]);

Tips:

  • Keep the locked section small — hold the lock only around the read-modify-write, not around slow UrlFetchApp calls when you can avoid it.
  • Fail loud when the lock can't be acquired; silent skips look like "the sync ran."
  • Prefer Document Lock when the contention is this spreadsheet; Script Lock when contention is the project across installs.

Happy Coding!

NitroGAS Chrome Extension

Want this snippet handy inside the Apps Script editor? NitroGAS gives you free themes and snippets — plus optional Co-Pilot when you want a boost (1-week, 1-month, or yearly passes — no subscription). Happy Coding!

Get the Extension