Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to understand LockService and implement it correctly?

Summary of Code

I have a Google Apps Script project that is used by around 80 users within a specific domain, however the app is executed by me (ie Publish > Deploy as web app > Execute the app as: Me).

One of the functions of the script is to populate a Google Sheet from a custom form (using HTML Service) and then notify myself and the submitting user (who is identified through the use of a simple login system and cookies).

It has been working fine for about 6 months, however on 1-2 occasions the notification email has been sent but the Google Sheet entry has not appeared.

I am thinking this might be due to concurrent use of the script (as two notification emails had the same timestamp) and have recently learned of Lock Service.

I am using this post to ensure I have the correct understanding of Lock and how to implement it in order to prevent entries not appearing in the Google Sheet due to concurrent script usage.

Implementation

The pseudo code of my scenario is:

Code.gs

var active_spreadsheet = SpreadsheetApp.openById("bbb");

// BEGIN - start lock here

var lock = LockService.getScriptLock();
try {
   lock.waitLock(30000); // wait 30 seconds for others' use of the code section and lock to stop and then proceed
 } catch (e) {
   Logger.log('Could not obtain lock after 30 seconds.');
 }

var active_sheet = active_spreadsheet.getSheetByName("ENTRIES");
var new_start_row = active_sheet.getLastRow() + 1;

//  Do lots of stuff - ie apply dynamic background colors based on previous entries colors, define the target range and set values, set data validations  

SpreadsheetApp.flush(); // applies all pending spreadsheet changes
lock.releaseLock();

// END - end lock here

return; 

Questions

01) Are the implementations of LockService, getScriptLock(), waitLock() and releaseLock() correct?

02) Is it recommended to use SpreadsheetApp.flush(), and if so is the implementation above correct?

Terminology (for reference)

from: https://developers.google.com/apps-script/reference/lock

Lock:
A representation of a mutual-exclusion lock.

LockService:
Prevents concurrent access to sections of code.

The Lock class has 4 methods:

hasLock()
Boolean, Returns true if the lock was acquired.

releaseLock()
void, Releases the lock, allowing other processes waiting on the lock to continue.

tryLock(timeoutInMillis)
Boolean, Attempts to acquire the lock, timing out after the provided number of milliseconds.

waitLock(timeoutInMillis)
void, Attempts to acquire the lock, timing out with an exception after the provided number of milliseconds.

The LockService class has 3 methods:

getDocumentLock()
Lock, Gets a lock that prevents any user of the current document from concurrently running a section of code.

getScriptLock()
Lock, Gets a lock that prevents any user from concurrently running a section of code.

getUserLock()
Lock, Gets a lock that prevents the current user from concurrently running a section of code.

like image 656
user1063287 Avatar asked Apr 05 '17 06:04

user1063287


2 Answers

In the above pseudo code, once the script doesn't get a lock it will still proceed to run the code. Is that the intended behavior? It is a better practice or option to throw a server busy message to the user. Like so:

var active_spreadsheet = SpreadsheetApp.openById("bbb");

// BEGIN - start lock here

var lock = LockService.getScriptLock();
try {
    lock.waitLock(30000); // wait 30 seconds for others' use of the code section and lock to stop and then proceed
} catch (e) {
    Logger.log('Could not obtain lock after 30 seconds.');
    return HtmlService.createHtmlOutput("<b> Server Busy please try after some time <p>")
    // In case this a server side code called asynchronously you return a error code and display the appropriate message on the client side
    return "Error: Server busy try again later... Sorry :("
}

// note:  if return is run in the catch block above the following will not run as the function will be exited

var active_sheet = active_spreadsheet.getSheetByName("ENTRIES");
var new_start_row = active_sheet.getLastRow() + 1;

//  Do lots of stuff - ie apply dynamic background colors based on previous entries colors, define the target range and set values, set data validations  

SpreadsheetApp.flush(); // applies all pending spreadsheet changes
lock.releaseLock();

// END - end lock here

return;
like image 199
Jack Brown Avatar answered Oct 07 '22 22:10

Jack Brown


So I think I have found an issue with the locking system. That is, when you trigger a popup box(that appears within a sheet) in the script, it scraps the lock... Or something like that, because it definitely doesn't keep the lock. I have a segment of code I am trying to run and basically without the popup box, the locking system works. There is no issue, the haslock will indeed return false when the first user has the lock... but then you introduce a popup box and the locking system doesn't work like it should, and grants a lock to the 2nd user even tho the popup box and that line of code, or any lines of code after it for that matter, have not been executed.. I tried it with a delay after the popup box because I originally thought that maybe it's not waiting for the input back so therefore it just kinda skips over the popup box after it has pushed it out to the sheet... but even with a long delay after the popup box portion of the code(and script is still running right in front of my face) the 2nd user is still granted a lock.. Maybe I am doing this wrong but the popup box is the only thing that throws my code off. I tried the try, catch, and if, if else... and no luck, i even tried just an if,else... I know it is not because the trylock/waitlock is too long, or too short I've tried a range of values, I also know it is not the sleep bc I've tried a range of values, as well as different placements within my code. Tested these quite heavily, on different google user accounts on multiple different days bc I originally thought it was me just being dumb or something.. but it really just seems like the popup box is the only thing that sends it off the reservation.. Examples of when the locking system works

function myFunction() {
  var lock = LockService.getScriptLock();
  lock.tryLock(5000);
  if (!lock.hasLock()) {
    Logger.log('Could not obtain lock after 5 seconds.');
    return;
  }
  else if (lock.hasLock()) {
    Logger.log('got the lock');
    //Browser.inputBox("TESTING123");
    Utilities.sleep(10000);
  }
lock.releaseLock();
}

2

function myFunction() {
  var lock = LockService.getScriptLock();
  lock.tryLock(5000);
  if (!lock.hasLock()) {
    Logger.log('Could not obtain lock after 5 seconds.');
  }
  else {
    Logger.log('got the lock');
    //Browser.inputBox("TESTING123");
    Utilities.sleep(10000);
  }
lock.releaseLock();
}

with the proposed try,catch statement

function myFunction() {
  var lock = LockService.getScriptLock();
  try {
    lock.waitLock(5000); // wait 5 seconds try to get lock
  } catch (e) {
    Logger.log('Could not obtain lock after 5 seconds.');
  }
  Utilities.sleep(10000);
  //Browser.inputBox("TESTING123");
  lock.releaseLock();
}

it does indeed catch the error and display the popup saying it couldn't get it

Examples when it doesnt work

function myFunction() {
  var lock = LockService.getScriptLock();
  lock.tryLock(5000);
  if (!lock.hasLock()) {
    Logger.log('Could not obtain lock after 5 seconds.');
    return;
  }
  else if (lock.hasLock()) {
    Logger.log('got the lock');
    Browser.inputBox("TESTING123");
    Utilities.sleep(10000);
  }
 lock.releaseLock();
}

2

function myFunction() {
  var lock = LockService.getScriptLock();
  lock.tryLock(5000);
  if (!lock.hasLock()) {
    Logger.log('Could not obtain lock after 5 seconds.');
  }
  else {
    Logger.log('got the lock');
    Browser.inputBox("TESTING123");
    Utilities.sleep(10000);
  }
lock.releaseLock();
}

with the proposed try catch statement

function myFunction() {
  var lock = LockService.getScriptLock();
  try {
    lock.waitLock(5000); // wait 5 seconds try to get lock
  } catch (e) {
    Logger.log('Could not obtain lock after 5 seconds.');
  }
  Utilities.sleep(10000);
  Browser.inputBox("TESTING123");
  lock.releaseLock();
}

the popup box appears in all cases. It shouldn't, because the popup box hasn't been filled in and resolved... even with putting a delay after the popup box line. I have decided to take to making my own locking system as it doesnt seem that the google system will be working for me since I need the lock to remain valid after popups.

like image 1
keith walker Avatar answered Oct 07 '22 22:10

keith walker