8000 delete target object from event handler collections when it has no mo… · pythonnet/pythonnet@9ace8dc · GitHub
[go: up one dir, main page]

Skip to content
< 8000 /div>

Commit 9ace8dc

Browse files
committed
delete target object from event handler collections when it has no more event handlers
fixes #1972
1 parent b5c222c commit 9ace8dc

File tree

3 files changed

+80
-0
lines changed

3 files changed

+80
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1313

1414
### Fixed
1515

16+
- Fixed objects leaking when Python attached event handlers to them even if they were later removed
17+
1618

1719
## [3.0.0](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.0) - 2022-09-29
1820

src/embed_tests/Events.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
example.Dispose()
42+
del example
43+
44+
gc.collect()
45+
");
46+
Runtime.Runtime.TryCollectingGarbage(10);
47+
Assert.AreEqual(0, ClassWithEventHandler.alive);
48+
}
49+
}
50+
51+
public class ClassWithEventHandler : IDisposable
52+
{
53+
internal static int alive;
54+
55+
public event EventHandler LeakEvent;
56+
private Array arr; // dummy array to exacerbate memory leak
57+
58+
public ClassWithEventHandler()
59+
{
60+
Interlocked.Increment(ref alive);
61+
this.arr = new int[800];
62+
}
63+
64+
public void Dispose()
65+
{
66+
this.LeakEvent = null;
67+
Debug.WriteLine(GC.GetTotalMemory(true));
68+
}
69+
70+
~ClassWithEventHandler()
71+
{
72+
Interlocked.Decrement(ref alive);
73+
}
74+
}

src/runtime/Util/EventHandlerCollection.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ internal bool RemoveEventHandler(BorrowedReference target, BorrowedReference han
9999
continue;
100100
}
101101
list.RemoveAt(i);
102+
if (list.Count == 0)
103+
{
104+
Remove(key);
105+
}
102106
return true;
103107
}
104108

0 commit comments

Comments
 (0)
0