cleanup after DI graph rewrite
This commit is contained in:
@@ -71,6 +71,11 @@ public class Computer : IComputer
|
||||
private TaskCompletionSource<object> _deferredGroupCompletionSource = CreateCompletedTaskCompletionSource();
|
||||
private List<Task> _deferredGroupTasks = [];
|
||||
|
||||
// Serializes the Open/OpenAsync/Close/Reset lifecycle so an asynchronous open cannot interleave with teardown
|
||||
// (which would let a background hardware probe outlive OpCode.Close()/Mutexes.Close()) and so two concurrent
|
||||
// opens cannot double-initialize. Also serializes every access to _deferredGroupCancellationTokenSource.
|
||||
private readonly object _openLock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="IComputer" /> instance with basic initial <see cref="Settings" />.
|
||||
/// </summary>
|
||||
@@ -480,6 +485,7 @@ public class Computer : IComputer
|
||||
return false;
|
||||
|
||||
bool added = false;
|
||||
IHardware[] initialHardware = null;
|
||||
lock (_lock)
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested && (isEnabled == null || isEnabled()) && !_groups.Contains(group))
|
||||
@@ -492,16 +498,28 @@ public class Computer : IComputer
|
||||
hardwareChanged.HardwareRemoved += HardwareRemovedEvent;
|
||||
}
|
||||
|
||||
TrackGroupHardwareDiscoveryTask(group);
|
||||
// Snapshot the hardware present at subscription time. Capturing it here -- before any background
|
||||
// discovery is started below -- guarantees hardware discovered later is announced exactly once, via the
|
||||
// event we just subscribed: never missed (subscription precedes discovery) and never also replayed from
|
||||
// this snapshot (a duplicate).
|
||||
initialHardware = [.. group.Hardware];
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (added)
|
||||
{
|
||||
// Start background discovery only now that the subscription and snapshot are in place, then track its task
|
||||
// so the run's completion (HardwareDiscoveryTask) waits for it.
|
||||
if (group is IHardwareDiscoveryTask discoveryTask)
|
||||
{
|
||||
discoveryTask.StartHardwareDiscovery();
|
||||
TrackGroupHardwareDiscoveryTask(group);
|
||||
}
|
||||
|
||||
if (HardwareAdded != null)
|
||||
{
|
||||
foreach (IHardware hardware in group.Hardware)
|
||||
foreach (IHardware hardware in initialHardware)
|
||||
HardwareAdded(hardware);
|
||||
}
|
||||
}
|
||||
@@ -563,9 +581,6 @@ public class Computer : IComputer
|
||||
/// </summary>
|
||||
public void Open()
|
||||
{
|
||||
if (_open)
|
||||
return;
|
||||
|
||||
OpenInternal(CancellationToken.None);
|
||||
}
|
||||
|
||||
@@ -588,17 +603,30 @@ public class Computer : IComputer
|
||||
/// <returns>A task that completes once all enabled hardware groups have been discovered.</returns>
|
||||
public Task OpenAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_open)
|
||||
return Task.CompletedTask;
|
||||
lock (_openLock)
|
||||
{
|
||||
if (_open)
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
return Task.Run(() => OpenInternal(cancellationToken), cancellationToken);
|
||||
}
|
||||
|
||||
private void OpenInternal(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_open)
|
||||
return;
|
||||
// Hold _openLock for the whole open so a concurrent Close()/Reset() cannot run mid-initialization and so a
|
||||
// second Open()/OpenAsync() observes _open atomically instead of double-initializing OpCode/Mutexes/groups.
|
||||
lock (_openLock)
|
||||
{
|
||||
if (_open)
|
||||
return;
|
||||
|
||||
OpenCore(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenCore(CancellationToken cancellationToken)
|
||||
{
|
||||
StartDeferredGroupRun();
|
||||
|
||||
try
|
||||
@@ -773,16 +801,9 @@ public class Computer : IComputer
|
||||
|
||||
private void AddDeferredGroup(Func<IGroup> createGroup, Func<bool> isEnabled)
|
||||
{
|
||||
CancellationTokenSource cancellationTokenSource = _deferredGroupCancellationTokenSource;
|
||||
if (cancellationTokenSource == null)
|
||||
{
|
||||
if (isEnabled())
|
||||
Add(createGroup());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CancellationToken cancellationToken = cancellationTokenSource.Token;
|
||||
// StartDeferredGroupRun (run under _openLock before AddGroups) always assigns the token source, so the deferred
|
||||
// path is the only reachable one here.
|
||||
CancellationToken cancellationToken = _deferredGroupCancellationTokenSource.Token;
|
||||
Task task = Task.Run(() =>
|
||||
{
|
||||
IGroup group = null;
|
||||
@@ -808,23 +829,7 @@ public class Computer : IComputer
|
||||
|
||||
private void AddDeferredGroups(Func<bool> isEnabled, params Func<IGroup>[] createGroups)
|
||||
{
|
||||
CancellationTokenSource cancellationTokenSource = _deferredGroupCancellationTokenSource;
|
||||
if (cancellationTokenSource == null)
|
||||
{
|
||||
foreach (Func<IGroup> createGroup in createGroups)
|
||||
{
|
||||
if (!isEnabled())
|
||||
return;
|
||||
|
||||
IGroup group = createGroup();
|
||||
if (group != null)
|
||||
Add(group);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CancellationToken cancellationToken = cancellationTokenSource.Token;
|
||||
CancellationToken cancellationToken = _deferredGroupCancellationTokenSource.Token;
|
||||
Task task = Task.Run(() =>
|
||||
{
|
||||
foreach (Func<IGroup> createGroup in createGroups)
|
||||
@@ -858,19 +863,7 @@ public class Computer : IComputer
|
||||
|
||||
private bool ShouldDeferDetection(string settingName, string environmentVariable)
|
||||
{
|
||||
string environmentValue = Environment.GetEnvironmentVariable(environmentVariable);
|
||||
if (!string.IsNullOrWhiteSpace(environmentValue))
|
||||
return IsTruthy(environmentValue);
|
||||
|
||||
return IsTruthy(_settings.GetValue(settingName, "false"));
|
||||
}
|
||||
|
||||
private static bool IsTruthy(string value)
|
||||
{
|
||||
return value.Equals("1", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("true", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("yes", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("on", StringComparison.OrdinalIgnoreCase);
|
||||
return SettingsParsing.ShouldDefer(_settings, settingName, environmentVariable);
|
||||
}
|
||||
|
||||
private void StartDeferredGroupRun()
|
||||
@@ -890,10 +883,12 @@ public class Computer : IComputer
|
||||
CancellationTokenSource cancellationTokenSource = _deferredGroupCancellationTokenSource;
|
||||
_deferredGroupCancellationTokenSource = null;
|
||||
TaskCompletionSource<object> completionSource;
|
||||
Task[] tasks;
|
||||
|
||||
lock (_deferredGroupLock)
|
||||
{
|
||||
completionSource = _deferredGroupCompletionSource;
|
||||
tasks = _deferredGroupTasks.ToArray();
|
||||
_deferredGroupTasks = [];
|
||||
}
|
||||
|
||||
@@ -904,8 +899,33 @@ public class Computer : IComputer
|
||||
}
|
||||
|
||||
cancellationTokenSource.Cancel();
|
||||
cancellationTokenSource.Dispose();
|
||||
|
||||
// Mark the run cancelled before draining so the Task.WhenAll continuation in CompleteDeferredGroupRunWhenRegistered
|
||||
// cannot win the race and raise HardwareDiscoveryCompleted during teardown.
|
||||
completionSource.TrySetCanceled();
|
||||
|
||||
// Wait for in-flight deferred construction to unwind before disposing the token source or returning to a caller
|
||||
// that is about to tear down native state. A deferred task only checks the token before createGroup() and again
|
||||
// in AddCore, so the group constructor (which probes hardware via OpCode/native APIs) always runs to completion;
|
||||
// draining here keeps it from racing OpCode.Close()/Mutexes.Close() in Close() or a fresh run from Reset()/Open().
|
||||
WaitForDeferredGroupTasks(tasks);
|
||||
|
||||
cancellationTokenSource.Dispose();
|
||||
}
|
||||
|
||||
private static void WaitForDeferredGroupTasks(Task[] tasks)
|
||||
{
|
||||
if (tasks.Length == 0)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Task.WaitAll(tasks);
|
||||
}
|
||||
catch (AggregateException)
|
||||
{
|
||||
// Deferred discovery tasks observe and log their own exceptions; nothing actionable surfaces here.
|
||||
}
|
||||
}
|
||||
|
||||
private static TaskCompletionSource<object> CreateCompletedTaskCompletionSource()
|
||||
@@ -933,20 +953,37 @@ public class Computer : IComputer
|
||||
private void CompleteDeferredGroupRunWhenRegistered()
|
||||
{
|
||||
TaskCompletionSource<object> completionSource;
|
||||
lock (_deferredGroupLock)
|
||||
completionSource = _deferredGroupCompletionSource;
|
||||
|
||||
WaitForRegisteredTasksThenComplete(completionSource, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completes the run once every tracked deferred task has finished. A deferred group that is itself an
|
||||
/// <see cref="IHardwareDiscoveryTask" /> registers its nested task from a background thread, so more tasks can
|
||||
/// appear after the first <c>Task.WhenAll</c>; this re-arms on the larger set (comparing against the count
|
||||
/// already awaited) until no new task has been registered, so completion never fires while discovery is still running.
|
||||
/// </summary>
|
||||
private void WaitForRegisteredTasksThenComplete(TaskCompletionSource<object> completionSource, int alreadyAwaited)
|
||||
{
|
||||
Task[] tasks;
|
||||
lock (_deferredGroupLock)
|
||||
{
|
||||
completionSource = _deferredGroupCompletionSource;
|
||||
// A superseding run (Reset()/Close() started a new one) owns completion now; stop.
|
||||
if (!ReferenceEquals(completionSource, _deferredGroupCompletionSource))
|
||||
return;
|
||||
|
||||
tasks = _deferredGroupTasks.ToArray();
|
||||
}
|
||||
|
||||
if (tasks.Length == 0)
|
||||
if (tasks.Length <= alreadyAwaited)
|
||||
{
|
||||
CompleteDeferredGroupRun(completionSource);
|
||||
return;
|
||||
}
|
||||
|
||||
_ = Task.WhenAll(tasks).ContinueWith(_ => CompleteDeferredGroupRun(completionSource),
|
||||
_ = Task.WhenAll(tasks).ContinueWith(_ => WaitForRegisteredTasksThenComplete(completionSource, tasks.Length),
|
||||
CancellationToken.None,
|
||||
TaskContinuationOptions.ExecuteSynchronously,
|
||||
TaskScheduler.Default);
|
||||
@@ -1045,25 +1082,31 @@ public class Computer : IComputer
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
if (!_open)
|
||||
return;
|
||||
|
||||
CancelDeferredGroupRun();
|
||||
|
||||
lock (_lock)
|
||||
lock (_openLock)
|
||||
{
|
||||
while (_groups.Count > 0)
|
||||
if (!_open)
|
||||
return;
|
||||
|
||||
// Cancel and DRAIN deferred discovery before tearing down native state: a deferred group constructor runs
|
||||
// to completion even after cancellation, so without waiting here a background probe could run concurrently
|
||||
// with OpCode.Close()/Mutexes.Close() and fault in native code.
|
||||
CancelDeferredGroupRun();
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
IGroup group = _groups[_groups.Count - 1];
|
||||
Remove(group);
|
||||
while (_groups.Count > 0)
|
||||
{
|
||||
IGroup group = _groups[_groups.Count - 1];
|
||||
Remove(group);
|
||||
}
|
||||
}
|
||||
|
||||
OpCode.Close();
|
||||
Mutexes.Close();
|
||||
|
||||
_smbios = null;
|
||||
_open = false;
|
||||
}
|
||||
|
||||
OpCode.Close();
|
||||
Mutexes.Close();
|
||||
|
||||
_smbios = null;
|
||||
_open = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1071,13 +1114,16 @@ public class Computer : IComputer
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
if (!_open)
|
||||
return;
|
||||
lock (_openLock)
|
||||
{
|
||||
if (!_open)
|
||||
return;
|
||||
|
||||
StartDeferredGroupRun();
|
||||
RemoveGroups();
|
||||
AddGroups(null, CancellationToken.None);
|
||||
CompleteDeferredGroupRunWhenRegistered();
|
||||
StartDeferredGroupRun();
|
||||
RemoveGroups();
|
||||
AddGroups(null, CancellationToken.None);
|
||||
CompleteDeferredGroupRunWhenRegistered();
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveGroups()
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibreHardwareMonitor.Hardware.Cpu;
|
||||
@@ -40,6 +41,7 @@ public class GenericCpu : Hardware
|
||||
private ulong _lastTimeStampCount;
|
||||
private double _timeStampCounterFrequency;
|
||||
private Task _timeStampCounterFrequencyTask;
|
||||
private CancellationTokenSource _timeStampCounterFrequencyCancellation;
|
||||
|
||||
public GenericCpu(int processorIndex, CpuId[][] cpuId, ISettings settings)
|
||||
: this(processorIndex, cpuId, settings, null)
|
||||
@@ -109,21 +111,23 @@ public class GenericCpu : Hardware
|
||||
// The estimate is only a seed for the first Update() (~1 s out) and is then self-corrected from real
|
||||
// TSC deltas, so the ~25 ms measurement window can run off the startup critical path.
|
||||
startupTrace?.Skip("GenericCpu.EstimateTimeStampCounterFrequency", "Deferred to background.");
|
||||
_timeStampCounterFrequencyCancellation = new CancellationTokenSource();
|
||||
CancellationToken cancellationToken = _timeStampCounterFrequencyCancellation.Token;
|
||||
_timeStampCounterFrequencyTask = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
EstimateAndStoreTimeStampCounterFrequency(affinity, null);
|
||||
EstimateAndStoreTimeStampCounterFrequency(affinity, null, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Deferred TSC frequency estimation failed: {ex}");
|
||||
}
|
||||
});
|
||||
}, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
EstimateAndStoreTimeStampCounterFrequency(affinity, startupTrace);
|
||||
EstimateAndStoreTimeStampCounterFrequency(affinity, startupTrace, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,7 +195,7 @@ public class GenericCpu : Hardware
|
||||
return startupTrace != null ? startupTrace.Measure(phase, action, getDetail) : action();
|
||||
}
|
||||
|
||||
private static void EstimateTimeStampCounterFrequency(out double frequency, out double error)
|
||||
private static void EstimateTimeStampCounterFrequency(CancellationToken cancellationToken, out double frequency, out double error)
|
||||
{
|
||||
// preload the function
|
||||
EstimateTimeStampCounterFrequency(0, out double f, out double e);
|
||||
@@ -202,6 +206,10 @@ public class GenericCpu : Hardware
|
||||
frequency = 0;
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
// Bail between measurement windows if Close() asked us to stop, so teardown is not held up.
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
EstimateTimeStampCounterFrequency(0.025, out f, out e);
|
||||
if (e < error)
|
||||
{
|
||||
@@ -241,8 +249,12 @@ public class GenericCpu : Hardware
|
||||
error = beginError + endError;
|
||||
}
|
||||
|
||||
private void EstimateAndStoreTimeStampCounterFrequency(GroupAffinity affinity, HardwareStartupTrace startupTrace)
|
||||
private void EstimateAndStoreTimeStampCounterFrequency(GroupAffinity affinity, HardwareStartupTrace startupTrace, CancellationToken cancellationToken)
|
||||
{
|
||||
// If Close() cancelled before this ran, skip it so we neither pin a thread nor touch OpCode during teardown.
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
GroupAffinity previousAffinity = ThreadAffinity.Set(affinity);
|
||||
try
|
||||
{
|
||||
@@ -250,7 +262,7 @@ public class GenericCpu : Hardware
|
||||
double error = 0;
|
||||
Measure(startupTrace,
|
||||
"GenericCpu.EstimateTimeStampCounterFrequency",
|
||||
() => EstimateTimeStampCounterFrequency(out frequency, out error));
|
||||
() => EstimateTimeStampCounterFrequency(cancellationToken, out frequency, out error));
|
||||
StoreTimeStampCounterFrequency(frequency, error);
|
||||
}
|
||||
finally
|
||||
@@ -271,19 +283,7 @@ public class GenericCpu : Hardware
|
||||
|
||||
private static bool ShouldDeferTscEstimation(ISettings settings)
|
||||
{
|
||||
string environmentValue = Environment.GetEnvironmentVariable(DeferTscEstimationEnvironmentVariable);
|
||||
if (!string.IsNullOrWhiteSpace(environmentValue))
|
||||
return IsTruthy(environmentValue);
|
||||
|
||||
return IsTruthy(settings.GetValue(DeferTscEstimationSetting, "false"));
|
||||
}
|
||||
|
||||
private static bool IsTruthy(string value)
|
||||
{
|
||||
return value.Equals("1", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("true", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("yes", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("on", StringComparison.OrdinalIgnoreCase);
|
||||
return SettingsParsing.ShouldDefer(settings, DeferTscEstimationSetting, DeferTscEstimationEnvironmentVariable);
|
||||
}
|
||||
|
||||
|
||||
@@ -333,15 +333,22 @@ public class GenericCpu : Hardware
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
// Cancel and fully wait for the deferred TSC estimation before tearing down: it calls OpCode.Rdtsc and pins
|
||||
// thread affinity, so it must finish (or skip on cancellation) before base.Close()/OpCode teardown rather than
|
||||
// being abandoned after a fixed timeout while still touching native state.
|
||||
_timeStampCounterFrequencyCancellation?.Cancel();
|
||||
try
|
||||
{
|
||||
_timeStampCounterFrequencyTask?.Wait(TimeSpan.FromMilliseconds(100));
|
||||
_timeStampCounterFrequencyTask?.Wait();
|
||||
}
|
||||
catch (AggregateException ex)
|
||||
{
|
||||
Debug.WriteLine($"Deferred TSC frequency estimation failed while closing: {ex}");
|
||||
}
|
||||
|
||||
_timeStampCounterFrequencyCancellation?.Dispose();
|
||||
_timeStampCounterFrequencyCancellation = null;
|
||||
|
||||
base.Close();
|
||||
}
|
||||
|
||||
|
||||
@@ -587,19 +587,7 @@ internal sealed class IntelCpu : GenericCpu
|
||||
|
||||
private static bool ShouldDeferInitialUpdate(ISettings settings)
|
||||
{
|
||||
string environmentValue = Environment.GetEnvironmentVariable(DeferInitialUpdateEnvironmentVariable);
|
||||
if (!string.IsNullOrWhiteSpace(environmentValue))
|
||||
return IsTruthy(environmentValue);
|
||||
|
||||
return IsTruthy(settings.GetValue(DeferInitialUpdateSetting, "false"));
|
||||
}
|
||||
|
||||
private static bool IsTruthy(string value)
|
||||
{
|
||||
return value.Equals("1", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("true", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("yes", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("on", StringComparison.OrdinalIgnoreCase);
|
||||
return SettingsParsing.ShouldDefer(settings, DeferInitialUpdateSetting, DeferInitialUpdateEnvironmentVariable);
|
||||
}
|
||||
|
||||
public override string GetReport()
|
||||
|
||||
@@ -97,15 +97,7 @@ internal sealed class HardwareStartupTrace : IDisposable
|
||||
string settingValue = settings.GetValue(EnabledSetting, "false");
|
||||
string environmentValue = Environment.GetEnvironmentVariable(EnabledEnvironmentVariable) ?? "";
|
||||
|
||||
return IsTruthy(settingValue) || IsTruthy(environmentValue);
|
||||
}
|
||||
|
||||
private static bool IsTruthy(string value)
|
||||
{
|
||||
return value.Equals("1", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("true", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("yes", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("on", StringComparison.OrdinalIgnoreCase);
|
||||
return SettingsParsing.IsTruthy(settingValue) || SettingsParsing.IsTruthy(environmentValue);
|
||||
}
|
||||
|
||||
private static string GetLogFileName(ISettings settings)
|
||||
|
||||
@@ -8,5 +8,13 @@ namespace LibreHardwareMonitor.Hardware;
|
||||
|
||||
internal interface IHardwareDiscoveryTask
|
||||
{
|
||||
/// <summary>
|
||||
/// Starts the group's background hardware discovery. <see cref="Computer" /> calls this only after it has
|
||||
/// subscribed to the group's <see cref="IHardwareChanged.HardwareAdded" /> event and snapshotted the
|
||||
/// already-present hardware, so each discovered piece of hardware is announced exactly once: never before the
|
||||
/// subscription exists (which would drop the notification) and never also via the initial snapshot (a duplicate).
|
||||
/// </summary>
|
||||
void StartHardwareDiscovery();
|
||||
|
||||
Task HardwareDiscoveryTask { get; }
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ internal class MemoryGroup : IGroup, IHardwareChanged, IHardwareDiscoveryTask
|
||||
private Task _hardwareDiscoveryTask = Task.CompletedTask;
|
||||
private Exception _lastException;
|
||||
private bool _disposed = false;
|
||||
private readonly ISettings _settings;
|
||||
private bool _hasPendingDimmDetection;
|
||||
private TimeSpan _pendingDimmDetectionDelay;
|
||||
|
||||
public MemoryGroup(ISettings settings)
|
||||
: this(settings, null)
|
||||
@@ -38,6 +41,8 @@ internal class MemoryGroup : IGroup, IHardwareChanged, IHardwareDiscoveryTask
|
||||
|
||||
internal MemoryGroup(ISettings settings, HardwareStartupTrace startupTrace)
|
||||
{
|
||||
_settings = settings;
|
||||
|
||||
Measure(startupTrace, "MemoryGroup.Driver.Configure", () =>
|
||||
{
|
||||
if (DriverManager.Driver is null || !DriverManager.Driver.IsOpen)
|
||||
@@ -58,12 +63,16 @@ internal class MemoryGroup : IGroup, IHardwareChanged, IHardwareDiscoveryTask
|
||||
|
||||
if (ShouldDeferDimmDetection(settings))
|
||||
{
|
||||
// Defer the start to StartHardwareDiscovery so Computer subscribes before any DIMM is announced.
|
||||
startupTrace?.Skip("MemoryGroup.DimmDetection", "Deferred to background.");
|
||||
StartDimmDetectionTask(settings, TimeSpan.Zero);
|
||||
_pendingDimmDetectionDelay = TimeSpan.Zero;
|
||||
_hasPendingDimmDetection = true;
|
||||
}
|
||||
else if (!TryAddDimms(settings, startupTrace))
|
||||
{
|
||||
StartRetryTask(settings);
|
||||
// Synchronous detection found nothing yet; retry in the background, also started from StartHardwareDiscovery.
|
||||
_pendingDimmDetectionDelay = _retryInterval;
|
||||
_hasPendingDimmDetection = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +85,15 @@ internal class MemoryGroup : IGroup, IHardwareChanged, IHardwareDiscoveryTask
|
||||
|
||||
public Task HardwareDiscoveryTask => _hardwareDiscoveryTask;
|
||||
|
||||
public void StartHardwareDiscovery()
|
||||
{
|
||||
if (!_hasPendingDimmDetection)
|
||||
return;
|
||||
|
||||
_hasPendingDimmDetection = false;
|
||||
StartDimmDetectionTask(_settings, _pendingDimmDetectionDelay);
|
||||
}
|
||||
|
||||
public string GetReport()
|
||||
{
|
||||
StringBuilder report = new();
|
||||
@@ -151,11 +169,6 @@ internal class MemoryGroup : IGroup, IHardwareChanged, IHardwareDiscoveryTask
|
||||
return false;
|
||||
}
|
||||
|
||||
private void StartRetryTask(ISettings settings)
|
||||
{
|
||||
StartDimmDetectionTask(settings, _retryInterval);
|
||||
}
|
||||
|
||||
private void StartDimmDetectionTask(ISettings settings, TimeSpan initialDelay)
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
@@ -232,19 +245,7 @@ internal class MemoryGroup : IGroup, IHardwareChanged, IHardwareDiscoveryTask
|
||||
|
||||
private static bool ShouldDeferDimmDetection(ISettings settings)
|
||||
{
|
||||
string environmentValue = Environment.GetEnvironmentVariable(DeferDimmDetectionEnvironmentVariable);
|
||||
if (!string.IsNullOrWhiteSpace(environmentValue))
|
||||
return IsTruthy(environmentValue);
|
||||
|
||||
return IsTruthy(settings.GetValue(DeferDimmDetectionSetting, "false"));
|
||||
}
|
||||
|
||||
private static bool IsTruthy(string value)
|
||||
{
|
||||
return value.Equals("1", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("true", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("yes", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("on", StringComparison.OrdinalIgnoreCase);
|
||||
return SettingsParsing.ShouldDefer(settings, DeferDimmDetectionSetting, DeferDimmDetectionEnvironmentVariable);
|
||||
}
|
||||
|
||||
private void AddDimms(List<SPDAccessor> accessors, ISettings settings, HardwareStartupTrace startupTrace)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
// Copyright (C) LibreHardwareMonitor and Contributors.
|
||||
|
||||
using System;
|
||||
|
||||
namespace LibreHardwareMonitor.Hardware;
|
||||
|
||||
/// <summary>
|
||||
/// Shared parsing for the boolean "defer detection / diagnostics" flags that several hardware components read from
|
||||
/// <see cref="ISettings" /> and environment variables. Keeps the truthy vocabulary and the env-overrides-setting
|
||||
/// precedence in one place so the components cannot drift.
|
||||
/// </summary>
|
||||
internal static class SettingsParsing
|
||||
{
|
||||
/// <summary>Returns whether <paramref name="value" /> is one of the accepted truthy tokens (1/true/yes/on).</summary>
|
||||
public static bool IsTruthy(string value)
|
||||
{
|
||||
return value != null
|
||||
&& (value.Equals("1", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("true", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("yes", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("on", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a defer flag: a non-empty environment variable wins, otherwise the setting value (default "false").
|
||||
/// </summary>
|
||||
public static bool ShouldDefer(ISettings settings, string settingName, string environmentVariable)
|
||||
{
|
||||
string environmentValue = Environment.GetEnvironmentVariable(environmentVariable);
|
||||
if (!string.IsNullOrWhiteSpace(environmentValue))
|
||||
return IsTruthy(environmentValue);
|
||||
|
||||
return IsTruthy(settings.GetValue(settingName, "false"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user