Skip to content

Commit 6a387db

Browse files
Open projects off main thread (#1893)
* moved the project opening process to a background thread to prevent the UI from blocking. * refactor project setup, ensure the service scope is disposed if there's an exception setting it up --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent 13eabbb commit 6a387db

1 file changed

Lines changed: 100 additions & 47 deletions

File tree

backend/FwLite/FwLiteShared/Services/ProjectServicesProvider.cs

Lines changed: 100 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ ILogger<ProjectServicesProvider> logger
2424
private IProjectProvider? FwDataProjectProvider =>
2525
projectProviders.FirstOrDefault(p => p.DataFormat == ProjectDataFormat.FwData);
2626
internal readonly ConcurrentDictionary<ProjectScope, ProjectScope> _projectScopes = new();
27+
2728
public async ValueTask DisposeAsync()
2829
{
2930
foreach (var projectScope in _projectScopes.Values)
3031
{
31-
await (projectScope.Cleanup?.Value.DisposeAsync() ?? ValueTask.CompletedTask);
32+
await projectScope.CleanupAsync();
3233
}
3334
}
3435

@@ -46,73 +47,112 @@ public async Task DisposeService(DotNetObjectReference<IAsyncDisposable> service
4647
}
4748

4849
[JSInvokable]
49-
public async Task<ProjectScope> OpenCrdtProject(string code)
50+
public Task<ProjectScope> OpenCrdtProject(string code)
5051
{
51-
var serviceScope = serviceProvider.CreateAsyncScope();
52-
var scopedServices = serviceScope.ServiceProvider;
53-
var project = crdtProjectsService.GetProject(code)
54-
?? throw new InvalidOperationException($"Crdt Project {code} not found");
55-
var server = lexboxProjectService.GetServer(project.Data);
56-
var currentProjectService = scopedServices.GetRequiredService<CurrentProjectService>();
57-
var projectData = await currentProjectService.SetupProjectContext(project);
58-
await scopedServices.GetRequiredService<SyncService>().SafeExecuteSync(true);
59-
await lexboxProjectService.ListenForProjectChanges(projectData, CancellationToken.None);
60-
var miniLcm = ActivatorUtilities.CreateInstance<MiniLcmJsInvokable>(scopedServices, project);
61-
var scope = new ProjectScope(Defer.Async(() =>
62-
{
63-
logger.LogInformation("Disposing project scope {ProjectName}", projectData.Name);
64-
return Task.CompletedTask;
65-
}), serviceScope, this, projectData.Name, miniLcm,
66-
ActivatorUtilities.CreateInstance<HistoryServiceJsInvokable>(scopedServices),
67-
ActivatorUtilities.CreateInstance<SyncServiceJsInvokable>(scopedServices))
52+
return Task.Run(async () =>
6853
{
69-
ProjectData = projectData,
70-
Server = server
71-
};
72-
_projectScopes.TryAdd(scope, scope);
73-
return scope;
54+
var serviceScope = serviceProvider.CreateAsyncScope();
55+
ProjectScope? scope = null;
56+
try
57+
{
58+
var scopedServices = serviceScope.ServiceProvider;
59+
var project = crdtProjectsService.GetProject(code)
60+
?? throw new InvalidOperationException($"Crdt Project {code} not found");
61+
var server = lexboxProjectService.GetServer(project.Data);
62+
var currentProjectService = scopedServices.GetRequiredService<CurrentProjectService>();
63+
var projectData = await currentProjectService.SetupProjectContext(project);
64+
await scopedServices.GetRequiredService<SyncService>().SafeExecuteSync(true);
65+
await lexboxProjectService.ListenForProjectChanges(projectData, CancellationToken.None);
66+
var miniLcm = ActivatorUtilities.CreateInstance<MiniLcmJsInvokable>(scopedServices, project);
67+
scope = ProjectScope.Create(serviceScope, this, projectData.Name, miniLcm);
68+
scope.ProjectData = projectData;
69+
scope.Server = server;
70+
scope.SetCrdtServices(
71+
ActivatorUtilities.CreateInstance<HistoryServiceJsInvokable>(scopedServices),
72+
ActivatorUtilities.CreateInstance<SyncServiceJsInvokable>(scopedServices)
73+
);
74+
_projectScopes.TryAdd(scope, scope);
75+
return scope;
76+
}
77+
catch
78+
{
79+
if (scope is not null) await scope.CleanupAsync();
80+
else await serviceScope.DisposeAsync();
81+
throw;
82+
}
83+
});
7484
}
7585

7686
[JSInvokable]
77-
public async Task<ProjectScope> OpenFwDataProject(string projectName)
87+
public Task<ProjectScope> OpenFwDataProject(string projectName)
7888
{
79-
var serviceScope = serviceProvider.CreateAsyncScope();
80-
var scopedServices = serviceScope.ServiceProvider;
81-
if (FwDataProjectProvider is null)
82-
throw new InvalidOperationException("FwData Project provider is not available");
83-
var project = FwDataProjectProvider.GetProject(projectName) ??
84-
throw new InvalidOperationException($"FwData Project {projectName} not found");
85-
var miniLcm = ActivatorUtilities.CreateInstance<MiniLcmJsInvokable>(scopedServices,
86-
await FwDataProjectProvider.OpenProject(project, scopedServices),
87-
project);
88-
var scope = new ProjectScope(Defer.Async(() =>
89+
return Task.Run(async () =>
8990
{
90-
logger.LogInformation("Disposing fwdata project scope {ProjectName}", projectName);
91-
return Task.CompletedTask;
92-
}), serviceScope, this, projectName, miniLcm, null, null);
93-
_projectScopes.TryAdd(scope, scope);
94-
return scope;
91+
var serviceScope = serviceProvider.CreateAsyncScope();
92+
ProjectScope? scope = null;
93+
try
94+
{
95+
var scopedServices = serviceScope.ServiceProvider;
96+
if (FwDataProjectProvider is null)
97+
throw new InvalidOperationException("FwData Project provider is not available");
98+
var project = FwDataProjectProvider.GetProject(projectName) ??
99+
throw new InvalidOperationException($"FwData Project {projectName} not found");
100+
var miniLcm = ActivatorUtilities.CreateInstance<MiniLcmJsInvokable>(
101+
scopedServices,
102+
await FwDataProjectProvider.OpenProject(project, scopedServices),
103+
project
104+
);
105+
scope = ProjectScope.Create(serviceScope, this, projectName, miniLcm);
106+
_projectScopes.TryAdd(scope, scope);
107+
return scope;
108+
}
109+
catch
110+
{
111+
if (scope is not null) await scope.CleanupAsync();
112+
else await serviceScope.DisposeAsync();
113+
throw;
114+
}
115+
});
95116
}
96117
}
97118

98119
public class ProjectScope
99120
{
100-
public ProjectScope(IAsyncDisposable cleanup,
121+
private static readonly ObjectFactory<ProjectScope> ProjectScopeFactory =
122+
ActivatorUtilities.CreateFactory<ProjectScope>(
123+
[
124+
typeof(AsyncServiceScope),
125+
typeof(ProjectServicesProvider),
126+
typeof(string),
127+
typeof(MiniLcmJsInvokable)
128+
]);
129+
public static ProjectScope Create(
101130
AsyncServiceScope serviceScope,
131+
ProjectServicesProvider projectServicesProvider,
132+
string projectName,
133+
MiniLcmJsInvokable miniLcm)
134+
{
135+
return ProjectScopeFactory.Invoke(serviceScope.ServiceProvider,
136+
[
137+
serviceScope,
138+
projectServicesProvider,
139+
projectName,
140+
miniLcm
141+
]);
142+
}
143+
144+
public ProjectScope(AsyncServiceScope serviceScope,
102145
ProjectServicesProvider projectServicesProvider,
103146
string projectName,
104147
MiniLcmJsInvokable miniLcm,
105-
HistoryServiceJsInvokable? historyService,
106-
SyncServiceJsInvokable? syncService)
148+
ILogger<ProjectScope> logger)
107149
{
108150
ProjectName = projectName;
109151
MiniLcm = DotNetObjectReference.Create(miniLcm);
110-
HistoryService = historyService is null ? null : DotNetObjectReference.Create(historyService);
111-
SyncService = syncService is null ? null : DotNetObjectReference.Create(syncService);
112152
Cleanup = DotNetObjectReference.Create(Defer.Async(async () =>
113153
{
154+
logger.LogInformation("Disposing project scope {ProjectName}", projectName);
114155
projectServicesProvider._projectScopes.TryRemove(this, out _);
115-
await cleanup.DisposeAsync();
116156
if (HistoryService is not null)
117157
{
118158
HistoryService.Dispose();
@@ -131,10 +171,23 @@ public ProjectScope(IAsyncDisposable cleanup,
131171
}));
132172
}
133173

174+
public void SetCrdtServices(
175+
HistoryServiceJsInvokable historyService,
176+
SyncServiceJsInvokable syncService)
177+
{
178+
HistoryService = DotNetObjectReference.Create(historyService);
179+
SyncService = DotNetObjectReference.Create(syncService);
180+
}
181+
182+
public ValueTask CleanupAsync()
183+
{
184+
return Cleanup?.Value.DisposeAsync() ?? ValueTask.CompletedTask;
185+
}
186+
134187
public DotNetObjectReference<IAsyncDisposable>? Cleanup { get; set; }
135188
public string ProjectName { get; set; }
136189
public LexboxServer? Server { get; set; }
137-
public ProjectData? ProjectData { get; init; }
190+
public ProjectData? ProjectData { get; set; }
138191
public DotNetObjectReference<MiniLcmJsInvokable> MiniLcm { get; set; }
139192
public DotNetObjectReference<HistoryServiceJsInvokable>? HistoryService { get; set; }
140193
public DotNetObjectReference<SyncServiceJsInvokable>? SyncService { get; set; }

0 commit comments

Comments
 (0)