Skip to content

Commit

Permalink
fix: fix memory leak
Browse files Browse the repository at this point in the history
fix memory leak
add more for FreeRuntime
  • Loading branch information
LazuliKao committed Jul 31, 2023
1 parent 76823b0 commit 79bd3b4
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 19 deletions.
19 changes: 19 additions & 0 deletions src/Hook/QuickJS/FreeRuntime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Hosihikari.NativeInterop.Hook.ObjectOriented;

Check failure on line 1 in src/Hook/QuickJS/FreeRuntime.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'NativeInterop' does not exist in the namespace 'Hosihikari' (are you missing an assembly reference?)
using Hosihikari.VanillaScript.QuickJS.Types;

namespace Hosihikari.VanillaScript.Hook.QuickJS;

internal class FreeRuntime : HookBase<FreeRuntime.HookDelegate>
{
internal unsafe delegate void HookDelegate(JsRuntime* rt);

public FreeRuntime()
: base("JS_FreeRuntime") { }

public override unsafe HookDelegate HookedFunc =>
rt =>
{
Loader.Manager.FreeRuntime(rt);
Original.Invoke(rt);
};
}
21 changes: 21 additions & 0 deletions src/Hook/QuickJS/NewRuntime2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Hosihikari.NativeInterop.Hook.ObjectOriented;

Check failure on line 1 in src/Hook/QuickJS/NewRuntime2.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'NativeInterop' does not exist in the namespace 'Hosihikari' (are you missing an assembly reference?)
using Hosihikari.VanillaScript.QuickJS.Types;

namespace Hosihikari.VanillaScript.Hook.QuickJS;

internal class NewRuntime2 : HookBase<NewRuntime2.HookDelegate>
{
internal unsafe delegate JsRuntime* HookDelegate(JsMallocFunctions* mf, void* opaque);

public NewRuntime2()
: base("JS_NewRuntime2") { }

public override unsafe HookDelegate HookedFunc =>
(mf, opaque) =>
{
var runtime = Original(mf, opaque);
Loader.Manager.AddRuntime(runtime);
return runtime;
};
}
//JS_FreeRuntime
3 changes: 2 additions & 1 deletion src/Hook/RequestReload.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ public RequestReload()
public override unsafe HookDelegate HookedFunc =>
minecraft =>
{
Original.Invoke(minecraft);
Config.Reload();
Loader.Manager.FreeAllContext();
Original.Invoke(minecraft);
};
}
}
51 changes: 51 additions & 0 deletions src/Loader/Manager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Hosihikari.VanillaScript.Loader;
public static partial class Manager
{
internal static readonly List<JsContextWrapper> LoadedScriptsContext = new();
internal static readonly List<JsRuntimeWrapper> LoadedScriptsRuntime = new();

internal static unsafe void AddContext(JsContext* ctx, bool isLoaderContext)
{
Expand All @@ -18,8 +19,32 @@ internal static unsafe void AddContext(JsContext* ctx, bool isLoaderContext)
}
}

internal static void FreeAllContext()
{
unsafe
{
foreach (var toFree in LoadedScriptsContext.ToArray())
{
try
{
toFree.Free();
}
catch (Exception ex)
{
Log.Logger.Error("Free JsContext " + ((nint)toFree.Context).ToString("X"), ex);
}
finally
{
LoadedScriptsContext.Remove(toFree);
}
}
}
Log.Logger.Trace("Free All JsContext.");
}

internal static unsafe void FreeContext(JsContext* ctx)
{
Log.Logger.Trace("JsContext Free " + ((nint)ctx).ToString("X"));
foreach (var toFree in LoadedScriptsContext.FindAll(x => x.Context == ctx).ToArray())
{
try
Expand All @@ -36,4 +61,30 @@ internal static unsafe void FreeContext(JsContext* ctx)
}
}
}

internal static unsafe void AddRuntime(JsRuntime* rt)
{
var rtInstance = JsRuntimeWrapper.FetchOrCreate(rt); //add to LoadedScriptsRuntime
Log.Logger.Trace("JsRuntime Add" + ((nint)rtInstance.Runtime).ToString("X"));
}

internal static unsafe void FreeRuntime(JsRuntime* rt)
{
foreach (var toFree in LoadedScriptsRuntime.FindAll(x => x.Runtime == rt).ToArray())
{
try
{
Log.Logger.Trace("JsRuntime Free" + ((nint)rt).ToString("X"));
toFree.Free();
}
catch (Exception ex)
{
Log.Logger.Error("Free JsRuntime " + ((nint)rt).ToString("X"), ex);
}
finally
{
LoadedScriptsRuntime.Remove(toFree);
}
}
}
}
13 changes: 13 additions & 0 deletions src/Loader/SetupRuntime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Hosihikari.VanillaScript.QuickJS.Wrapper;

namespace Hosihikari.VanillaScript.Loader;

public static partial class Manager
{
public static event Action<JsRuntimeWrapper>? OnRuntimeCreated;

internal static void SetupRuntime(JsRuntimeWrapper ctx)
{
OnRuntimeCreated?.Invoke(ctx);
}
}
3 changes: 3 additions & 0 deletions src/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ public void Initialize(AssemblyPlugin plugin)
new EnableScriptingHook().Install();
new Hook.QuickJS.Eval().Install();
new Hook.QuickJS.FreeContext().Install();
new Hook.QuickJS.FreeRuntime().Install();
new Hook.QuickJS.NewRuntime2().Install();
new Hook.QuickJS.AddIntrinsicBaseObjects().Install();
new Hook.RequestReload().Install();
new Hook.JsLog.ContextObjectBindPrint().Install();
Assets.Prepare.Init();
}
Expand Down
10 changes: 8 additions & 2 deletions src/QuickJS/Helper/JsPromiseHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public static void AwaitTask(
Task tasks
)
{
var safeThis = new SafeJsValue(thisObj);
var safeThis = new SafeJsValue(thisObj, ctxPtr);
Task.Run(async () =>
{
try
Expand Down Expand Up @@ -105,7 +105,7 @@ public static void AwaitTask<T>(
Func<T, JsValue> fetchResult
)
{
var safeThis = new SafeJsValue(thisObj);
var safeThis = new SafeJsValue(thisObj, ctxPtr);
Task.Run(async () =>
{
try
Expand All @@ -125,6 +125,9 @@ Func<T, JsValue> fetchResult
1,
&resultObj
);
safeThis.FreeThis();
promise.resolve.FreeThis();
promise.reject.FreeThis();
}
}
});
Expand All @@ -145,6 +148,9 @@ Func<T, JsValue> fetchResult
1,
&reasonObj
);
safeThis.FreeThis();
promise.resolve.FreeThis();
promise.reject.FreeThis();
}
}
});
Expand Down
26 changes: 26 additions & 0 deletions src/QuickJS/Types/JsMallocFunctions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace Hosihikari.VanillaScript.QuickJS.Types;

using size_t = UIntPtr;

public ref struct JsMallocState
{
public size_t MallocCount;
public size_t MallocSize;
public size_t MallocLimit;
public unsafe void* Opaque; /* user opaque */
}

public unsafe ref struct JsMallocFunctions
{
//void *(*js_malloc)(JSMallocState *s, size_t size);
public delegate* unmanaged<JsMallocState*, nuint, void*> JsMalloc;

//void (*js_free)(JSMallocState *s, void *ptr);
public delegate* unmanaged<JsMallocState*, void*, void> JsFree;

//void *(*js_realloc)(JSMallocState *s, void *ptr, size_t size);
public delegate* unmanaged<JsMallocState*, void*, nuint, void*> JsReAlloc;

//size_t (*js_malloc_usable_size)(const void *ptr);
public delegate* unmanaged<void*, size_t> JsMallocUsableSize;
}
15 changes: 9 additions & 6 deletions src/QuickJS/Wrapper/AutoDropJsValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ public unsafe AutoDropJsValue(JsValue value, JsContext* context)
{
_value = value;
_context = context;
if (JsContextWrapper.TryGet((nint)context, out var tCtx))
{
tCtx.FreeContextCallback += FreeThis;
}
}

/// <summary>
Expand All @@ -44,24 +48,23 @@ public JsValue Steal()

public void Dispose()
{
ReleaseUnmanagedResources();
FreeThis();
GC.SuppressFinalize(this); //prevent call ~SafeJsValue()
}

~AutoDropJsValue()
{
ReleaseUnmanagedResources();
FreeThis();
}

//bool _disposed = false;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReleaseUnmanagedResources()
private void FreeThis()
{
unsafe
{
//if (_disposed)
// return;
//_disposed = true;
if (JsContextWrapper.TryGet((nint)_context, out var tCtx))
tCtx.FreeContextCallback -= FreeThis;
#if DEBUG
//var stack = Environment.StackTrace;
//LevelTick.PostTick(() =>
Expand Down
28 changes: 23 additions & 5 deletions src/QuickJS/Wrapper/JsContextWrapper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;
using Hosihikari.VanillaScript.Loader;
using Hosihikari.VanillaScript.QuickJS.Helper;
Expand All @@ -10,11 +9,12 @@ namespace Hosihikari.VanillaScript.QuickJS.Wrapper;
public class JsContextWrapper
{
public unsafe JsContext* Context { get; }
private List<GCHandle> savedObject = new();
private readonly List<GCHandle> _savedObject = new();
public event Action? FreeContextCallback;

internal void Pin(object obj)
{
savedObject.Add(GCHandle.Alloc(obj));
_savedObject.Add(GCHandle.Alloc(obj));
}

public static unsafe implicit operator JsContextWrapper(JsContext* ctx)
Expand Down Expand Up @@ -57,11 +57,29 @@ public static unsafe JsContextWrapper FetchOrCreate(JsContext* ctx)
return newInstance;
}

private bool _freed = false;

internal void ThrowIfFree()
{
if (_freed)
{
throw new ObjectDisposedException("JsContextWrapper");
}
}

internal void Free()
{
foreach (var pinedItem in savedObject)
try
{
foreach (var pinedItem in _savedObject)
{
pinedItem.Free();
}
FreeContextCallback?.Invoke();
}
finally
{
pinedItem.Free();
_freed = true;
}
}

Expand Down
65 changes: 65 additions & 0 deletions src/QuickJS/Wrapper/JsRuntimeWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using Hosihikari.VanillaScript.QuickJS.Types;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Hosihikari.VanillaScript.Loader;

namespace Hosihikari.VanillaScript.QuickJS.Wrapper;

public class JsRuntimeWrapper
{
public unsafe JsRuntime* Runtime { get; }
private readonly List<GCHandle> _savedObject = new();

internal void Pin(object obj)
{
_savedObject.Add(GCHandle.Alloc(obj));
}

public static unsafe implicit operator JsRuntimeWrapper(JsRuntime* rt)
{
return FetchOrCreate(rt);
}

private unsafe JsRuntimeWrapper(JsRuntime* rt)
{
Runtime = rt;
Manager.SetupRuntime(this);
}

public static bool TryGet(nint ctxPtr, [NotNullWhen(true)] out JsRuntimeWrapper? ctx)
{
unsafe
{
if (
Manager.LoadedScriptsRuntime.FirstOrDefault(x => x.Runtime == ctxPtr.ToPointer()) is
{ } oldCtx
)
{
ctx = oldCtx;
return true;
}

ctx = null;
return false;
}
}

public static unsafe JsRuntimeWrapper FetchOrCreate(JsRuntime* ctx)
{
if (Manager.LoadedScriptsRuntime.FirstOrDefault(x => x.Runtime == ctx) is { } oldCtx)
{
return oldCtx;
}
var newInstance = new JsRuntimeWrapper(ctx);
Manager.LoadedScriptsRuntime.Add(newInstance);
return newInstance;
}

internal void Free()
{
foreach (var pinedItem in _savedObject)
{
pinedItem.Free();
}
}
}
Loading

0 comments on commit 79bd3b4

Please sign in to comment.