LockService ambiguity

2019-07-04 02:31发布

问题:

In the LockService documentation: https://developers.google.com/apps-script/service_lock it states that "getPublicLock() - Gets a lock that prevents concurrent access to a section of code by simultaneous executions for the current user"

So the query is around the comment: "section of code". If I have multiple sections of code that use the LockService.getPublicLock(), are they essentially independent locks?

For example:

function test1() {
    var lock = LockService.getPublicLock();

    if (lock.tryLock(10000)) {
        // Do some critical stuff
        lock.releaseLock();
    }
}


function test2() {
    var lock = LockService.getPublicLock();

    if (lock.tryLock(10000)) {
        // Do some critical stuff
        lock.releaseLock();
    }
}

If I have two invocations of my script concurrently executing, with one user accessing test1() and another user accessing test2(), will they both succeed? Or as it alludes to in this post: http://googleappsdeveloper.blogspot.co.uk/2011/10/concurrency-and-google-apps-script.html are the locks simply at the script level? So for this scenario, only one of test1() or test2() would succeed but not both.

If it is truly as the documentation states, and both will succeed, what denotes a 'section of code' ?? Is it the line numbers that the LockService.getPublicLock() appears on or is it the surrounding function?

回答1:

There is only one public lock and only one private lock.

If you wish to have several locks you'll need to implement some sort of named lock service yourself. An example below, using the script database functionality:

var validTime = 60*1000;  // maximum number of milliseconds for which a lock may be held
var lockType = "Named Locks";  // just a type in the database to identify these entries
function getNamedLock( name ) {
  return {
    locked: false,
    db : ScriptDb.getMyDb(),
    key: {type: lockType, name:name },
    lock: function( timeout ) {
      if ( this.locked ) return true;
      if ( timeout===undefined ) timeout = 10000;
      var endTime = Date.now()+timeout;
      while ( (this.key.time=Date.now()) < endTime ) {
        this.key = this.db.save( this.key );
        if ( this.db.query( 
              {type: lockType, 
               name:this.key.name, 
               time:this.db.between( this.key.time-validTime, this.key.time+1 ) }
            ).getSize()==1 )
          return this.locked = true;        // no other or earlier key in the last valid time, so we have it
        db.remove( this.key );              // someone else has, or might be trying to get, this lock, so try again
        Utilities.sleep(Math.random()*200); // sleep randomly to avoid another collision
      }
      return false;
    },
    unlock: function () {
      if (this.locked) this.db.remove(this.key);
      this.locked = false;
    }
  }
}

To use this service, we'd do something like:

var l = getNamedLock( someObject );
if ( l.lock() ) {
  // critical code, can use some fields of l for convenience, such as
  // l.db - the database object
  // l.key.time - the time at which the lock was acquired
  // l.key.getId() - database ID of the lock, could be a convenient unique ID
} else {
  // recover somehow
}
l.unlock();

Notes:

  1. This assumes that the database operation db.save() is essentially indivisible - I think it must be, because otherwise there would be BIG trouble in normal use.

  2. Because the time is in milliseconds we have to assume that it is possible for more than one task to try the lock with the same time stamp, otherwise the function could be simplified.

  3. We assume that locks are never held for more than a minute (but you can change this) since the execution time limit will stop your script anyway.

  4. Periodically you should remove all locks from the database that are more than a minute old, to save it getting cluttered with old locks from crashed scripts.