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 GalaxyRepositoryClient _client;
|
||||||
private readonly ConcurrentDictionary<string, GalaxyObject> _byTagName = new(StringComparer.Ordinal);
|
private readonly ConcurrentDictionary<string, GalaxyObject> _byTagName = new(StringComparer.Ordinal);
|
||||||
private readonly ConcurrentDictionary<int, GalaxyObject> _byGobjectId = new();
|
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>
|
/// <summary>Opaque token identifying this session in the AdminUI registry.</summary>
|
||||||
public Guid Token { get; } = Guid.NewGuid();
|
public Guid Token { get; } = Guid.NewGuid();
|
||||||
@@ -48,25 +50,37 @@ internal sealed class GalaxyBrowseSession : IBrowseSession
|
|||||||
public async Task<IReadOnlyList<BrowseNode>> RootAsync(CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<BrowseNode>> RootAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
var all = await _client.DiscoverHierarchyAsync(
|
await _rootGate.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
new DiscoverHierarchyOptions { IncludeAttributes = false }, cancellationToken)
|
try
|
||||||
.ConfigureAwait(false);
|
|
||||||
|
|
||||||
// Populate caches so later ExpandAsync calls can resolve children in-memory.
|
|
||||||
foreach (var obj in all)
|
|
||||||
{
|
{
|
||||||
_byTagName[obj.TagName] = obj;
|
var all = await _client.DiscoverHierarchyAsync(
|
||||||
_byGobjectId[obj.GobjectId] = obj;
|
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>
|
/// <summary>
|
||||||
@@ -146,7 +160,7 @@ internal sealed class GalaxyBrowseSession : IBrowseSession
|
|||||||
_byGobjectId[obj.GobjectId] = obj;
|
_byGobjectId[obj.GobjectId] = obj;
|
||||||
|
|
||||||
var displayName = !string.IsNullOrEmpty(obj.ContainedName) ? obj.ContainedName : obj.TagName;
|
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(
|
result.Add(new BrowseNode(
|
||||||
NodeId: obj.TagName,
|
NodeId: obj.TagName,
|
||||||
DisplayName: displayName,
|
DisplayName: displayName,
|
||||||
@@ -179,6 +193,7 @@ internal sealed class GalaxyBrowseSession : IBrowseSession
|
|||||||
{
|
{
|
||||||
if (_disposed) return;
|
if (_disposed) return;
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
_rootGate.Dispose();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _session.DisposeAsync().ConfigureAwait(false);
|
await _session.DisposeAsync().ConfigureAwait(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user