feat(engine): ConnectionInstanceRegistry with per-id caching + invalidate on delete
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using VolcanicCore.Api.Auth;
|
||||
using VolcanicCore.Api.Contracts;
|
||||
using VolcanicCore.Api.Engine;
|
||||
using VolcanicCore.Persistence.Repositories;
|
||||
|
||||
namespace VolcanicCore.Api.Endpoints;
|
||||
@@ -46,8 +47,11 @@ public static class ConnectionsEndpoints
|
||||
}
|
||||
});
|
||||
|
||||
g.MapDelete("/{id}", (string id, ConnectionRepository repo) =>
|
||||
repo.Delete(id) ? Results.NoContent() : Results.NotFound());
|
||||
g.MapDelete("/{id}", async (string id, ConnectionRepository repo, ConnectionInstanceRegistry instances) =>
|
||||
{
|
||||
await instances.InvalidateAsync(id);
|
||||
return repo.Delete(id) ? Results.NoContent() : Results.NotFound();
|
||||
});
|
||||
}
|
||||
|
||||
private static JsonElement StripSecrets(string configJson, IReadOnlySet<string> secretFields)
|
||||
|
||||
69
src/VolcanicCore.Api/Engine/ConnectionInstanceRegistry.cs
Normal file
69
src/VolcanicCore.Api/Engine/ConnectionInstanceRegistry.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System.Text.Json;
|
||||
using VolcanicCore.Api.Registries;
|
||||
using VolcanicCore.Persistence.Repositories;
|
||||
|
||||
namespace VolcanicCore.Api.Engine;
|
||||
|
||||
public sealed class ConnectionInstanceRegistry : IConnectionInstanceRegistry, IAsyncDisposable
|
||||
{
|
||||
private sealed record Entry(string ProviderId, object Handle);
|
||||
|
||||
private readonly ConnectionRepository _repo;
|
||||
private readonly ConnectionProviderRegistry _providers;
|
||||
private readonly Dictionary<string, Entry> _cache = new(StringComparer.Ordinal);
|
||||
private readonly SemaphoreSlim _mu = new(1, 1);
|
||||
|
||||
public ConnectionInstanceRegistry(ConnectionRepository repo, ConnectionProviderRegistry providers)
|
||||
{
|
||||
_repo = repo;
|
||||
_providers = providers;
|
||||
}
|
||||
|
||||
public async Task<object> GetAsync(string connectionId, CancellationToken ct)
|
||||
{
|
||||
await _mu.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
if (_cache.TryGetValue(connectionId, out var hit)) return hit.Handle;
|
||||
|
||||
var row = _repo.Get(connectionId)
|
||||
?? throw new KeyNotFoundException($"Connection {connectionId} not found.");
|
||||
var provider = _providers.Get(row.ProviderId);
|
||||
using var doc = JsonDocument.Parse(row.ConfigJson);
|
||||
var handle = await provider.OpenAsync(doc.RootElement.Clone(), ct);
|
||||
_cache[connectionId] = new Entry(row.ProviderId, handle);
|
||||
return handle;
|
||||
}
|
||||
finally { _mu.Release(); }
|
||||
}
|
||||
|
||||
public async Task InvalidateAsync(string connectionId)
|
||||
{
|
||||
await _mu.WaitAsync();
|
||||
Entry? removed = null;
|
||||
try
|
||||
{
|
||||
if (_cache.Remove(connectionId, out var e)) removed = e;
|
||||
}
|
||||
finally { _mu.Release(); }
|
||||
|
||||
if (removed is not null)
|
||||
await _providers.Get(removed.ProviderId).CloseAsync(removed.Handle);
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
Entry[] snapshot;
|
||||
await _mu.WaitAsync();
|
||||
try
|
||||
{
|
||||
snapshot = _cache.Values.ToArray();
|
||||
_cache.Clear();
|
||||
}
|
||||
finally { _mu.Release(); }
|
||||
|
||||
foreach (var e in snapshot)
|
||||
try { await _providers.Get(e.ProviderId).CloseAsync(e.Handle); }
|
||||
catch { /* best-effort on shutdown */ }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using VolcanicCore.Api;
|
||||
using VolcanicCore.Api.Auth;
|
||||
using VolcanicCore.Api.Endpoints;
|
||||
using VolcanicCore.Api.Engine;
|
||||
using VolcanicCore.Api.Registries;
|
||||
using VolcanicCore.Core;
|
||||
using VolcanicCore.Persistence;
|
||||
@@ -112,6 +113,10 @@ builder.Services.AddSingleton<NodeRegistry>(sp =>
|
||||
builder.Services.AddSingleton<ConnectionProviderRegistry>(sp =>
|
||||
new ConnectionProviderRegistry(sp.GetServices<IConnectionProvider>()));
|
||||
|
||||
builder.Services.AddSingleton<ConnectionInstanceRegistry>();
|
||||
builder.Services.AddSingleton<IConnectionInstanceRegistry>(sp =>
|
||||
sp.GetRequiredService<ConnectionInstanceRegistry>());
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Apply migrations at boot.
|
||||
|
||||
Reference in New Issue
Block a user