feat: Adds an Item/Mobile memory leak detector. Fixes minor leak in doors. (#2159)

### Summary

Adds the command [TrackLeaks to enable tracking item/mobiles that have been deleted but still have dangling references. Requires adding the _TRACK_LEAKS_ define constant during build.
This commit is contained in:
Kamron Batman 2025-04-15 19:14:45 -07:00 committed by GitHub
parent 1e349f4369
commit 4aa272d429
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 256 additions and 9 deletions

View file

@ -13,18 +13,31 @@ namespace System;
/// </summary>
internal sealed class Gen2GcCallback : CriticalFinalizerObject
{
private readonly Func<object, bool> _callback;
private readonly Func<bool>? _callback0;
private readonly Func<object, bool>? _callback1;
private GCHandle _weakTargetObj;
private Gen2GcCallback(Func<bool> callback) => _callback0 = callback;
private Gen2GcCallback(Func<object, bool> callback, object targetObj)
{
_callback = callback;
_callback1 = callback;
_weakTargetObj = GCHandle.Alloc(targetObj, GCHandleType.Weak);
}
/// <summary>
/// Schedule 'callback' to be called in the next GC. If the callback returns true it is
/// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop.
/// rescheduled for the next Gen 2 GC, otherwise the callback stops.
/// </summary>
public static void Register(Func<bool> callback)
{
// Create an unreachable object that remembers the callback function and target object.
new Gen2GcCallback(callback);
}
/// <summary>
/// Schedule 'callback' to be called in the next GC. If the callback returns true it is
/// rescheduled for the next Gen 2 GC, otherwise the callback stops.
///
/// NOTE: This callback will be kept alive until either the callback function returns false,
/// or the target object dies.
@ -51,8 +64,8 @@ internal sealed class Gen2GcCallback : CriticalFinalizerObject
// Execute the callback method.
try
{
Debug.Assert(_callback != null);
if (_callback?.Invoke(targetObj) != true)
Debug.Assert(_callback1 != null);
if (!_callback1(targetObj))
{
// If the callback returns false, this callback object is no longer needed.
_weakTargetObj.Free();
@ -63,8 +76,29 @@ internal sealed class Gen2GcCallback : CriticalFinalizerObject
{
// Ensure that we still get a chance to resurrect this object, even if the callback throws an exception.
#if DEBUG
// Except in DEBUG, as we really shouldn't be hitting any exceptions here.
throw;
// Except in DEBUG, as we really shouldn't be hitting any exceptions here.
throw;
#endif
}
}
else
{
// Execute the callback method.
try
{
Debug.Assert(_callback0 != null);
if (!_callback0())
{
// If the callback returns false, this callback object is no longer needed.
return;
}
}
catch
{
// Ensure that we still get a chance to resurrect this object, even if the callback throws an exception.
#if DEBUG
// Except in DEBUG, as we really shouldn't be hitting any exceptions here.
throw;
#endif
}
}