Severity: Warning
Files:
src/Servy.Manager/ViewModels/PerformanceViewModel.cs lines 230-234
src/Servy.Manager/ViewModels/MainViewModel.cs lines 969-975
Description:
MainViewModel serializes ProcessHelper.MaintainCache() + GetProcessTreeMetrics() calls under _metricsLock:
// MainViewModel.cs:969-975
lock (_metricsLock)
{
ProcessHelper.MaintainCache();
var metrics = ProcessHelper.GetProcessTreeMetrics(targetPid.Value);
cpu = metrics.CpuUsage;
ram = metrics.RamUsage;
}
PerformanceViewModel calls the same two methods with no lock:
// PerformanceViewModel.cs:230-234
var processMetrics = await Task.Run(() =>
{
ProcessHelper.MaintainCache();
return ProcessHelper.GetProcessTreeMetrics(pid);
});
Both view models run on their own background ticks and can therefore have MaintainCache and GetProcessTreeMetrics interleaved on the shared static ProcessHelper.PrevCpuTimes cache. Even though PrevCpuTimes is a ConcurrentDictionary, the CPU-delta computation (read previous sample → compute delta → write new sample) is a compound read-modify-write. Concurrent threads for the same PID can produce incorrect deltas (double-counted kernel time, or the CPU% glitches already flagged in #796).
Only one of the two patterns is correct — either the lock in MainViewModel is superfluous, or it is necessary and PerformanceViewModel is missing it.
Suggested fix:
Decide the contract at the source:
- If the lock is required (most likely, given the RMW on
PrevCpuTimes): move it into ProcessHelper.GetProcessTreeMetrics itself (one place, no caller has to remember). Remove the caller-side locks from both view models.
- If it is not required: remove the lock from
MainViewModel to stop false-serialization of unrelated PIDs.
Either way the two call sites should match, and ProcessHelper's thread-safety contract should be documented in XML-doc comments on the static methods so future callers don't have to infer it from one of two inconsistent usages.
Severity: Warning
Files:
src/Servy.Manager/ViewModels/PerformanceViewModel.cslines 230-234src/Servy.Manager/ViewModels/MainViewModel.cslines 969-975Description:
MainViewModelserializesProcessHelper.MaintainCache()+GetProcessTreeMetrics()calls under_metricsLock:PerformanceViewModelcalls the same two methods with no lock:Both view models run on their own background ticks and can therefore have
MaintainCacheandGetProcessTreeMetricsinterleaved on the shared staticProcessHelper.PrevCpuTimescache. Even thoughPrevCpuTimesis aConcurrentDictionary, the CPU-delta computation (read previous sample → compute delta → write new sample) is a compound read-modify-write. Concurrent threads for the same PID can produce incorrect deltas (double-counted kernel time, or the CPU% glitches already flagged in #796).Only one of the two patterns is correct — either the lock in
MainViewModelis superfluous, or it is necessary andPerformanceViewModelis missing it.Suggested fix:
Decide the contract at the source:
PrevCpuTimes): move it intoProcessHelper.GetProcessTreeMetricsitself (one place, no caller has to remember). Remove the caller-side locks from both view models.MainViewModelto stop false-serialization of unrelated PIDs.Either way the two call sites should match, and
ProcessHelper's thread-safety contract should be documented in XML-doc comments on the static methods so future callers don't have to infer it from one of two inconsistent usages.