
Playwrightで同じChromeプロファイルを二重に開くとクラッシュする|PIDを書いたロックファイルで止めた
こんにちは、すきまログです。
note への下書き作成や公開を、Playwright で自動化しています。ログイン状態を保つために、launchPersistentContext でアカウントごとの Chrome プロファイル(user-data フォルダ)を使い回しています。
ある日、AIのエージェントを複数同時に動かして作業していたら、同じアカウントのプロファイルを2つの処理が同時に開いて、Chrome がクラッシュしました。メモリも逼迫して、ほかの処理まで巻き込まれました。
それ以来、同じプロファイルを二重に開こうとしたら、2つ目を起動前に止めるロックを入れています。
何が起きたか
Playwright の launchPersistentContext は、指定したフォルダを Chrome のプロファイルとして使います。
const ctx = await chromium.launchPersistentContext(USER_DATA, {
headless: false,
viewport: { width: 1280, height: 900 },
});1つの処理だけが使っているぶんには、ログイン状態がそのまま残っていて便利です。ところが、同じフォルダを2つのプロセスが同時に開くと、プロファイルの奪い合いになります。
その日は、作業を急ぐために、同じアカウントの作業を2つのエージェントに分けて動かしていました。最初のエージェントの完了報告があいまいだったので、続きの作業を別のエージェントに頼んだところ、最初のエージェントがまだ動いていたのです。結果として、2つが同じプロファイルを同時に触り、Chrome が落ちました。
ロックファイルで止める
対策はシンプルです。プロファイルを開く前に、「今このプロファイルを使っているプロセスの番号(PID)」をファイルに書いておく。次に開こうとした処理は、そのファイルを見て、書かれたプロセスがまだ生きていたら起動をやめます(実際のコードから。メッセージは一部省いています)。
function acquireLock(userDataDir) {
const lockPath = userDataDir + '.lock';
if (fs.existsSync(lockPath)) {
const pid = parseInt(fs.readFileSync(lockPath, 'utf8').trim(), 10);
let alive = false;
if (pid) {
try { process.kill(pid, 0); alive = true; } catch (_) { alive = false; }
}
if (alive) {
console.error('[ロック] 同じプロファイルを使う別のプロセス(PID ' + pid + ')が実行中です。');
process.exit(1);
}
// 古いロック(プロセスが既に終了済み)は放置されたものとして上書きする
}
fs.writeFileSync(lockPath, String(process.pid));
// ...(終了時に自分のロックだけ消す処理)
}ポイントは process.kill(pid, 0) です。シグナル番号 0 を送ると、プロセスを止めずに「存在するかどうか」だけを確かめられます。存在しなければ例外になるので、それで生死を判定しています。
古いロックが残っても詰まらないようにする
ロックファイルの方式でいちばん困るのは、処理が途中で落ちて、ロックファイルだけが残ることです。そうなると、誰も使っていないのに、ずっと「使用中」と言われて起動できなくなります。
そこで、ロックファイルがあっても、書かれた PID のプロセスがもう存在しなければ、古いロックとみなして上書きするようにしました。ロックに「誰が持っているか」を書いておくのは、このためです。
終了するときは、自分が書いたロックだけを消します。
const release = () => {
try {
if (fs.existsSync(lockPath) && fs.readFileSync(lockPath, 'utf8').trim() === String(process.pid)) {
fs.unlinkSync(lockPath);
}
} catch (_) { /* ignore */ }
};
process.on('exit', release);
process.on('SIGINT', () => { release(); process.exit(1); });中身が自分の PID かどうかを確かめてから消すので、ほかの処理が書き直したロックを間違って消すことはありません。
残ったブラウザも先に片付ける
ロックとは別に、前回の実行で残った Chrome のプロセスが、プロファイルを握ったままになっていることもありました。この場合、ロックは古いので通るのに、Chrome の起動で失敗したり固まったりします。
そこで、起動の前に、そのプロファイルのフォルダをコマンドラインに含む Chrome のプロセスだけを終了させています(Windows なので PowerShell の Get-CimInstance Win32_Process で探しています)。ほかのプロファイルの Chrome には触らないように、フォルダのパスで絞り込むのが大事です。
「別のアカウント同士なら大丈夫」でもなかった
ロックはプロファイルごとなので、別のアカウントのプロファイルなら同時に開けます。ただ、実際に複数アカウントの処理を同時に走らせたら、メモリ不足で落ちたことがありました。Chrome を何個も同時に立ち上げると、それだけでかなりのメモリを使います。
なので、運用のルールとしては、ブラウザを開く処理はアカウントが違っても1つずつ順番に走らせることにしています。ロックは「同じプロファイルの二重起動」を機械で止めるもので、それ以外の同時実行はルールで避ける、という分担です。
学んだこと
🔴 launchPersistentContext で同じプロファイルを2つのプロセスが開くと、クラッシュする。 起動の前に機械で止める
ロックファイルには PID を書く。 書いたプロセスが死んでいれば古いロックとして上書きできるので、落ちたあとに詰まらない
終了時は、自分のロックだけを消す。 中身の PID を確かめてから消す
ロックで止められない同時実行(別アカウント同士のメモリ不足)は、順番に走らせるルールで避ける
同じ「ブラウザ自動化の詰まりどころ」の話
日々の自動化の詰まりどころは X でも書いています → @Sukima___log