Add IInternal/PublicDisposableService (#1696)

* Add IInternal/PublicDisposableService

Plugins are exposed interfaces that are not inherited from
`IDisposable`, but services implementing plugin interfaces often
implement `IDisposable`. Some plugins may try to call
`IDisposable.Dispose` on everything provided, and it also is possible to
use `using` clause too eagerly while working on Dalamud itself, such as
writing `using var smth = await Service<SomeService>.GetAsync();`. Such
behaviors often lead to a difficult-to-debug errors, and making those
services either not an `IDisposable` or making `IDisposable.Dispose` do
nothing if the object has been loaded would prevent such errors. As
`ServiceManager` must be the only class dealing with construction and
disposal of services, `IInternalDisposableService` has been added to
limit who can dispose the object. `IPublicDisposableService` also has
been added to classes that can be constructed and accessed directly by
plugins; for those, `Dispose` will be ignored if the instance is a
service instance, and only `DisposeService` will respond.

In addition, `DalamudPluginInterface` and `UiBuilder` also have been
changed so that their `IDisposable.Dispose` no longer respond, and
instead, internal functions have been added to only allow disposal from
Dalamud.

* Cleanup

* Postmerge fixes

* More explanation on RunOnFrameworkThread(ClearHooks)

* Mark ReliableFileStorage public ctor obsolete

---------

Co-authored-by: goat <16760685+goaaats@users.noreply.github.com>
This commit is contained in:
srkizer 2024-03-17 00:58:05 +09:00 committed by GitHub
parent dcec076ca7
commit 87b9edb448
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 441 additions and 381 deletions

View file

@ -204,12 +204,12 @@ internal sealed partial class FontAtlasFactory
{
while (this.IsBuildInProgress)
await Task.Delay(100);
this.Garbage.Dispose();
this.Clear();
});
}
else
{
this.Garbage.Dispose();
this.Clear();
}
return newRefCount;
@ -227,6 +227,20 @@ internal sealed partial class FontAtlasFactory
var axisSubstance = this.Substances.OfType<GamePrebakedFontHandle.HandleSubstance>().Single();
return new(factory, this, axisSubstance, isAsync) { BuildStep = FontAtlasBuildStep.PreBuild };
}
public void Clear()
{
try
{
this.Garbage.Dispose();
}
catch (Exception e)
{
Log.Error(
e,
$"Disposing {nameof(FontAtlasBuiltData)} of {this.Owner?.Name ?? "???"}.");
}
}
}
private class DalamudFontAtlas : IFontAtlas, DisposeSafety.IDisposeCallback
@ -547,13 +561,13 @@ internal sealed partial class FontAtlasFactory
{
if (this.buildIndex != rebuildIndex)
{
data.ExplicitDisposeIgnoreExceptions();
data.Release();
return;
}
var prevBuiltData = this.builtData;
this.builtData = data;
prevBuiltData.ExplicitDisposeIgnoreExceptions();
prevBuiltData?.Release();
this.buildTask = EmptyTask;
fontsAndLocks.EnsureCapacity(data.Substances.Sum(x => x.RelevantHandles.Count));

View file

@ -31,7 +31,7 @@ namespace Dalamud.Interface.ManagedFontAtlas.Internals;
/// </summary>
[ServiceManager.BlockingEarlyLoadedService]
internal sealed partial class FontAtlasFactory
: IServiceType, GamePrebakedFontHandle.IGameFontTextureProvider, IDisposable
: IInternalDisposableService, GamePrebakedFontHandle.IGameFontTextureProvider
{
private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new();
private readonly CancellationTokenSource cancellationTokenSource = new();
@ -161,7 +161,7 @@ internal sealed partial class FontAtlasFactory
this.dalamudAssetManager.IsStreamImmediatelyAvailable(DalamudAsset.LodestoneGameSymbol);
/// <inheritdoc/>
public void Dispose()
void IInternalDisposableService.DisposeService()
{
this.cancellationTokenSource.Cancel();
this.scopedFinalizer.Dispose();

View file

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Interface.Internal;
@ -291,11 +292,15 @@ internal abstract class FontHandle : IFontHandle
{
if (disposing)
{
if (Interlocked.Exchange(ref this.manager, null) is not { } managerToDisassociate)
return;
if (this.pushedFonts.Count > 0)
Log.Warning($"{nameof(IFontHandle)}.{nameof(IDisposable.Dispose)}: fonts were still in a stack.");
this.Manager.FreeFontHandle(this);
this.manager = null;
managerToDisassociate.FreeFontHandle(this);
this.Disposed?.InvokeSafely();
this.Disposed = null;
this.ImFontChanged = null;
}
}