Skip to content

Commit

Permalink
Create new service scope each time a project is opened (#1405)
Browse files Browse the repository at this point in the history
* Create new service scope each time a project is opened

* remove project scope tracking now that we're creating custom scopes per project

* ensure when the circuit is closed all the created project scopes will also be destroyed

---------

Co-authored-by: Kevin Hahn <[email protected]>
  • Loading branch information
myieye and hahn-kev authored Jan 23, 2025
1 parent 0e2f8d8 commit 9cdd4d8
Showing 1 changed file with 22 additions and 24 deletions.
46 changes: 22 additions & 24 deletions backend/FwLite/FwLiteShared/Services/ProjectServicesProvider.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FwLiteShared.Projects;
using System.Collections.Concurrent;
using FwLiteShared.Projects;
using LcmCrdt;
using LexCore.Utils;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -12,24 +13,22 @@ namespace FwLiteShared.Services;
//this service is special, it is scoped, but it should not inject any scoped project services
public class ProjectServicesProvider(
CrdtProjectsService crdtProjectsService,
IServiceProvider scopedServices,
IServiceProvider serviceProvider,
LexboxProjectService lexboxProjectService,
ChangeEventBus changeEventBus,
IEnumerable<IProjectProvider> projectProviders,
IJSRuntime jsRuntime,
ILogger<ProjectServicesProvider> logger
) : IAsyncDisposable
): IAsyncDisposable
{
private IProjectProvider? FwDataProjectProvider =>
projectProviders.FirstOrDefault(p => p.DataFormat == ProjectDataFormat.FwData);
private readonly ConcurrentWeakDictionary<string, ProjectScope> _projectScopes = new();
//handles cleanup of project scopes which didn't get cleaned up by the js code, maybe because the user closed the tab
//this will get executed when the blazor circuit is disposed
internal readonly ConcurrentDictionary<ProjectScope, ProjectScope> _projectScopes = new();
public async ValueTask DisposeAsync()
{
foreach (var scope in _projectScopes.Values)
foreach (var projectScope in _projectScopes.Values)
{
await (scope.Cleanup?.Value.DisposeAsync() ?? ValueTask.CompletedTask);
await (projectScope.Cleanup?.Value.DisposeAsync() ?? ValueTask.CompletedTask);
}
}

Expand All @@ -42,6 +41,8 @@ public async Task DisposeService(DotNetObjectReference<IAsyncDisposable> service
[JSInvokable]
public async Task<ProjectScope> OpenCrdtProject(string projectName)
{
var serviceScope = serviceProvider.CreateAsyncScope();
var scopedServices = serviceScope.ServiceProvider;
var project = crdtProjectsService.GetProject(projectName) ??
throw new InvalidOperationException($"Crdt Project {projectName} not found");
var currentProjectService = scopedServices.GetRequiredService<CurrentProjectService>();
Expand All @@ -55,18 +56,18 @@ public async Task<ProjectScope> OpenCrdtProject(string projectName)
var scope = new ProjectScope(Defer.Async(() =>
{
logger.LogInformation("Disposing project scope {ProjectName}", projectName);
currentProjectService.ClearProjectContext();
entryUpdatedSubscription.Dispose();
_projectScopes.Remove(projectName);
return Task.CompletedTask;
}), projectName, miniLcm, ActivatorUtilities.CreateInstance<HistoryServiceJsInvokable>(scopedServices));
await TrackScope(scope);
}), serviceScope, this, projectName, miniLcm, ActivatorUtilities.CreateInstance<HistoryServiceJsInvokable>(scopedServices));
_projectScopes.TryAdd(scope, scope);
return scope;
}

[JSInvokable]
public async Task<ProjectScope> OpenFwDataProject(string projectName)
public Task<ProjectScope> OpenFwDataProject(string projectName)
{
var serviceScope = serviceProvider.CreateAsyncScope();
var scopedServices = serviceScope.ServiceProvider;
if (FwDataProjectProvider is null)
throw new InvalidOperationException("FwData Project provider is not available");
var project = FwDataProjectProvider.GetProject(projectName) ??
Expand All @@ -77,24 +78,18 @@ public async Task<ProjectScope> OpenFwDataProject(string projectName)
var scope = new ProjectScope(Defer.Async(() =>
{
logger.LogInformation("Disposing fwdata project scope {ProjectName}", projectName);
_projectScopes.Remove(projectName);
return Task.CompletedTask;
}), projectName, miniLcm, null);
await TrackScope(scope);
return scope;
}

private async ValueTask TrackScope(ProjectScope scope)
{
var oldScope = _projectScopes.Remove(scope.ProjectName);
_projectScopes.Add(scope.ProjectName, scope);
await (oldScope?.Cleanup?.Value.DisposeAsync() ?? ValueTask.CompletedTask);
}), serviceScope, this, projectName, miniLcm, null);
_projectScopes.TryAdd(scope, scope);
return Task.FromResult(scope);
}
}

public class ProjectScope
{
public ProjectScope(IAsyncDisposable cleanup,
AsyncServiceScope serviceScope,
ProjectServicesProvider projectServicesProvider,
string projectName,
MiniLcmJsInvokable miniLcm,
HistoryServiceJsInvokable? historyService)
Expand All @@ -104,6 +99,7 @@ public ProjectScope(IAsyncDisposable cleanup,
HistoryService = historyService is null ? null : DotNetObjectReference.Create(historyService);
Cleanup = DotNetObjectReference.Create(Defer.Async(async () =>
{
projectServicesProvider._projectScopes.TryRemove(this, out _);
await cleanup.DisposeAsync();
if (HistoryService is not null)
{
Expand All @@ -112,6 +108,8 @@ public ProjectScope(IAsyncDisposable cleanup,

MiniLcm.Value.Dispose();
MiniLcm.Dispose();
await serviceScope.DisposeAsync();
//cleanup the dotnet object reference, this will not trigger this callback again
Cleanup?.Dispose();
Cleanup = null;
}));
Expand Down

0 comments on commit 9cdd4d8

Please sign in to comment.