何が起きるか
同じ Windows ユーザーが、ユーザーの切り替えやリモートデスクトップで 2 つの対話セッションを持ち、同じインストール先の App を両方で動かした場合、プロセスは 2 つ走る一方、触る資源は共有される。
多重起動の抑止 (App.SingleInstanceMutexName) は接頭辞の無い名前なので、セッションごとの名前空間に作られる。両セッションで別々に起動できる
設定の保存 (SettingsStore.CrossProcessMutexName) も同じく接頭辞が無い。互いの保存を直列化できない
更新の取得 (%AppData%\VRCToolsDataSync\update\<インストール先のハッシュ>\incoming.zip) は同じファイルを指す。取得の直列化 (UpdateManager._downloadGate) はプロセス内にしか効かない → PR fix(update): 取得をクロスプロセスで直列化し、入っている版と突き合わせる #74 で対応
更新機構の側は対応済み (PR #74 )
案 1〜3 のどれとも少し違い、更新機構の中だけで閉じる 2 つを入れた。多重起動の抑止と設定の保存には触っていないので、下の「残っていること」はそのまま残る。
取得の直列化を Global\ にした (UpdateStage.CreateDownloadMutex)。書き始めから昇格または破棄までと、後始末を押さえる。適用のロックとは別に置き、取る順は「取得 → 適用」で揃えた。取れないときは見送らずに待つ (要求は _pending から取り出した後なので、落とすと次の確認まで誰も取りに行かない)
適用の直前に、インストール先に入っている版と突き合わせるようにした (UpdateInstaller.InstalledAppVersion)。TryLoadVerified の版の前後の判定は実行中のプロセスの版だけを見ており、適用のロックを待っていた旧版の App が素通りして 2 度目の置き換えへ進めた。これが .old を失う筋だった
これで、本文にある「完成済みのファイルを消す」と、8/30 のコメントにある「.old が上書きされて本当の旧版が失われる」が両方閉じた。
残っていること
1. 多重起動の抑止と設定の保存の名前空間
ここがこの issue の本体である。名前空間をどう揃えるかは機能をまたぐ判断になる。
抑止を Global\ にすると、2 つ目のセッションでは「既に起動しています」と言われて何も出てこない (先行インスタンスは別セッションにあり、ウィンドウを見せられない)。利用者から見ると壊れている
セッションごとに動かせるままにするなら、設定ファイルの側をセッション間で直列化するか、セッションごとに分ける必要がある
案。
共有資源に触る所を Global\ の名前で直列化する
設定の置き場所をセッションごとに分ける。ディスクは食うが、互いに干渉しない
2 つ目のセッションでは一部の機能を降りる
2. 置き換えの最中に、別セッションの App が旧 app\ を掴む
8/30 のコメントに書いた 2 つの影響のうち、後半 (.old の上書き) は PR #74 で閉じた。前半は残っている。
別セッションの App は接頭辞の無い抑止を素通りして起動し、旧 app\ の DLL を読み込む。掴まれたままだと、ヘルパの入れ替え (リネーム) が失敗する。
ただし UpdateInstaller が巻き戻し、取得は残るので、次の機会に適用し直される。壊れる方向の失敗ではない。抑止を Global\ にすれば消えるので、1 とセットで決まる。
3. UpdateStage.Discard() が incoming.zip も消す
Discard() は適用のロックの下からしか呼ばれず、取得のロックは取らない。別セッションが取得している最中に「再起動して適用」を押されると、書きかけを消しうる。
塞ぐには「どのファイルをどちらのロックが持つか」を組み替えることになり、#48 の中断からの復旧の判定 (横に残った記録を ZIP と突き合わせて仕上げる経路) に触れる。失うのは取得のやり直し 1 回で、.old や設定を失う類ではないため、PR #74 では触らずに記録だけ残した。
前提
何が起きるか
同じ Windows ユーザーが、ユーザーの切り替えやリモートデスクトップで 2 つの対話セッションを持ち、同じインストール先の App を両方で動かした場合、プロセスは 2 つ走る一方、触る資源は共有される。
App.SingleInstanceMutexName) は接頭辞の無い名前なので、セッションごとの名前空間に作られる。両セッションで別々に起動できるSettingsStore.CrossProcessMutexName) も同じく接頭辞が無い。互いの保存を直列化できない更新の取得 (→ PR fix(update): 取得をクロスプロセスで直列化し、入っている版と突き合わせる #74 で対応%AppData%\VRCToolsDataSync\update\<インストール先のハッシュ>\incoming.zip) は同じファイルを指す。取得の直列化 (UpdateManager._downloadGate) はプロセス内にしか効かない更新機構の側は対応済み (PR #74)
案 1〜3 のどれとも少し違い、更新機構の中だけで閉じる 2 つを入れた。多重起動の抑止と設定の保存には触っていないので、下の「残っていること」はそのまま残る。
Global\にした (UpdateStage.CreateDownloadMutex)。書き始めから昇格または破棄までと、後始末を押さえる。適用のロックとは別に置き、取る順は「取得 → 適用」で揃えた。取れないときは見送らずに待つ (要求は_pendingから取り出した後なので、落とすと次の確認まで誰も取りに行かない)UpdateInstaller.InstalledAppVersion)。TryLoadVerifiedの版の前後の判定は実行中のプロセスの版だけを見ており、適用のロックを待っていた旧版の App が素通りして 2 度目の置き換えへ進めた。これが.oldを失う筋だったこれで、本文にある「完成済みのファイルを消す」と、8/30 のコメントにある「
.oldが上書きされて本当の旧版が失われる」が両方閉じた。残っていること
1. 多重起動の抑止と設定の保存の名前空間
ここがこの issue の本体である。名前空間をどう揃えるかは機能をまたぐ判断になる。
Global\にすると、2 つ目のセッションでは「既に起動しています」と言われて何も出てこない (先行インスタンスは別セッションにあり、ウィンドウを見せられない)。利用者から見ると壊れている案。
Global\の名前で直列化する2. 置き換えの最中に、別セッションの App が旧
app\を掴む8/30 のコメントに書いた 2 つの影響のうち、後半 (
.oldの上書き) は PR #74 で閉じた。前半は残っている。別セッションの App は接頭辞の無い抑止を素通りして起動し、旧
app\の DLL を読み込む。掴まれたままだと、ヘルパの入れ替え (リネーム) が失敗する。ただし
UpdateInstallerが巻き戻し、取得は残るので、次の機会に適用し直される。壊れる方向の失敗ではない。抑止をGlobal\にすれば消えるので、1 とセットで決まる。3.
UpdateStage.Discard()がincoming.zipも消すDiscard()は適用のロックの下からしか呼ばれず、取得のロックは取らない。別セッションが取得している最中に「再起動して適用」を押されると、書きかけを消しうる。塞ぐには「どのファイルをどちらのロックが持つか」を組み替えることになり、#48 の中断からの復旧の判定 (横に残った記録を ZIP と突き合わせて仕上げる経路) に触れる。失うのは取得のやり直し 1 回で、
.oldや設定を失う類ではないため、PR #74 では触らずに記録だけ残した。前提
Global\になったGlobal\になった