下面有比赛条件吗?

Is there any race condition below?

我的快递服务器中有以下代码(为简洁起见,已将其删减)。我有一个共同的对象,我在三个不同的 restful 端点 adding/modifying/reading。由于 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);
    }

这只是一个想法,但 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 由错误或 'close' 事件完成时,GET 才会响应。

你没有任何我会称之为"race condition"的东西;这里有不确定因素在起作用,但在实践中它不太可能重要。

看起来你的 post 启动了一个进程和 returns ID,你的 put 取消了进程,你的 get returns 当前进程的状态。由此我了解到,在您的 post 完成并提供 ID 之前,您永远无法 get

如果您在 exec 异步侦听器完成之前进行接收和返回的 get 调用,您将获得最后一个正在进行的状态 - 我假设这是设计使然.所以这里唯一可能发生的冲突是,如果您进行了 put 调用来停止您的进程。

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

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

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