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

Lock Service not always locking in Google Apps Script

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

我将数字存储为脚本 属性。当我需要一个新的订单号时,应用程序获取当前值,递增它,并保存新值以供下次使用。为确保两个人 运行 应用程序不会获得相同的号码,对脚本 属性 的访问被置于脚本锁或互斥体中。

这很好用,每天都有数百次调用通过该函数。但是在过去的一个月里,有两次,两个用户都得到了相同的订单号。

// 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;
  }
}

我是不是做错了什么?问题出在Google这边吗?

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

我将尝试实施他的建议,因为它们不会造成伤害,但我想听听关于这个问题的任何其他想法。

这实际上是一个比 think.The 问题更简单的问题,问题在以下代码部分:

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

您将 属性 设置为 prevValue + 1,然后立即将 newValue 分配给相同的 属性。在您的代码中,这两行同步执行,但 setProperty 方法本质上是异步的。在极少数情况下,写入 Google 服务器上的 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 在下一个用户运行它之前被正确更新,并且 isTimeUp() 函数确保在写入 PropertiesService 的互联网故障事件中没有无限循环(如果我并没有在最后写这篇文章时变得懒惰,如果更新超时我也会回滚任何效果。但现在我们开始陷入困境 ;)