下面有比赛条件吗?
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
调用来停止您的进程。
在与结果对象交互时,您的 put
和 get
都是同步的,因此先接收到的那个将最先完成。出于我们的目的,您可以忽略进程取消请求,因为它不会修改结果对象。不能保证它们会按照客户端发送的相同顺序接收,这在您的场景中可能是也可能不是实际问题。
我相信(虽然我的记忆可能有问题),如果你使用cluster
来处理不同进程中的请求,你将无法无论如何都要通过共享对象传递数据,因此已经排除了这种可能性增加的任何复杂性。
因此,网络性能和可靠性的差异是您唯一真正的通配符。服务器将按照请求的传入顺序处理请求,并为您提供预期的结果。如果您只有一个客户端,则可以等到收到前一个请求的响应后再发送下一个请求,这可能会使您的性能降低到不可接受的程度,但或多或少会使其防弹。否则,只需发送您的请求即可,不用担心,只需让您的应用足够强大以识别和处理第二次取消请求,即使您已经取消了该过程。
我的快递服务器中有以下代码(为简洁起见,已将其删减)。我有一个共同的对象,我在三个不同的 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
调用来停止您的进程。
在与结果对象交互时,您的 put
和 get
都是同步的,因此先接收到的那个将最先完成。出于我们的目的,您可以忽略进程取消请求,因为它不会修改结果对象。不能保证它们会按照客户端发送的相同顺序接收,这在您的场景中可能是也可能不是实际问题。
我相信(虽然我的记忆可能有问题),如果你使用cluster
来处理不同进程中的请求,你将无法无论如何都要通过共享对象传递数据,因此已经排除了这种可能性增加的任何复杂性。
因此,网络性能和可靠性的差异是您唯一真正的通配符。服务器将按照请求的传入顺序处理请求,并为您提供预期的结果。如果您只有一个客户端,则可以等到收到前一个请求的响应后再发送下一个请求,这可能会使您的性能降低到不可接受的程度,但或多或少会使其防弹。否则,只需发送您的请求即可,不用担心,只需让您的应用足够强大以识别和处理第二次取消请求,即使您已经取消了该过程。