-
Nodeでストリームをpipeするときにリソースリークを起こさないために
- 2019年11月19日
- Node.js
Nodeでファイルやネットワークを扱うときにはストリームを用いることが多いと思います。例えばネットワーク越しにデータを取得してファイルに保存する、といった処理ではpipe
を使って入力側と出力側のストリームをつなげるというのをよくやります。
このpipe
にはあまり知られていない落とし穴があります。Nodeのリファレンスのstream.Readable
の項には以下の記述があります。
One important caveat is that if the
https://nodejs.org/dist/latest-v12.x/docs/api/stream.html#stream_readable_pipe_destination_optionsReadable
stream emits an error during processing, theWritable
destination is not closed automatically. If an error occurs, it will be necessary to manually close each stream in order to prevent memory leaks.
つまり、入力側のストリームで何かエラーが発生した場合、出力側のストリームは自動的には閉じられないので明示的に閉じる必要があるということです。
これを忘れるとどうなるかというと、例えば出力側がファイルだとするとエラーのたびに開きっぱなしのファイルが増えていき、Linuxやmacではファイルディスクリプタの上限に引っかかってプロセスが死にます。先日実際に死にました。Windowsでどうなるかは確認していませんがあまり好ましいことにはならないでしょう。これがバッチのように実行の都度プロセスが起動→終了する場合であれば大きな問題にはなりにくいですが、プロセスがサーバーとして動き続ける場合は危険です。
例を見てみましょう。S3にあるファイルをローカルに保存するためにpipe
で2つのストリームをつなげる場合です。
const s3 = new AWS.S3();
const writeStream = fs.createWriteStream('foo.txt');
const readStream = s3.getObject({Bucket: 'BUCKET_NAME', Key: 'foo.txt'}).createReadStream();
readStream.pipe(writeStream);
readStream.on('error', (err) => {
// Writable is not closed automatically when Readable emits 'error'
// so we have to close Writable manually as below:
writeStream.destroy(err); // this will emit 'error' and 'close'
// or
writeStream.destroy(); // this will emit 'close'
// or
writeStream.end(); // this will emit 'finish'
console.error(err);
});
readStream.on('end', () => {
// Writable automatically close when Readable ends by default
// unless specify pipe option like `readStream.pipe(writeStream, {end:false});`
console.log('ok');
});
入力側でerror
イベントが発生した場合、リファレンスに従い出力側をdestroy
なりend
なりで閉じてあげる必要があるようです。コメントに書いたように閉じ方によって発生するイベントが変化するので、そのあたりは使い分けかなと思います。エラーなくend
イベントが発生した場合は自動的に閉じられるので何もする必要はありません。ただし、pipe
の第2引数に{end:false}
を指定した場合は自動的には閉じられなくなります。
Web上で調べてもこの件に触れられている情報源は数少なかったので、意外と知られていないのかなと考えて今回軽く整理してみました。
CやC++でfree
やdelete
を忘れると厄介なことになりますが、比較的新しめの言語ではメモリやリソースの解放に神経を使うことも少なくなりました。それでもときどきこういうことがあるので、リファレンスはきちんと目を通しましょうというお話でした。
この記事を書いた人 : 佐藤寛之
AWS bluebird css CSV docker docker compose electron ES6 es2015 Git Heroku ITコンサルティング JavaScript justinmind less mongoDB Node.js php PostgreSQL Private Space Promise React react-router reactjs Salesforce scss Selenium Builder selenium IDE Selenium WebDriver stylus TypeScript VirtualBox VisualStudioCode vue vuejs webpack システム開発プロジェクト セキュリティ ワイヤーフレーム 上流工程 卒FIT 帳票 要件定義 設計 電力小売業界