锁定服务并不总是锁定在 Google Apps 脚本中
Lock Service not always locking in Google Apps Script
在我的 Google Apps 脚本应用程序中,有一个地方可以生成唯一的递增订单号。为此,我使用了内置 LockService
和 PropertiesService
。
我将数字存储为脚本 属性。当我需要一个新的订单号时,应用程序获取当前值,递增它,并保存新值以供下次使用。为确保两个人 运行 应用程序不会获得相同的号码,对脚本 属性 的访问被置于脚本锁或互斥体中。
这很好用,每天都有数百次调用通过该函数。但是在过去的一个月里,有两次,两个用户都得到了相同的订单号。
// 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 的互联网故障事件中没有无限循环(如果我并没有在最后写这篇文章时变得懒惰,如果更新超时我也会回滚任何效果。但现在我们开始陷入困境 ;)
在我的 Google Apps 脚本应用程序中,有一个地方可以生成唯一的递增订单号。为此,我使用了内置 LockService
和 PropertiesService
。
我将数字存储为脚本 属性。当我需要一个新的订单号时,应用程序获取当前值,递增它,并保存新值以供下次使用。为确保两个人 运行 应用程序不会获得相同的号码,对脚本 属性 的访问被置于脚本锁或互斥体中。
这很好用,每天都有数百次调用通过该函数。但是在过去的一个月里,有两次,两个用户都得到了相同的订单号。
// 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 的互联网故障事件中没有无限循环(如果我并没有在最后写这篇文章时变得懒惰,如果更新超时我也会回滚任何效果。但现在我们开始陷入困境 ;)