此代码是否正确锁定了 onFormSubmit(e) 方法?

Does this code lock the onFormSubmit(e) method correctly?

我有一个 Google 电子表格脚本函数 onFormSubmit(e),只要有人提交我的表单,它就会被触发器调用。

在该函数中,我创建了一个模板文档的临时副本,并根据提交的表单值在其中进行搜索和替换。 然后将该临时副本转换为 pdf 并通过电子邮件发送到多个电子邮件地址,然后删除。

现在我读到锁定可能是这里的一个问题,所以我决定获得一个 LockService.getScriptLock(),并将我的脚本包裹在一个锁中,首先尝试获得一个 30 秒的锁 lock.waitLock(30000),然后在脚本结束时释放锁lock.releaseLock(),防止并发问题。

当有人提交表单时,脚本本身似乎 运行 不会超过 13 秒。

总之,代码看起来有点像这样:

function onFormSubmit(e) {
    // Get a public lock on the script
    var lock = LockService.getScriptLock();
    try {
      lock.waitLock(30000);  // Wait for 30 seconds

      // copy template, search and replace etc...

      lock.releaseLock()

    }catch(e) {
      Logger.log('Could not obtain lock after 30 seconds.');
      MailApp.sendEmail("admin@something.com", "Could not obtain lock after 30 seconds.");
    }
}

还有什么我应该做的吗?我锁定太多还是太少?我记得在 Java 的日子里,长时间锁定全局应用程序会话上下文是一种罪过;另外制作模板的副本,并打开模板的副本进行搜索和替换,这需要某种锁定吗?

根据您所描述的性能计时,您可能会发现您可以摆脱所展示的功能。至少,直到那一天,由于你无法控制的力量,处理需要额外的时间。

你的锁处理的基本结构没问题,但是你在 try..catch 里面做的太多了。请注意,任何异常都会被捕获,但您的处理仅限于waitlock()超时异常,因此您应该避免任何其他可能产生异常的语句try 区块.

以下是重构函数的方式:

function onFormSubmit(e) {
  // Perform any "pre" operations on private
  // or non-critical shared resources.
  var sheet = e.range.getSheet();   // for example, accessing event object

  // Get a public lock on the script
  var lock = LockService.getScriptLock();  // Choose appropriate scope
  try {
    // just attempt to get the lock here - nothing else that may
    // throw an exception.
    lock.waitLock(30000);  // Wait for 30 seconds
  }catch(e) {
    // Handle lock exception here
    Logger.log('Could not obtain lock after 30 seconds.');
    MailApp.sendEmail("admin@something.com", "Could not obtain lock after 30 seconds.");
  }

  ////// Critical section begins   vvvvv

  // operate only on shared modifiable data

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

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

  // Ensure the lock is released before exiting.
  if (lock.hasLock()) {
    throw new Error("Lock violation");
  }
  else {
    return;
  } 
}

代码Lock Service provides a way to fence off a critical section。关键部分是我们控制对共享资源的访问的地方,共享资源必须稳定以供读取或写入。这里的原理可以总结为:

  • 尽可能推迟进入临界区。 任何可以在临界区之外执行而不依赖于临界区的操作都应该提前完成。这不是一个硬性规定,但根据我的经验,我发现它支持下一个目标的纪律,迫使你(或下一个开发人员)预先分析一行新代码是属于还是不属于关键部分。
  • 选择合适的锁范围。这将取决于封装在临界区中的资源操作类型。
  • 一旦进入关键部分,尽快退出。这个目标是通过将你自己限制在依赖于 "shared modifiable data" 的操作来实现的,也称为 "critical resources".
  • 限制在临界区之后执行的操作。 同样,这是一种纪律。在关键部分之外,我们应该再次只管理私有或非关键共享资源。但在这里我们进一步限制自己,只限于依赖于临界区发生的事情的操作。我们的目标是在退出临界区后尽快退出函数。
  • 释放锁,并仔细检查。如果我们在从函数返回时不小心留下了锁,它只会在执行脚本退出或正在执行时释放被杀死(例如 6 分钟后)。这是通过在我们的 releaseLock()hasLock() 验证检查之间结束 post 关键部分代码来实现的。

I remember in my Java days it was a sin to lock the global application session context for too long...

在Google Apps Script中,我们没有这个概念;没有全局应用程序会话。脚本可以异步执行,一个用户的多个实例以及其他用户的多个实例的可能性。在此环境中,关注点转移到仅在 适当范围 锁定,以限制锁定的可能影响。

...what about making a copy of the template, and opening the copy of the template for search and replace, does that require some sort of locking?

  • 复制模板不需要关键部分,因为我们正在从稳定的共享资源(模板)中读取以创建(大概)私有资源。 (除非模板本身可以被脚本修改。)
  • 打开 private 副本以进行搜索和替换 - 不重要,不需要锁定。