Naar inhoud
lightbulb Welkom op de nieuwe kennisbank | We hebben de docs volledig vernieuwd met meer dan 160 features. Bekijk wat nieuw isarrow_forward

Lock Service tegen race conditions

Voorkom race conditions in gedeelde Apps Script resources met de Lock Service: script-, document- en gebruikerslocks, plus waitLock en tryLock.

Waarom je de Lock Service nodig hebt

Wanneer meerdere gebruikers tegelijk een formulier indienen, een trigger afvuurt terwijl een vorige nog loopt, of meerdere tabbladen hetzelfde script uitvoeren, kunnen race conditions optreden. Twee uitvoeringen lezen dan dezelfde data, verwerken die parallel en schrijven overlappende resultaten terug. Het gevolg is verloren rijen, dubbele ID's of een teller die te laag uitkomt.

De Lock Service van Apps Script biedt drie typen locks:

  • Script lock: blokkeert alle uitvoeringen van het script, voor alle gebruikers.
  • Document lock: blokkeert toegang tot het huidige document (alleen beschikbaar in container-gebonden scripts).
  • User lock: per-gebruiker lock; uitvoeringen van andere gebruikers worden niet geblokkeerd.
function welkLockType() {
  const scriptLock = LockService.getScriptLock();
  const docLock = LockService.getDocumentLock();
  const userLock = LockService.getUserLock();
}

Basis lock-patroon

function veiligSchrijven() {
  const lock = LockService.getScriptLock();

  try {
    lock.waitLock(30000);

    const blad = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Log');
    const volgendeRij = blad.getLastRow() + 1;
    blad.getRange(volgendeRij, 1).setValue(new Date());
    blad.getRange(volgendeRij, 2).setValue(Session.getActiveUser().getEmail());
    blad.getRange(volgendeRij, 3).setValue('Verwerkt');

  } catch (e) {
    Logger.log('Lock niet verkregen of fout: ' + e.message);
    throw e;
  } finally {
    lock.releaseLock();
  }
}
warning

Geef de lock altijd vrij in finally

Roep releaseLock() altijd aan in een finally-blok. De lock wordt weliswaar automatisch vrijgegeven zodra de uitvoering eindigt, maar door hem direct los te laten houd je de exclusieve toegang zo kort mogelijk en laat je wachtende uitvoeringen sneller door. Een Apps Script-uitvoering duurt zelf maximaal 6 minuten, dus zo lang kan een lock in het uiterste geval vastgehouden worden.

Niet-blokkerende locks met tryLock

Gebruik tryLock() wanneer je niet wilt wachten maar direct wilt afhaken als het script al draait, bijvoorbeeld bij een trigger die vaak afvuurt.

function probeerLock() {
  const lock = LockService.getScriptLock();

  if (!lock.tryLock(5000)) {
    Logger.log('Script wordt al uitgevoerd, probeer later opnieuw');
    return false;
  }

  try {
    Logger.log('Lock verkregen, bezig...');
    Utilities.sleep(2000);
    return true;
  } finally {
    lock.releaseLock();
  }
}

Teller-patroon zonder race condition

function verhoogTeller() {
  const lock = LockService.getScriptLock();

  try {
    lock.waitLock(10000);

    const props = PropertiesService.getScriptProperties();
    const huidig = parseInt(props.getProperty('teller') || '0', 10);
    props.setProperty('teller', String(huidig + 1));

    Logger.log(`Teller: ${huidig + 1}`);
  } finally {
    lock.releaseLock();
  }
}

Formulierverwerking met lock

function onFormSubmit(e) {
  const lock = LockService.getScriptLock();

  try {
    lock.waitLock(30000);

    const antwoord = e.response;
    const items = antwoord.getItemResponses();
    const naam = items[0].getResponse();
    const email = antwoord.getRespondentEmail();

    const blad = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Registraties');
    const volgendeRij = blad.getLastRow() + 1;

    blad.getRange(volgendeRij, 1, 1, 3).setValues([[
      new Date(),
      naam,
      email,
    ]]);

    GmailApp.sendEmail(email, 'Registratie bevestigd', `Beste ${naam}, je bent geregistreerd.`);

  } finally {
    lock.releaseLock();
  }
}

Lock-status controleren

function controleerLockStatus() {
  const lock = LockService.getScriptLock();

  Logger.log(`Lock vastgehouden: ${lock.hasLock()}`);

  if (!lock.hasLock()) {
    if (lock.tryLock(1000)) {
      Logger.log('Lock verkregen');
      lock.releaseLock();
    } else {
      Logger.log('Lock niet beschikbaar');
    }
  }
}
lightbulb

Kies een realistische timeout

Stem de timeout van waitLock of tryLock af op hoe lang het kritieke gedeelte realistisch duurt. Te kort en je krijgt onnodige fouten bij drukte; te lang en wachtende gebruikers blijven hangen. Voor een snelle spreadsheet-append is 10 tot 30 seconden meestal genoeg. Houd het kritieke blok klein: lees, wijzig en schrijf binnen de lock, en doe trage taken zoals e-mail versturen waar mogelijk daarbuiten.

Hoe lang houdt een script een lock maximaal vast?

Een lock wordt automatisch vrijgegeven zodra de uitvoering eindigt. Omdat een Apps Script-uitvoering zelf maximaal 6 minuten duurt, kan een lock in het uiterste geval dus zo lang vastgehouden worden. De parameter van waitLock(ms) of tryLock(ms) is geen vasthoudtijd maar een wachttijd: zo lang wacht je hoogstens om de lock te verkrijgen.

Wat is het verschil tussen waitLock en tryLock?

waitLock(ms) blokkeert totdat de lock beschikbaar is of de wachttijd verstrijkt, en gooit dan een fout. tryLock(ms) probeert de lock te krijgen binnen de wachttijd en retourneert true of false zonder een fout te gooien. Gebruik tryLock als je netjes wilt afhaken zonder het script te laten crashen.

Deelt elke trigger-uitvoering dezelfde lock-namespace?

Script locks zijn gedeeld over alle uitvoeringen van hetzelfde script-project, dus een trigger en een handmatige run concurreren om dezelfde script lock. User locks gelden per individuele gebruiker, en document locks per container-document.

Wanneer kies ik een user lock of document lock in plaats van een script lock?

Kies een script lock als alle uitvoeringen om dezelfde gedeelde resource strijden, bijvoorbeeld één centrale teller of logblad. Kies een user lock als elke gebruiker zijn eigen data heeft en je alleen botsingen binnen dezelfde gebruiker wilt voorkomen. Kies een document lock voor scripts die aan één specifiek document gebonden zijn.

Hoe debug ik een lock die nooit doorkomt?

Zoek in de uitvoeringslogboeken naar foutmeldingen rond waitLock. Een lock die niet doorkomt betekent vrijwel altijd dat een andere uitvoering nog binnen het kritieke gedeelte zit; controleer of die uitvoering trage taken binnen de lock doet die je beter erbuiten kunt zetten. Omdat locks bij het einde van een uitvoering automatisch verdwijnen, blijft een lock nooit langer dan de maximale uitvoeringstijd hangen.

Beschermt de Lock Service ook tegen gelijktijdige toegang vanuit verschillende projecten?

Nee. Een script lock geldt alleen binnen hetzelfde Apps Script-project. Twee aparte projecten die naar dezelfde spreadsheet schrijven, weten niets van elkaars locks. Bundel in dat geval de schrijflogica in één project of gebruik een gedeeld document lock op het container-document.

Veilige incrementele ID-generator

  1. Haal de script lock op met LockService.getScriptLock().
  2. Wacht maximaal 10 seconden op de lock met lock.waitLock(10000).
  3. Lees de huidige teller uit PropertiesService.getScriptProperties().
  4. Verhoog de teller en sla die meteen terug op.
  5. Geef de nieuwe waarde terug als het volgende ID.
  6. Geef de lock vrij in een finally-blok.

De Lock Service is essentieel voor elk Apps Script dat gedeelde state beheert. Zonder locks riskeer je data-corruptie in drukbezochte spreadsheets of formulierverwerkers; met een klein, zorgvuldig afgebakend kritiek gedeelte blijft de overhead minimaal.