fix: correctly use dependency order to unload, declare all plugin services as deps to PM

This commit is contained in:
goat 2023-03-08 22:37:44 +01:00
parent af670d6b11
commit f032adb155
No known key found for this signature in database
GPG key ID: 49E2AA8C6A76498B
4 changed files with 222 additions and 26 deletions

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
@ -19,6 +20,13 @@ namespace Dalamud;
/// </summary>
internal static class ServiceManager
{
/**
* TODO:
* - Unify dependency walking code(load/unload
* - Visualize/output .dot or imgui thing
*/
/// <summary>
/// Static log facility for Service{T}, to avoid duplicate instances for different types.
/// </summary>
@ -27,6 +35,38 @@ internal static class ServiceManager
private static readonly TaskCompletionSource BlockingServicesLoadedTaskCompletionSource = new();
private static readonly List<Type> LoadedServices = new();
/// <summary>
/// Kinds of services.
/// </summary>
[Flags]
public enum ServiceKind
{
/// <summary>
/// Not a service.
/// </summary>
None = 0,
/// <summary>
/// Regular service.
/// </summary>
ManualService = 1 << 0,
/// <summary>
/// Service that is loaded asynchronously while the game starts.
/// </summary>
EarlyLoadedService = 1 << 1,
/// <summary>
/// Service that is loaded before the game starts.
/// </summary>
BlockingEarlyLoadedService = 1 << 2,
/// <summary>
/// Service that is loaded automatically when the game starts, synchronously or asynchronously.
/// </summary>
AutoLoadService = EarlyLoadedService | BlockingEarlyLoadedService,
}
/// <summary>
/// Gets task that gets completed when all blocking early loading services are done loading.
@ -86,12 +126,34 @@ internal static class ServiceManager
var dependencyServicesMap = new Dictionary<Type, List<Type>>();
var getAsyncTaskMap = new Dictionary<Type, Task>();
// fill getAsyncTaskMap with services that were provided
lock (LoadedServices)
{
foreach (var loadedService in LoadedServices)
{
var getTask = (Task)typeof(Service<>)
.MakeGenericType(loadedService)
.InvokeMember(
"GetAsync",
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public,
null,
null,
null);
Debug.Assert(getTask != null, "Provided service getTask was null");
getAsyncTaskMap[typeof(Service<>).MakeGenericType(loadedService)] = getTask;
}
}
foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes())
{
var attr = serviceType.GetCustomAttribute<Service>(true)?.GetType();
if (attr?.IsAssignableTo(typeof(EarlyLoadedService)) != true)
var serviceKind = serviceType.GetServiceKind();
if (serviceKind == ServiceKind.None)
continue;
Debug.Assert(!serviceKind.HasFlag(ServiceKind.ManualService), "Regular services should never end up here");
var getTask = (Task)typeof(Service<>)
.MakeGenericType(serviceType)
@ -102,9 +164,9 @@ internal static class ServiceManager
null,
null);
if (attr.IsAssignableTo(typeof(BlockingEarlyLoadedService)))
if (serviceKind.HasFlag(ServiceKind.BlockingEarlyLoadedService))
{
getAsyncTaskMap[serviceType] = getTask;
getAsyncTaskMap[typeof(Service<>).MakeGenericType(serviceType)] = getTask;
blockingEarlyLoadingServices.Add(serviceType);
}
else
@ -148,8 +210,29 @@ internal static class ServiceManager
{
foreach (var serviceType in servicesToLoad)
{
if (dependencyServicesMap[serviceType].Any(
x => getAsyncTaskMap.GetValueOrDefault(x)?.IsCompleted == false))
var hasDeps = true;
foreach (var dependency in dependencyServicesMap[serviceType])
{
var depServiceKind = dependency.GetServiceKind();
var depResolveTask = getAsyncTaskMap.GetValueOrDefault(dependency);
if (depResolveTask == null && (depServiceKind.HasFlag(ServiceKind.EarlyLoadedService) || depServiceKind.HasFlag(ServiceKind.BlockingEarlyLoadedService)))
{
Log.Information("{Type}: {Dependency} has no resolver task, is it early loaded or blocking early loaded?", serviceType.FullName!, dependency.FullName!);
Debug.Assert(false, $"No resolver for dependent service {dependency.FullName}");
}
else if (depResolveTask is { Status: TaskStatus.Created })
{
depResolveTask.Start();
}
else if (depResolveTask is { IsCompleted: false })
{
Log.Verbose("{Type} waiting for {Dependency}", serviceType.FullName!, dependency.FullName!);
hasDeps = false;
}
}
if (!hasDeps)
continue;
tasks.Add((Task)typeof(Service<>)
@ -227,23 +310,104 @@ internal static class ServiceManager
return;
}
lock (LoadedServices)
var dependencyServicesMap = new Dictionary<Type, List<Type>>();
var allToUnload = new HashSet<Type>();
var unloadOrder = new List<Type>();
Log.Information("==== COLLECTING SERVICES TO UNLOAD ====");
foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes())
{
while (LoadedServices.Any())
if (!serviceType.IsAssignableTo(typeof(IServiceType)))
continue;
dependencyServicesMap[serviceType] =
((List<Type>)typeof(Service<>)
.MakeGenericType(serviceType)
.InvokeMember(
"GetDependencyServices",
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public,
null,
null,
null))!
.Select(x => x.GetGenericArguments()[0]).ToList();
/*
Log.Verbose("=> Deps for {Type}", serviceType.FullName!);
foreach (var dependencyService in dependencyServicesMap[serviceType])
{
var serviceType = LoadedServices.Last();
LoadedServices.RemoveAt(LoadedServices.Count - 1);
Log.Verbose("\t\t=> {Type}", dependencyService.FullName!);
}
*/
typeof(Service<>)
.MakeGenericType(serviceType)
allToUnload.Add(serviceType);
}
void UnloadService(Type serviceType)
{
if (unloadOrder.Contains(serviceType))
return;
var deps = dependencyServicesMap[serviceType];
foreach (var dep in deps)
{
UnloadService(dep);
}
unloadOrder.Add(serviceType);
Log.Information("Queue for unload {Type}", serviceType.FullName!);
}
foreach (var serviceType in allToUnload)
{
UnloadService(serviceType);
}
Log.Information("==== UNLOADING ALL SERVICES ====");
unloadOrder.Reverse();
foreach (var type in unloadOrder)
{
Log.Verbose("Unload {Type}", type.FullName!);
typeof(Service<>)
.MakeGenericType(type)
.InvokeMember(
"Unset",
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic,
null,
null,
null);
}
}
lock (LoadedServices)
{
LoadedServices.Clear();
}
}
/// <summary>
/// Get the service type of this type.
/// </summary>
/// <param name="type">The type to check.</param>
/// <returns>The type of service this type is.</returns>
public static ServiceKind GetServiceKind(this Type type)
{
var attr = type.GetCustomAttribute<Service>(true)?.GetType();
if (attr == null)
return ServiceKind.None;
Debug.Assert(
type.IsAssignableTo(typeof(IServiceType)),
"Service did not inherit from IServiceType");
if (attr.IsAssignableTo(typeof(BlockingEarlyLoadedService)))
return ServiceKind.BlockingEarlyLoadedService;
if (attr.IsAssignableTo(typeof(EarlyLoadedService)))
return ServiceKind.EarlyLoadedService;
return ServiceKind.ManualService;
}
/// <summary>