【问题标题】:Is there any race condition below?下面有比赛条件吗?
【发布时间】:2017-11-14 12:52:14
【问题描述】:

我的快递服务器中有以下代码(为简洁起见已将其删减)。我有一个共同的对象,我正在三个不同的宁静端点中添加/修改/阅读。由于 nodejs 中的所有 http 请求都是异步的,我可以同时获取 put 和 get 请求。因此,尽管 PUT 发生了,但可以说状态 更新,我的 GET 可能会得到一个稍微陈旧的响应?

据我了解,我的测试表明这里没有竞争条件。因为更新results 对象是一个同步操作,所有异步操作都应该等待它。有人可以帮助更好地解释这是否正确吗?

    var obj = {};
    const exec = require('child_process').exec;
    app.post('/foo', (req, res) => {
         var result = {};
         result.id = generateSomeRandomId();
         result.failed = 0;
         result.status = 'running'
         //execute some command and update result
         const child = exec('some command');
         child.stdout.on('data',  (data) => {
             //some logic
         });
         child.stderr.on('data',  (data) => {
             result.failed = result.failed + 1;
          });
         child.on('close',  (code, signal) => {
              if (signal !== null && signal !== undefined) {
                    result.status = 'cancelled';
              } else {
                    result.status = 'completed';
                    result.runtime = calculateRunTime();
                }
         });
         result.pid = child.pid;
         obj[result.id] = result; 
         res.send(result); 
    }   

    app.put('/foo/:id', (req, res) => {
         var result =  obj[req.params.id];
         if (result.status === 'running' && result.pid !== undefined) {
              kill(result.pid, 'SIGKILL');
              result.status = 'cancelled';
              result.runtime = calculateRunTime();
         }
         res.send(result);
    }   
    app.get('/foo/:id', (req, res) => {
         var result =  obj[req.params.id];
         res.send(result);
    }

【问题讨论】:

  • 你在说app.post('/foo'吗?没有“比赛”,但您将在任何异步部分运行之前res.post(result);...所以,无论您在//execute some command and update resultresult.pid = child.pid; 之间做什么,都不会影响您在res.send(result); 中发送的内容跨度>
  • 另外,result.failed = result.failed + 1; 导致 results.failed 成为 undefined 或者,如果有任何错误,result.failed === NaN
  • @JaromandaX 是的,我明白了。 post 中的所有异步操作将等到所有同步操作完成。我的问题主要是关于并发的 PUT 和 GET 请求。我会在那里有比赛条件吗?你对 result.failed NaN 部分是正确的,为简洁起见,我跳过了初始化。将其添加回来。
  • 我只是重新阅读了代码......我明白你在做什么,所以,你开始“一些命令”,然后轮询结果
  • @JaromandaX 你能告诉我你对比赛条件的看法吗?

标签: javascript node.js express race-condition


【解决方案1】:

你没有任何我称之为“竞争条件”的东西;这里有一个不确定因素在起作用,但在实践中它可能并不重要。

看起来您的post 启动了一个进程并返回了ID,您的put 取消了该进程,而您的get 返回了该进程的当前状态。由此我推测,在您的 post 完成并提供 ID 之前,您将永远无法 get

如果您在您的 exec 异步侦听器完成之前进行了接收并返回的 get 调用,您将获得最后一个正在进行的状态 - 我认为这是设计使然。因此,这里唯一可能的冲突是,如果您拨打了 put 电话来停止您的进程。

在与结果对象交互时,putget 都是同步的,因此先接收到的就是先完成的。出于我们的目的,您可以忽略进程取消请求,因为它不会修改结果对象。无法保证它们会按照客户发送它们的顺序被接收,这在您的场景中可能是也可能不是实际问题。

相信(虽然我的记忆可能有问题),如果您使用cluster 处理不同进程中的请求,您将无法通过共享来传递数据无论如何,对象,因此已经排除了这种可能性增加的任何复杂性。

因此,网络性能和可靠性的差异是您唯一真正的通配符。服务器将按照请求进入的顺序处理请求,并为您提供预期的结果。如果您只有一个客户端,则可以等到从上一个请求中获得响应后再发送下一个请求,这可能会降低您的性能,令人无法接受,但或多或​​少会使其防弹。否则,只需发送您的请求,不必担心,只需让您的应用足够强大,以识别和处理第二次取消请求,即使您已经取消了流程。

【讨论】:

  • 谢谢杰森,这很有道理。就像你说的那样,POST 和 GET 之间没有竞争条件,无论对象的当前状态是 GET 将返回的。现在问题同时出现在 POST 和 PUT 上,如果 POST “几乎”完成(刚刚进入child.on('close'))并且 PUT 同时出现,我可能最终会取消该过程,尽管它已经合法地完成了执行。承诺会更好地避免这种情况吗?
  • 如果不了解您的要求,我无法回答这个问题 - 取消流程是否存在“无效”时间?如果是这样,则需要在结果对象中跟踪该条件,并且您需要检查它并选择“失败”以取消 - 您可以通过在 child.on('close') 触发时立即更改 result.status = 'finishing' 来完成此操作,这将导致它失败您的result.status == 'running' 签入您的看跌期权。如果没有取消进程的时间无效,那么您必须接受即使合法完成也可以取消。
  • 哦,但是,仍然没有“竞争条件”,因为您的 on('close') 事件和您的 put 调用仍在同一进程中执行 - 如果 close 调用首先发生,那么它会首先完成,如果put 调用首先发生,它将首先完成。所以,这使我的第一条评论无关紧要。
  • 但是on('close')不是发生在异步回调中吗?因此,如果我理解正确,如果 on('close') 先出现,它会在执行 PUT 之前先完成吗?但要注意的另一件事是 PUT 中的所有内容都是同步的
  • 这是大家误解的javascript异步部分。 on('close') 是一个异步事件监听器。但是当它触发时,它会在你的一个主线程中运行回调。就像您的 put() 是一个异步事件侦听器一样,但是当它触发时,它会在您的一个主线程中运行回调。因此,无论哪个事件首先触发,都是最先完成的事件——它们永远不会并行运行(再次假设您的代码不会按原样使用cluster,所以您不必担心任何类型的多处理)。
【解决方案2】:

这只是一个想法,但 Promise 在这里可能会有所帮助:

var obj = {};
const exec = require('child_process').exec;
app.post('/foo', (req, res) => {
     var result = {};
     result.id = generateSomeRandomId();
     result.status = 'running';
     const child = exec('some command');
     child.stdout.on('data',  (data) => {
         //some logic
     });

     result.promise = new Promise(resolve => {
       child.stderr.on('data',  (data) => {
           result.failed = result.failed + 1;
           resolve(false);
        });
       child.on('close',  (code, signal) => {
            // ...
           resolve(true);
       });
     });

     result.pid = child.pid;
     obj[result.id] = result;
     res.send(result); 
}   

app.get('/foo/:id', (req, res) => {
     var result =  obj[req.params.id];
     if(result.status === 'running') {
       result.promise.then(() => res.send(result));
     }
     else {
       res.send(result);
     }
}

在这种情况下,只有当 child 由错误或“关闭”事件完成时,GET 才会响应。

【讨论】:

  • +1,谢谢。使用 Promise 是一个有趣的想法。我的要求是,即使子进程在运行,也需要返回当前结果,所以不想等到子进程完成。我还有一个 PUT 也可以修改结果。
  • 同样在这个解决方案中,如果孩子没有完成,会发生什么?客户等待?
  • @Manali 是的,它会等待。
  • 您认为原始代码中存在竞争条件?你能解释一下那部分吗?
  • @Manali 是的,我认为可能存在某种竞争条件。例如,您一一发送 2 个请求,PUT 和 GET。并且您期望 PUT 将终止该进程,而 GET 将使您获得“已取消”状态。但是在通过网络发送这些请求的过程中,包的顺序可能会改变,并且 GET 可能会在 PUT 之前出现(而且,PUT 请求可能会完全丢失)。你会得到不相关的图片。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-04-14
  • 1970-01-01
  • 1970-01-01
  • 2013-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多