エラーメッセージ
Windows上でNode.jsアプリを開発していると、EBUSYエラーという壁にぶつかることがよくあります。これは通常、ファイルの削除、名前の変更、移動を試みた際に発生し、なぜ単純な操作が突然拒否されるのか不思議に思うことでしょう。
Error: EBUSY: resource busy or locked, unlink 'C:\project\data\temp.txt'
発生原因
技術的に言えば、EBUSYはNode.jsのバグではなく、Windowsオペレーティングシステムからの直接的なメッセージです。ファイルが開いている間でも削除できることが多いLinuxやmacOSとは異なり、Windowsは厳格なファイルロックを強制します。プロセスがそのファイルに対して開かれた「ハンドル」を保持している場合、OSは変更要求を拒否します。
主な原因は以下の通りです:
- アクティブなストリーム:
.end()で適切に閉じられていないファイルストリーム(readまたはwrite)。 - エディタのインデックス作成: VS CodeやIntelliJがバックグラウンドでプロジェクトファイルをインデックスしている。
- ウイルス対策ソフトのスキャン: Windows Defenderやサードパーティ製ツールが、ファイル作成の瞬間にスキャンを実行している。
- システムサービス: Windows Search Indexerやエクスプローラーのプレビューペインがファイルを開いたままにしている。
- レースコンディション: 前回の書き込み操作がまだディスクにフラッシュされている最中にファイルを削除しようとしている。
ステップバイステップの解決策
1. ストリームを明示的に閉じる
ストリームの閉じ忘れは、EBUSYの最も一般的な原因です。データの書き込みが終わっていても、Node.jsはガベージコレクションが実行されるまでファイルハンドルを保持し続けることがあります。これには数秒かかる場合があります。安全を期すために、ファイルを削除する前に必ずcloseイベントを待つようにしてください。
const fs = require('fs');
const stream = fs.createWriteStream('example.txt');
stream.write('処理用のデータ');
// 書き込みの終了を通知
stream.end();
// OSがロックを解除したことを100%確信してから削除する
stream.on('close', async () => {
try {
await fs.promises.unlink('example.txt');
console.log('ファイルの削除に成功しました');
} catch (err) {
console.error('EBUSYが解消されません:', err);
}
});
2. スマートなリトライループを実装する
ウイルス対策スキャンなどの外部ロックは通常一時的なもので、50ミリ秒未満で終わることが多いです。シンプルなリトライメカニズムを導入することで、これらの一時的な「瞬き」を回避し、アプリをクラッシュさせずにスムーズに実行し続けることができます。
const fs = require('fs').promises;
async function safeUnlink(filePath, retries = 5, delay = 100) {
for (let i = 0; i setTimeout(res, delay));
continue;
}
throw err;
}
}
}
3. graceful-fsを使用する
アプリケーションで大量のファイルI/Oを行う場合は、graceful-fsの使用を検討してください。これはnpmやjestでも採用されている、ネイティブのfsモジュールの実績あるドロップイン代替品です。EBUSYやEMFILEといった一時的なOSエラーによる失敗を自動的にリトライします。
// インストール: npm install graceful-fs
const fs = require('graceful-fs');
fs.unlink('path/to/file', (err) => {
if (err) throw err;
console.log('正常に削除されました');
});
4. ロックしているプロセスを特定する
コードに問題がないのにエラーが続く場合は、間違いなく他のプログラムがロックを保持しています。Windowsのリソースモニターを使用して原因を見つけることができます:
Win + Rを押し、resmonと入力してEnterを押します。- 「CPU」タブに移動します。
- 「関連付けられたハンドル」の検索ボックスに、ファイル名(例:
temp.txt)を入力します。 - リストには、どのプロセス(Defenderの
MsMpEng.exeやEverything.exeなど)が所有者であるかが正確に表示されます。
検証と予防
修正が確実であることを確認するために、「100回ストレステスト」を実行してください。ファイル操作をループで囲み、100回連続で高速に実行します。レースコンディションは単発の手動実行では隠れがちですが、負荷がかかると即座に表面化します。
今後これらの問題を回避するために:
- 同期メソッドを避ける:
fs.unlinkSyncはイベントループをブロックし、fs.promisesよりもロックの競合が発生しやすくなります。 - 一時フォルダを除外する: 数千の小さなログを生成する場合は、専用のフォルダに配置し、ウイルス対策ソフトのリアルタイム保護からそのパスを除外してください。
- 最初にクリーンアップ: 削除(unlink)する前に、必ずストリームの
.destroy()または.close()を呼び出してください。

