majorinfra-failureverifiedfirsthand
iCloudの退避ファイルでrsyncがmmapデッドロックし断続停止
iCloud上のObsidianのvaultを毎時gitミラーへ写す定期ジョブが、ときどき `Resource deadlock avoided` でrsyncごと落ちていた。落ちる回と通る回があり、再現しづらかった。
原因
iCloudが容量最適化で中身を退避した「dataless」ファイルを、rsyncがmmapで読もうとしてEDEADLKで止まっていた。退避ファイルは .icloud 拡張子を持たないため、スクリプトの除外パターンもすり抜けていた。
結果
退避ファイルに当たった回だけミラーが失敗する断続障害になった。最初の修正ではmacOS標準のrsyncをGNU rsyncに替えたが、GNU rsyncも読み取りにmmapを使うため、3日後に同じデッドロックが再発した。
対策
rsyncの前に find -flags +dataless で退避ファイルを探し、brctl download と head -c 1 で中身を実体化させてから同期する。
何が起きたか
iCloudに置いたObsidianのvaultを、launchdで毎時rsyncしてgitミラーに写し、 privateリポジトリへpushする。その定期ジョブから「rsync失敗(Code20)」の通知が来た。 ログの中身はこうだった。
error: <某>.md: mmap: Resource deadlock avoided
現場の混乱
macOSの /usr/bin/rsync は、名前こそrsyncだが中身はopenrsyncという別実装で、
「rsync 2.6.9 compatible」を名乗っている。これがiCloudの「dataless」ファイル、
つまり容量最適化で中身をクラウドへ退避し、サイズは0より大きいのに
ディスク上のブロックが0のファイルをmmapで読もうとして、EDEADLKで異常終了していた。
退避ファイルは .icloud 拡張子を付けないので、スクリプトの *.icloud 除外を
素通りする。退避されたファイルが0件の回は成功するため、落ちたり通ったりする。
しかも既存のスクリプトには --inplace が付いていた。これはopenrsyncの別のバグ、
絵文字や長い日本語のファイル名で mkstempat: Illegal byte sequence になる問題を
避けるための回避策だった。openrsyncの二重のバグに挟まれていた。
最初の修正は、AIコーディングエージェントとの作業で出した。Homebrewで
GNU rsync 3.4.4を入れ、スクリプトで明示的に選び、見つからなければ黙って
openrsyncに戻らずエラーで止める。--inplace も外した。作業メモには
「GNU rsyncはread()ベースなのでmmapデッドロックは起きない」と書かれ、
フル同期は2,980ファイルで成功した。
3日後、別のエラーコードで同じ症状が戻ってきた。
read errors mapping ...: Resource deadlock avoided (11)
GNU rsyncも読み取りにmmapを使う。メモの前提は誤りだった。
原因
問題の本体はrsyncの実装ではなく、iCloudの退避ファイルそのものだった。 中身がローカルに無いファイルをmmapで読むと、実装を替えてもデッドロックする。 最初の修正は「openrsyncが悪い」という診断に寄りかかり、GNU rsyncの読み取り方式を 確かめないまま根治と記録していた。
対策
rsyncの前に、退避ファイルを先に片づける処理を入れた。
find "$VAULT_SRC" -type f -flags +datalessで退避ファイルを見つける。 これが退避ファイル検出の最短手だった。- 見つけたものは
brctl downloadでダウンロードを指示し、head -c 1で 実際に読んで実体化を確定させる。
GNU rsyncへの切り替えと --inplace の除去はそのまま残した。退避ファイルを
実体化させる以上、iCloudの容量最適化とはぶつかりうる。そこは留意点として残っている。
出典
- 運営者自身が起こした事故の一次記録です。外部に公開された元記事はありません。