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();
}
}
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');
}
}
}
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
- Haal de script lock op met
LockService.getScriptLock(). - Wacht maximaal 10 seconden op de lock met
lock.waitLock(10000). - Lees de huidige teller uit
PropertiesService.getScriptProperties(). - Verhoog de teller en sla die meteen terug op.
- Geef de nieuwe waarde terug als het volgende ID.
- 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.