锁定服务并不总是锁定在Google Apps脚本中

时间:2017-05-11 16:50:47

标签: google-apps-script mutex

在我的Google Apps脚本应用程序中,我可以生成一个唯一的递增订单号。为此,我使用了内置LockServicePropertiesService

我将一个数字存储为脚本属性。当我需要一个新的订单号时,应用程序将获取当前值,递增它,并保存下一次的新值。为了确保运行应用程序的两个人没有获得相同的编号,对脚本锁或mutex的访问权限放在脚本锁中。

这很好用,每天都有数百个电话通过该功能。但在过去一个月中,两个用户最终得到了相同的订单号。

// Increment the existing value, and return our new value.
function getPropIncrementViaMutex(propId) {
  try {
    var scriptProperties = PropertiesService.getScriptProperties();
    var lock = LockService.getScriptLock();
    var success = false;
    var prevValue = null;
    var newValue = null;
    var wasSet = null;

    while (!success) {
      success = lock.tryLock(500);
      if (!success) {
        Utilities.sleep(1000);
      } else {
        prevValue = Number(scriptProperties.getProperty(propId));
        scriptProperties.setProperty(propId, prevValue + 1);
        newValue  = Number(scriptProperties.getProperty(propId));
        lock.releaseLock();
        wasSet = (newValue === (prevValue + 1));
      }
    }
    if (wasSet) {
      return newValue;
    } else {
      throw new Error("Error incrementing order number. Previous Value: " + prevValue + " New Value: " + newValue);
    }

  } catch(e) {
    if (lock) {
      lock.releaseLock();
    }
    throw e;
  }
}

我在这里做错了吗?谷歌的问题到底了吗?

一位同事建议将lock.tryLock(500)的锁定时间增加到更高的lock.tryLock(800)。 他还建议在释放锁时,事先调用Utility.sleep(300),这样脚本就有足够的时间来更新属性。 他认为发布是在房产更新之前发生的。

我会尝试实施他的建议,因为他们不会受到伤害,但我想听听有关这个问题的任何其他想法。

1 个答案:

答案 0 :(得分:1)

这实际上是一个比想象中更简单的问题。问题在以下代码部分中:

    prevValue = Number(scriptProperties.getProperty(propId));
    scriptProperties.setProperty(propId, prevValue + 1);
    newValue  = Number(scriptProperties.getProperty(propId));

您将属性设置为prevValue + 1,然后立即将newValue分配给相同的属性。在您的代码中,这两行是同步执行的,但是setProperty方法本质上是异步的。在极少数情况下,在Googles服务器上写入PropertiesService可能会有所延迟。同时,您的代码继续执行。这可能导致在上一行中的propId实际更新之前分配newValue。

解决此问题的方法很简单:

function isTimeUp(startTime, milliSeconds){
  var now = new Date();
  return now.getTime() - startTime.getTime() > milliSeconds
}
.
.    
.
prevValue = Number(scriptProperties.getProperty(propId));
newValue = PrevValue + 1;
scriptProperties.setProperty(propId, newValue);
var start = new Date();
// run while loop for a maximum 30000 milliseconds = 30 seconds, to prevent endless loop
while (Number(scriptProperties.getProperty(propId))) != newValue &&
!isTimeUp(start, 30000)){};
.
.
.

直接从代码中的prevValue而不是从属性中分配newValue。不再有异步问题。
while语句和isTimeUp()函数用于确保在释放锁之前更新属性。可能有些过大,但是您要确保该函数确保在下一个用户运行propId之前已正确更新propId,并且isTimeUp()函数可确保在Internet小故障写入PropertiesService的情况下没有无限循环(如果我不会在编写本书的最后就懒惰,如果更新超时我也会回滚任何效果。但是现在我们开始陷入困境;)