问题描述
我需要在生成后将 v8 堆转储上传到 AWS S3 存储桶中,但是上传的文件是 0KB 或 256KB。服务器上的文件大小超过 70MB,因此请求似乎不会等到堆转储没有完全刷新到磁盘。我猜想通过管道传输到 fs.createWriteStream
的可读流是以异步方式发生的,而调用该函数的 await
实际上并不是在等待。我使用的是 v3 版本的 AWS NodeJS SDK。我做错了什么?
async function createHeapSnapshot (fileName) {
const snapshotStream = v8.getHeapSnapshot();
// It's important that the filename end with `.heapsnapshot`,// otherwise Chrome DevTools won't open it.
const fileStream = fs.createWriteStream(fileName);
snapshotStream.pipe(fileStream);
}
async function pushHeapSnapshottoS3(fileName)
{
const heapDump = fs.createReadStream(fileName);
const s3Client = new S3Client();
const putCommand = new PutObjectCommand(
{
Bucket: "my-bucket",Key: `heapdumps/${fileName}`,Body: heapDump
}
)
return s3Client.send(putCommand);
}
app.get('/heapdump',asyncMiddleware(async (req,res) => {
const currentDateTime = Date.Now();
const fileName = `${currentDateTime}.heapsnapshot`;
await createHeapSnapshot(fileName);
await pushHeapSnapshottoS3(fileName);
res.send({
heapdumpFileName: `${currentDateTime}.heapsnapshot`
});
}));
解决方法
你的猜测是正确的。 createHeapSnapshot()
返回一个承诺,但该承诺与流完成时没有任何联系。因此,当调用者对该承诺使用 await
时,承诺在流实际完成之前很久就被解决了。 async
函数中没有任何魔法可以知道何时完成了像 .pipe()
这样的非承诺异步操作。因此,您的 async
函数返回一个与流函数完全没有联系的承诺。
由于流对 promise 没有太多的原生支持,您可以手动承诺流的完成和错误:
function createHeapSnapshot (fileName) {
return new Promise((resolve,reject) => {
const snapshotStream = v8.getHeapSnapshot();
// It's important that the filename end with `.heapsnapshot`,// otherwise Chrome DevTools won't open it.
const fileStream = fs.createWriteStream(fileName);
fileStream.on('error',reject).on('finish',resolve);
snapshotStream.on('error',reject);
snapshotStream.pipe(fileStream);
});
}
或者,您可以使用较新的 pipeline()
function,它支持承诺(nodejs v15 中添加了内置承诺支持)并替换了 .pipe()
并具有内置错误监控来拒绝承诺:
const { pipeline } = require('stream/promises');
function createHeapSnapshot (fileName) {
const snapshotStream = v8.getHeapSnapshot();
// It's important that the filename end with `.heapsnapshot`,// otherwise Chrome DevTools won't open it.
return pipeline(snapshotStream,fs.createWriteStream(fileName))
}