← 事故一覧へ

majorinfra-failureverifiedfirsthand

iCloudの退避ファイルでrsyncがmmapデッドロックし断続停止

iCloud上のObsidianのvaultを毎時gitミラーへ写す定期ジョブが、ときどき `Resource deadlock avoided` でrsyncごと落ちていた。落ちる回と通る回があり、再現しづらかった。

発生日
深刻度
5/10
影響範囲
uptime
タグ
#rsync#icloud#macos#obsidian#backup#ai-coding

原因

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の容量最適化とはぶつかりうる。そこは留意点として残っている。

出典

  • 運営者自身が起こした事故の一次記録です。外部に公開された元記事はありません。