Lock script execution on a document when script is deployed as web app

拥有回忆 提交于 2021-02-08 07:32:07

问题


My Google Apps Script is deployed as web app and can be accessed by any user. Its functionality is to open and change the text in that document.

I send the script a document ID as a query parameters like so:

https://script.google.com/a/macros/s/AKfycbzCP...TnwHUbXxzDM/exec?docId=1_cMN0nuJadBw6QVjKtdEA6eXhE8ubIoxIJai1ticxnE`

Web app opens the document and changes the text in the document.

function doGet(e){
  var params=e.parameters;
  var doc = DocumentApp.openById(params['docId']);
  ...
  /* change text of the document */
}

Problem

Now when there are more than one user trying to run the app-script simultaneously on the same document, the web app fails to handle concurrency and functionality breaks.

I looked into Lock Service but lock service's document lock only works for container bound scripts and not for web apps.

Then I tried to set property using cache service var cache = CacheService.getDocumentCache(); and property service var documentProperties = PropertiesService.getDocumentProperties(); for the document but document property and document cache returns null in web apps and are restricted to container bound scripts only, as stated in the documentation:

If this method is called outside of the context of a containing document (such as from a standalone script or web app), this method returns null.

Is there any way to handle concurrency for script execution in a document when Google Apps Script is deployed as web app. (Not container bound)


回答1:


As @azawaza points out, you should be using a Lock with an appropriate scope, and a Script Lock is the better fit for your scenario. This is discussed in Does this code lock the onFormSubmit(e) method correctly?

If the critical section of code is sufficiently quick, then there's no real concern about making the user updating Document 2 wait while another update to Document 1 proceeds; they won't wait long. Something like:

function doGet1(e){
  // Perform any "pre" operations on private
  // or non-critical shared resources.
  var params=e.parameters;

  // Get a script lock, because we're about to modify a shared resource.
  var lock = LockService.getScriptLock();
  // Wait for up to 10 seconds for other processes to finish.
  lock.waitLock(10000);

  ////// Critical section begins   vvvvv

  var doc = DocumentApp.openById(params['docId']);

  // change text of the document here

  doc.saveAndClose();

  ////// Critical section ends     ^^^^^
  lock.releaseLock();

  // Continue with operations on private
  // or non-critical shared resources.

  return ContentService.createTextOutput("Document updated.")
}

Specific resource locks

Out of the box, the Google Apps Script Lock Service is designed to protect Critical Sections of code. If we want to control access to a specific resource (perhaps for a long-ish time), such as a Google Document, we can adapt it by changing what we are "locking".

In this example, the Lock service protects a critical section wherein Script Properties are checked and updated. These properties have "keys" that match our docId parameter; the value is not important, as we can use simple existence of the key as our test.

Note: Currently, this script could block a user "forever" (until script times out) if another script fails to delete the property protecting their use of the shared document. You'd want to take greater care in production code.

function doGet2(e){
  // Perform any "pre" operations on private
  // or non-critical shared resources.
  var params=e.parameters;

  // Wait for exclusive access to docId
  var ready = false;
  // Get a script lock, because we're about to modify a shared resource.
  var lock = LockService.getScriptLock();

  while (!ready) {
    // Wait for up to 1 second for other processes to finish.
    if (lock.tryLock(1000)) {
      ////// Critical section begins   vvvvv      

      var properties = PropertiesService.getScriptProperties();

      // If nobody has "locked" this document, lock it; we're ready.
      if (properties.getProperty(docId) == null) {
        // Set a property with key=docId.
        properties.setProperty(docId,"Locked"); 
        ready = true;
      }

      ////// Critical section ends     ^^^^^
      lock.releaseLock();
    }
  }

  // We have exclusive access to docId now.

  var doc = DocumentApp.openById(params['docId']);

  // change text of the document here

  doc.saveAndClose();

  // Delete the "key" for this document, so others can access it.
  properties.deleteProperty(docId); 

  return ContentService.createTextOutput("Document updated.")
}

Named Locks

The logic that we've used in the previous example can be encapsulated into an Object to provide a more elegant interface. In fact, Bruce McPherson has done just that with his cNamedLock Library, described on his Desktop Liberation site. Using that library, you can implement document-specific locking like this:

function doGet3(e){
  // Perform any "pre" operations on private
  // or non-critical shared resources.
  var params=e.parameters;

  // Get a named lock.
  var namedLock = new NamedLock().setKey(docId);

  namedLock.lock();
  ////// Critical section begins   vvvvv      

  // We have exclusive access to docId now.

  var doc = DocumentApp.openById(params['docId']);

  // change text of the document here

  doc.saveAndClose();

  ////// Critical section ends     ^^^^^
  namedLock.unlock();

  return ContentService.createTextOutput("Document updated.")
}


来源:https://stackoverflow.com/questions/34878161/lock-script-execution-on-a-document-when-script-is-deployed-as-web-app

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!