# Lock Service tegen race conditions [[TOC]] ## 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. ```javascript function welkLockType() { const scriptLock = LockService.getScriptLock(); const docLock = LockService.getDocumentLock(); const userLock = LockService.getUserLock(); } ``` ## Basis lock-patroon ```javascript 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(); } } ``` :::warn title="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. ```javascript 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 ```javascript 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 ```javascript 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 ```javascript 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'); } } } ``` :::tip title="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. ::: :::faq ### 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. ::: :::howto title="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.