Skip to content

fix(settings): 設定の保存の排他を錠前ファイルに移す - #82

Merged
limit7412 merged 2 commits into
masterfrom
claude/global-mutex-integrity-label
Aug 31, 2026
Merged

fix(settings): 設定の保存の排他を錠前ファイルに移す#82
limit7412 merged 2 commits into
masterfrom
claude/global-mutex-integrity-label

Conversation

@limit7412

@limit7412 limit7412 commented Aug 31, 2026

Copy link
Copy Markdown
Owner

issue #81 への対応。#80 に積んでいたが、#80 がマージされたので base は master である。

直すもの

GlobalMutex.CreateGlobal\ の名前で Mutex を作り、作れなければセッション内だけの名前へ落ちる。落ちた側は誰とも待ち合わせていないのに、待ち合わせているつもりで進む。 設定の保存でこれが起きると、両方が同じ内容を読んでから書き戻し、片方の更新が黙って消える。#52 で直したかったものが、この経路だけ残る。

#81 の前提を見直した

issue には「昇格したプロセスが作った物は mandatory label で開けない」と書いた。これは #80 の時点で半分外れている可能性が高い。

Mutex.OpenExisting が求めるのは SynchronizeModify である。mutant の generic mapping では MUTANT_QUERY_STATE が read、SYNCHRONIZE が execute に写る。どちらも write ではないので、既定の NO_WRITE_UP では下の integrity level からでも通る。断られるのは new Mutex の方で、こちらは WRITE_DAC などを含む全権を求める。#80OpenExisting を足した時点で、label だけが理由の相手とは待ち合わせられているはずである。

それでも残るものの方が大きい。 Global\ の名前を作るには SeCreateGlobalPrivilege が要る。持たない相手は物を作れず、OpenExisting も「物が無い」で返るので、結局セッション内だけの名前へ落ちる。しかも全員が落ちるので、誰も Global\ の物を作らない。対話セッションをまたぐ排他が、黙って丸ごと無くなる。 label の話より広く、しかも静かである。

label に P/Invoke で触る案は、この見立てだと的も外している。採らなかった。

直し方: 名前空間の話を持ち込まない

設定ファイルの隣に置いた錠前ファイル (settings.json.lock) を FileShare.None で開く。

  • ファイルハンドルの共有の指定は計算機の中で一意に効く。 どの対話セッションから開いても同じ 1 つを取り合う。Global\ のような名前空間の分岐がそもそも無い
  • 権限が要らない。 設定ファイルを書ける相手なら、その隣の錠前も開ける
  • 持ったまま落ちても詰まらない。 ハンドルはプロセスの終了で OS が閉じる。ファイルは残るが、中身は使っていない
  • 守る相手ごとに分かれる。 置き場所がそのまま鍵になるので、パスの綴りをそろえる話 (aeb212d) も要らない

取れなかったときは、これまでどおり諦めてプロセス内ロックだけで best-effort で書く。待ち続けると呼び出し元がその間ずっと止まるためである。

旧版と待ち合わせるための旧名の Mutex (33d7494) はそのまま残し、「旧名 → 錠前ファイル」の順で取る。錠前ファイルを取るのはこの版だけで、この版は必ず旧名から取るので、順の食い違いは起きない。

受け入れるもの: 更新の適用と取得のロック

UpdateStage の 2 つは GlobalMutex のままにした。

失うものが違う。 あちらで待ち合わせに失敗しても、失うのは更新のやり直し 1 回である。.old も設定も残る。設定の保存で失うのは利用者が入力した値そのものである。

差し替えの重さも違う。 適用のロックは CrossProcessLock (Mutex のスレッド親和性のために所有スレッドを立てている) と CLI の ApplyLockHolder (返して取り直す) が噛んでいる。錠前ファイルにはスレッド親和性が無いのでどちらも単純になるが、#74 / #75 で詰めたばかりの適用の経路をまとめて書き換えることになる。ここで持ち込む重さではない。

#81 はこの分を残して開けておく。

検証

  • テストを追加
    • 取った錠前は返すまで他から取れないこと
    • 返せば次の相手が取れること
    • 取れないときに待ち続けずに戻ること
    • 前回の錠前ファイルが残っていても取れること
    • 別のファイルの錠前とは互いに待たないこと
  • fix(settings): 設定の保存の排他を対話セッションをまたぐ名前にする #80 で足した並走の保存のテストは、そのまま通る
  • 2 つの対話セッションでの確認は環境が要るため、実機に委ねる

Refs #81, #52


Generated by Claude Code

@limit7412
limit7412 marked this pull request as ready for review August 31, 2026 09:16
Base automatically changed from claude/remaining-issues-priority-m7daxq to master August 31, 2026 09:17
Global\ の名前は作るのに権限が要る。作れない相手や、既にある物を開けない相手は
セッション内だけの名前へ落ちる。落ちた側は誰とも待ち合わせていないのに、
待ち合わせているつもりで進む。設定の保存でこれが起きると、両方が同じ内容を
読んでから書き戻し、片方の更新が黙って消える。

名前空間の話を持ち込まなければ、この分岐そのものが要らない。設定ファイルの隣に
置いた錠前ファイルを共有無しで開く。ファイルハンドルの共有の指定は計算機の中で
一意に効くので、どの対話セッションから開いても同じ 1 つを取り合う。権限も要らない。
持ったまま落ちてもハンドルは OS が閉じるので、次の相手が待たされ続けることもない。

更新の適用と取得のロックは Mutex のままにした。あちらで待ち合わせに失敗しても
失うのは更新のやり直し 1 回で、利用者の入力ではない。差し替えには適用の経路を
まとめて書き換える必要があり、ここで持ち込む重さではない。

Refs #81, #52

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 74937918ca

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/VRCToolsDataSync.Core/Infra/SettingsStore.cs
錠前ファイルへ移った結果、直前の版 (パス別の Global\ の Mutex を取る) とこの版が
別々の対話セッションで同じ settings.json を保存すると、直列化するものが無くなる。
残していた接頭辞の無い Mutex はセッションごとに別物なので、そこでは繋がらない。

守れる範囲が違う 2 つを、どちらも取る。接頭辞の無い名前は対話セッションの中で、
Global\ の名前はセッションをまたいで効く。取る順は
「Legacy → CrossSession → 錠前ファイル」で固定した。旧版が取るのはこの並びの
先頭からの一部でしかないので、順の食い違いは起きない。

いま出ている prerelease は 9701f0a のもので、パス別の Global\ の Mutex は
入っていない。ただしこの repo は master から短い間隔で prerelease を切るため、
入った版が出回る可能性は十分ある。旧版が行き渡ったら両方消してよい。

Refs #81
@limit7412
limit7412 merged commit a8d2b3a into master Aug 31, 2026
1 check passed
@limit7412
limit7412 deleted the claude/global-mutex-integrity-label branch August 31, 2026 09:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants