You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
SetActiveProfile racing DeleteProfile leaves the active id pointing at a deleted profile, so a later profile with that id becomes active on its own #120
DeleteProfile (lines 60-66) removes the profile, then clears _activeProfileId only if it already equals the deleted id.
The two methods can interleave like this:
SetActiveProfile("x") passes the ContainsKey check.
DeleteProfile("x") removes the profile. _activeProfileId is not "x" yet, so it is left alone.
SetActiveProfile writes _activeProfileId = "x" and returns true.
The active id now names a profile that no longer exists. GetActiveProfile() returns null for now. Later, when a profile with the id "x" is created again, it becomes active even though nothing activated it.
Why it matters
The class is written to be thread-safe: it uses ConcurrentDictionary, has a _lock, and the CreateProfile race was fixed in #112. This is the one check-then-act path left. When it hits, the app runs with a stale active profile, and a recreated profile becomes active without anyone choosing it.
Repro (MSTest, reproduced)
intbad=0;for(inti=0;i<20000;i++){ProfileManagerpm=new();pm.CreateProfile("x","X");usingBarrierb=new(2);Taskt1=Task.Run(()=>{b.SignalAndWait();pm.SetActiveProfile("x");});Taskt2=Task.Run(()=>{b.SignalAndWait();pm.DeleteProfile("x");});Task.WaitAll(t1,t2);pm.CreateProfile("x","Recreated");if(pm.GetActiveProfile()is not null)bad++;}// Observed: bad == 27 of 20000. Expected: 0.
Suggested fix
Take _lock around the check-and-set in SetActiveProfile and around the remove-and-clear in DeleteProfile, so each pair runs as one step.
A defensive alternative is for GetActiveProfile/CreateProfile to discard an _activeProfileId that points at a missing profile. The lock is the simpler fix.
Acceptance: the stress test above reports 0, and it is added as a regression test.
Priority: Medium. The race is rare (27 of 20,000 in the stress test), but when it hits, the active id points at a deleted profile, and a profile later recreated with that id becomes active without anyone choosing it. It breaks the class's thread-safety guarantee.
Area / suggested owner:ProfileManager.SetActiveProfile and DeleteProfile (maintainer: @matt-edmondson)
Activity
matt-edmondson commentedon Sep 26, 2026
Triage
ProfileManager.SetActiveProfileandDeleteProfile(maintainer: @matt-edmondson)CreateProfilerace fixed in Concurrent CreateProfile calls for the same id can return a Profile that is never stored, so its bindings are silently lost #112, and as the open Concurrent BindChord calls corrupt a profile's bindings, despite the "thread-safe services" guarantee #109 (concurrentBindChordcorrupts bindings). A single locking pass overProfileManagercould fix both.Generated by Claude Code