fix(galaxy.browser): volatile _disposed, RootAsync gate, O(1) child hint
This commit is contained in:
@@ -20,7 +20,9 @@ internal sealed class GalaxyBrowseSession : IBrowseSession
|
||||
private readonly GalaxyRepositoryClient _client;
|
||||
private readonly ConcurrentDictionary<string, GalaxyObject> _byTagName = new(StringComparer.Ordinal);
|
||||
private readonly ConcurrentDictionary<int, GalaxyObject> _byGobjectId = new();
|
||||
private bool _disposed;
|
||||
private volatile bool _disposed;
|
||||
private readonly SemaphoreSlim _rootGate = new(1, 1);
|
||||
private HashSet<int> _hasChildrenSet = new();
|
||||
|
||||
/// <summary>Opaque token identifying this session in the AdminUI registry.</summary>
|
||||
public Guid Token { get; } = Guid.NewGuid();
|
||||
@@ -48,25 +50,37 @@ internal sealed class GalaxyBrowseSession : IBrowseSession
|
||||
public async Task<IReadOnlyList<BrowseNode>> RootAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
var all = await _client.DiscoverHierarchyAsync(
|
||||
new DiscoverHierarchyOptions { IncludeAttributes = false }, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// Populate caches so later ExpandAsync calls can resolve children in-memory.
|
||||
foreach (var obj in all)
|
||||
await _rootGate.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
_byTagName[obj.TagName] = obj;
|
||||
_byGobjectId[obj.GobjectId] = obj;
|
||||
var all = await _client.DiscoverHierarchyAsync(
|
||||
new DiscoverHierarchyOptions { IncludeAttributes = false }, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// Populate caches so later ExpandAsync calls can resolve children in-memory.
|
||||
foreach (var obj in all)
|
||||
{
|
||||
_byTagName[obj.TagName] = obj;
|
||||
_byGobjectId[obj.GobjectId] = obj;
|
||||
}
|
||||
|
||||
// Precompute the set of GobjectIds that appear as a parent — used by
|
||||
// Project to compute HasChildrenHint in O(1) instead of O(n²).
|
||||
_hasChildrenSet = new HashSet<int>(_byGobjectId.Values.Select(o => o.ParentGobjectId));
|
||||
|
||||
// Roots are objects whose parent isn't part of the returned set (typically
|
||||
// ParentGobjectId == 0 for WinPlatforms / top-level Areas).
|
||||
var roots = all
|
||||
.Where(o => o.ParentGobjectId == 0 || !_byGobjectId.ContainsKey(o.ParentGobjectId))
|
||||
.ToList();
|
||||
|
||||
LastUsedUtc = DateTime.UtcNow;
|
||||
return Project(roots);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rootGate.Release();
|
||||
}
|
||||
|
||||
// Roots are objects whose parent isn't part of the returned set (typically
|
||||
// ParentGobjectId == 0 for WinPlatforms / top-level Areas).
|
||||
var roots = all
|
||||
.Where(o => o.ParentGobjectId == 0 || !_byGobjectId.ContainsKey(o.ParentGobjectId))
|
||||
.ToList();
|
||||
|
||||
LastUsedUtc = DateTime.UtcNow;
|
||||
return Project(roots);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -146,7 +160,7 @@ internal sealed class GalaxyBrowseSession : IBrowseSession
|
||||
_byGobjectId[obj.GobjectId] = obj;
|
||||
|
||||
var displayName = !string.IsNullOrEmpty(obj.ContainedName) ? obj.ContainedName : obj.TagName;
|
||||
var hasChildrenHint = _byGobjectId.Values.Any(o => o.ParentGobjectId == obj.GobjectId);
|
||||
var hasChildrenHint = _hasChildrenSet.Contains(obj.GobjectId);
|
||||
result.Add(new BrowseNode(
|
||||
NodeId: obj.TagName,
|
||||
DisplayName: displayName,
|
||||
@@ -179,6 +193,7 @@ internal sealed class GalaxyBrowseSession : IBrowseSession
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
_rootGate.Dispose();
|
||||
try
|
||||
{
|
||||
await _session.DisposeAsync().ConfigureAwait(false);
|
||||
|
||||
Reference in New Issue
Block a user