◐ Shell
reader mode source ↗
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
File filter
Conversations
Jump to
Diff view
Apply and reload
Show whitespace
Diff view
Apply and reload
6 changes: 3 additions & 3 deletions src/embed_tests/TestDomainReload.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ from Python.EmbeddingTest.Domain import MyClass
def test_obj_call():
obj = MyClass()
obj.Method()
obj.StaticMethod()
obj.Property = 1
obj.Field = 10

Expand Down Expand Up @@ -288,7 +288,7 @@ void ExecTest()

GC.Collect();
GC.WaitForPendingFinalizers(); // <- this will put former `num` into Finalizer queue
Finalizer.Instance.Collect(forceDispose: true);
// ^- this will call PyObject.Dispose, which will call XDecref on `num.Handle`,
// but Python interpreter from "run" 1 is long gone, so it will corrupt memory instead.
Assert.False(numRef.IsAlive);
Expand Down Expand Up @@ -333,7 +333,7 @@ void ExecTest()
PythonEngine.Initialize(); // <- "run" 2 starts
GC.Collect();
GC.WaitForPendingFinalizers();
Finalizer.Instance.Collect(forceDispose: true);
Assert.False(objRef.IsAlive);
}
finally
Expand Down
149 changes: 33 additions & 116 deletions src/embed_tests/TestFinalizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
using Python.Runtime;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;

namespace Python.EmbeddingTest
Expand All @@ -28,26 +28,14 @@ public void TearDown()
PythonEngine.Shutdown();
}

private static bool FullGCCollect()
{
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
try
{
return GC.WaitForFullGCComplete() == GCNotificationStatus.Succeeded;
}
catch (NotImplementedException)
{
// Some clr runtime didn't implement GC.WaitForFullGCComplete yet.
return false;
}
finally
{
GC.WaitForPendingFinalizers();
}
}

[Test]
[Ignore("Ignore temporarily")]
public void CollectBasicObject()
{
Assert.IsTrue(Finalizer.Instance.Enable);
Expand All @@ -64,11 +52,7 @@ public void CollectBasicObject()
Assert.IsFalse(called, "The event handler was called before it was installed");
Finalizer.Instance.CollectOnce += handler;

WeakReference shortWeak;
WeakReference longWeak;
{
MakeAGarbage(out shortWeak, out longWeak);
}
FullGCCollect();
// The object has been resurrected
Warn.If(
Expand All @@ -86,7 +70,7 @@ public void CollectBasicObject()
var garbage = Finalizer.Instance.GetCollectedObjects();
Assert.NotZero(garbage.Count, "There should still be garbage around");
Warn.Unless(
garbage.Any(T => ReferenceEquals(T.Target, longWeak.Target)),
$"The {nameof(longWeak)} reference doesn't show up in the garbage list",
garbage
);
Expand All @@ -104,33 +88,45 @@ public void CollectBasicObject()
}

[Test]
[Ignore("Ignore temporarily")]
public void CollectOnShutdown()
{
IntPtr op = MakeAGarbage(out var shortWeak, out var longWeak);
int hash = shortWeak.Target.GetHashCode();
List<WeakReference> garbage;
if (!FullGCCollect())
{
Assert.IsTrue(WaitForCollected(op, hash, 10000));
}
Assert.IsFalse(shortWeak.IsAlive);
garbage = Finalizer.Instance.GetCollectedObjects();
Assert.IsNotEmpty(garbage, "The garbage object should be collected");
Assert.IsTrue(garbage.Any(r => ReferenceEquals(r.Target, longWeak.Target)),
"Garbage should contains the collected object");

PythonEngine.Shutdown();
garbage = Finalizer.Instance.GetCollectedObjects();
Assert.IsEmpty(garbage);
}

private static IntPtr MakeAGarbage(out WeakReference shortWeak, out WeakReference longWeak)
{
PyLong obj = new PyLong(1024);
shortWeak = new WeakReference(obj);
longWeak = new WeakReference(obj, true);
return obj.Handle;
}

private static long CompareWithFinalizerOn(PyObject pyCollect, bool enbale)
Expand Down Expand Up @@ -191,62 +187,6 @@ public void SimpleTestMemory()
}
}

class MyPyObject : PyObject
{
public MyPyObject(IntPtr op) : base(op)
{
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
GC.SuppressFinalize(this);
throw new Exception("MyPyObject");
}
internal static void CreateMyPyObject(IntPtr op)
{
Runtime.Runtime.XIncref(op);
new MyPyObject(op);
}
}

[Test]
public void ErrorHandling()
{
bool called = false;
var errorMessage = "";
EventHandler<Finalizer.ErrorArgs> handleFunc = (sender, args) =>
{
called = true;
errorMessage = args.Error.Message;
};
Finalizer.Instance.Threshold = 1;
Finalizer.Instance.ErrorHandler += handleFunc;
try
{
WeakReference shortWeak;
WeakReference longWeak;
{
MakeAGarbage(out shortWeak, out longWeak);
var obj = (PyLong)longWeak.Target;
IntPtr handle = obj.Handle;
shortWeak = null;
longWeak = null;
MyPyObject.CreateMyPyObject(handle);
obj.Dispose();
obj = null;
}
FullGCCollect();
Finalizer.Instance.Collect();
Assert.IsTrue(called);
}
finally
{
Finalizer.Instance.ErrorHandler -= handleFunc;
}
Assert.AreEqual(errorMessage, "MyPyObject");
}

[Test]
public void ValidateRefCount()
{
Expand Down Expand Up @@ -279,36 +219,13 @@ public void ValidateRefCount()
}
}

private static IntPtr CreateStringGarbage()
{
PyString s1 = new PyString("test_string");
// s2 steal a reference from s1
PyString s2 = new PyString(s1.Handle);
return s1.Handle;
}

private static bool WaitForCollected(IntPtr op, int hash, int milliseconds)
{
var stopwatch = Stopwatch.StartNew();
do
{
var garbage = Finalizer.Instance.GetCollectedObjects();
foreach (var item in garbage)
{
// The validation is not 100% precise,
// but it's rare that two conditions satisfied but they're still not the same object.
if (item.Target.GetHashCode() != hash)
{
continue;
}
var obj = (IPyDisposable)item.Target;
if (obj.GetTrackedHandles().Contains(op))
{
return true;
}
}
} while (stopwatch.ElapsedMilliseconds < milliseconds);
return false;
}
}
}
7 changes: 7 additions & 0 deletions src/runtime/debughelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,12 @@ public static void PrintHexBytes(byte[] bytes)
Console.WriteLine();
}
}
}
}
9 changes: 2 additions & 7 deletions src/runtime/delegatemanager.cs
Original file line number Diff line number Diff line change
@@ -181,7 +181,7 @@ A possible alternate strategy would be to create custom subclasses
too "special" for this to work. It would be more work, so for now
the 80/20 rule applies :) */

public class Dispatcher : IPyDisposable
{
public IntPtr target;
public Type dtype;
@@ -202,7 +202,7 @@ public Dispatcher(IntPtr target, Type dtype)
return;
}
_finalized = true;
Finalizer.Instance.AddFinalizedObject(this);
}

public void Dispose()
Expand Down Expand Up @@ -276,11 +276,6 @@ public object TrueDispatch(ArrayList args)
Runtime.XDecref(op);
return result;
}

public IntPtr[] GetTrackedHandles()
{
return new IntPtr[] { target };
}
}


Expand Down
Loading
Toggle all file notes Toggle all file annotations