| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -13,6 +13,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. | |||
| 13 | 13 | ||
| 14 | 14 | ### Fixed | |
| 15 | 15 | ||
| 16 | + - Fixed objects leaking when Python attached event handlers to them even if they were later removed | ||
| 17 | + | ||
| 16 | 18 | ||
| 17 | 19 | ## [3.0.0](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.0) - 2022-09-29 | |
| 18 | 20 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,67 @@ | |||
| 1 | + using System; | ||
| 2 | + using System.Diagnostics; | ||
| 3 | + using System.Threading; | ||
| 4 | + | ||
| 5 | + using NUnit.Framework; | ||
| 6 | + | ||
| 7 | + using Python.Runtime; | ||
| 8 | + | ||
| 9 | + namespace Python.EmbeddingTest; | ||
| 10 | + | ||
| 11 | + public class Events | ||
| 12 | + { | ||
| 13 | + [OneTimeSetUp] | ||
| 14 | + public void SetUp() | ||
| 15 | + { | ||
| 16 | + PythonEngine.Initialize(); | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + [OneTimeTearDown] | ||
| 20 | + public void Dispose() | ||
| 21 | + { | ||
| 22 | + PythonEngine.Shutdown(); | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + [Test] | ||
| 26 | + public void UsingDoesNotLeak() | ||
| 27 | + { | ||
| 28 | + using var scope = Py.CreateScope(); | ||
| 29 | + scope.Exec(@" | ||
| 30 | + import gc | ||
| 31 | + | ||
| 32 | + from Python.EmbeddingTest import ClassWithEventHandler | ||
| 33 | + | ||
| 34 | + def event_handler(): | ||
| 35 | + pass | ||
| 36 | + | ||
| 37 | + for _ in range(2000): | ||
| 38 | + example = ClassWithEventHandler() | ||
| 39 | + example.LeakEvent += event_handler | ||
| 40 | + example.LeakEvent -= event_handler | ||
| 41 | + del example | ||
| 42 | + | ||
| 43 | + gc.collect() | ||
| 44 | + "); | ||
| 45 | + Runtime.Runtime.TryCollectingGarbage(10); | ||
| 46 | + Assert.AreEqual(0, ClassWithEventHandler.alive); | ||
| 47 | + } | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + public class ClassWithEventHandler | ||
| 51 | + { | ||
| 52 | + internal static int alive; | ||
| 53 | + | ||
| 54 | + public event EventHandler LeakEvent; | ||
| 55 | + private Array arr; // dummy array to exacerbate memory leak | ||
| 56 | + | ||
| 57 | + public ClassWithEventHandler() | ||
| 58 | + { | ||
| 59 | + Interlocked.Increment(ref alive); | ||
| 60 | + this.arr = new int[800]; | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + ~ClassWithEventHandler() | ||
| 64 | + { | ||
| 65 | + Interlocked.Decrement(ref alive); | ||
| 66 | + } | ||
| 67 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -364,6 +364,8 @@ struct PendingFinalization | |||
| 364 | 364 | { | |
| 365 | 365 | public IntPtr PyObj; | |
| 366 | 366 | public BorrowedReference Ref => new(PyObj); | |
| 367 | + public ManagedType? Managed => ManagedType.GetManagedObject(Ref); | ||
| 368 | + public nint RefCount => Runtime.Refcount(Ref); | ||
| 367 | 369 | public int RuntimeRun; | |
| 368 | 370 | #if TRACE_ALLOC | |
| 369 | 371 | public string StackTrace; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1051,9 +1051,20 @@ public PyList Dir() | |||
| 1051 | 1051 | return Runtime.GetManagedString(strval.BorrowOrThrow()); | |
| 1052 | 1052 | } | |
| 1053 | 1053 | ||
| 1054 | - string? DebuggerDisplay => DebugUtil.HaveInterpreterLock() | ||
| 1055 | - ? this.ToString() | ||
| 1056 | - : $"pyobj at 0x{this.rawPtr:X} (get Py.GIL to see more info)"; | ||
| 1054 | + ManagedType? InternalManagedObject => ManagedType.GetManagedObject(this.Reference); | ||
| 1055 | + | ||
| 1056 | + string? DebuggerDisplay | ||
| 1057 | + { | ||
| 1058 | + get | ||
| 1059 | + { | ||
| 1060 | + if (DebugUtil.HaveInterpreterLock()) | ||
| 1061 | + return this.ToString(); | ||
| 1062 | + var obj = this.InternalManagedObject; | ||
| 1063 | + return obj is { } | ||
| 1064 | + ? obj.ToString() | ||
| 1065 | + : $"pyobj at 0x{this.rawPtr:X} (get Py.GIL to see more info)"; | ||
| 1066 | + } | ||
| 1067 | + } | ||
| 1057 | 1068 | ||
| 1058 | 1069 | ||
| 1059 | 1070 | /// <summary> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,7 @@ | |||
| 1 | 1 | using System; | |
| 2 | 2 | using System.Collections.Generic; | |
| 3 | + using System.Globalization; | ||
| 4 | + using System.Linq; | ||
| 3 | 5 | using System.Reflection; | |
| 4 | 6 | using System.Reflection.Emit; | |
| 5 | 7 | using System.Threading; | |
@@ -17,13 +19,15 @@ internal class CodeGenerator | |||
| 17 | 19 | private readonly AssemblyBuilder aBuilder; | |
| 18 | 20 | private readonly ModuleBuilder mBuilder; | |
| 19 | 21 | ||
| 22 | + const string NamePrefix = "__Python_Runtime_Generated_"; | ||
| 23 | + | ||
| 20 | 24 | internal CodeGenerator() | |
| 21 | 25 | { | |
| 22 | - var aname = new AssemblyName { Name = "__CodeGenerator_Assembly" }; | ||
| 26 | + var aname = new AssemblyName { Name = GetUniqueAssemblyName(NamePrefix + "Assembly") }; | ||
| 23 | 27 | var aa = AssemblyBuilderAccess.Run; | |
| 24 | 28 | ||
| 25 | 29 | aBuilder = Thread.GetDomain().DefineDynamicAssembly(aname, aa); | |
| 26 | - mBuilder = aBuilder.DefineDynamicModule("__CodeGenerator_Module"); | ||
| 30 | + mBuilder = aBuilder.DefineDynamicModule(NamePrefix + "Module"); | ||
| 27 | 31 | } | |
| 28 | 32 | ||
| 29 | 33 | /// <summary> | |
@@ -77,5 +81,20 @@ internal static void GenerateMarshalByRefsBack(ILGenerator il, IReadOnlyList<Typ | |||
| 77 | 81 | } | |
| 78 | 82 | } | |
| 79 | 83 | } | |
| 84 | + | ||
| 85 | + static string GetUniqueAssemblyName(string name) | ||
| 86 | + { | ||
| 87 | + var taken = new HashSet<string>(AppDomain.CurrentDomain | ||
| 88 | + .GetAssemblies() | ||
| 89 | + .Select(a => a.GetName().Name)); | ||
| 90 | + for (int i = 0; i < int.MaxValue; i++) | ||
| 91 | + { | ||
| 92 | + string candidate = name + i.ToString(CultureInfo.InvariantCulture); | ||
| 93 | + if (!taken.Contains(candidate)) | ||
| 94 | + return candidate; | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | + throw new NotSupportedException("Too many assemblies"); | ||
| 98 | + } | ||
| 80 | 99 | } | |
| 81 | 100 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -99,6 +99,10 @@ internal bool RemoveEventHandler(BorrowedReference target, BorrowedReference han | |||
| 99 | 99 | continue; | |
| 100 | 100 | } | |
| 101 | 101 | list.RemoveAt(i); | |
| 102 | + if (list.Count == 0) | ||
| 103 | + { | ||
| 104 | + Remove(key); | ||
| 105 | + } | ||
| 102 | 106 | return true; | |
| 103 | 107 | } | |
| 104 | 108 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments