Skip to content

Commit 55abd29

Browse files
authored
Merge pull request #1074 from losttech/bugs/1073
Track Runtime run number
2 parents 3328d7d + 47b3913 commit 55abd29

15 files changed

+273
-174
lines changed

src/embed_tests/Codecs.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -421,13 +421,18 @@ public PyObject TryEncode(object value)
421421
}
422422
}
423423

424-
class InstancelessExceptionDecoder : IPyObjectDecoder
424+
class InstancelessExceptionDecoder : IPyObjectDecoder, IDisposable
425425
{
426426
readonly PyObject PyErr = Py.Import("clr.interop").GetAttr("PyErr");
427427

428428
public bool CanDecode(PyType objectType, Type targetType)
429429
=> PythonReferenceComparer.Instance.Equals(PyErr, objectType);
430430

431+
public void Dispose()
432+
{
433+
PyErr.Dispose();
434+
}
435+
431436
public bool TryDecode<T>(PyObject pyObj, out T value)
432437
{
433438
if (pyObj.HasAttr("value"))

src/embed_tests/GlobalTestsSetup.cs

+16
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,22 @@ namespace Python.EmbeddingTest
99
[SetUpFixture]
1010
public partial class GlobalTestsSetup
1111
{
12+
[OneTimeSetUp]
13+
public void GlobalSetup()
14+
{
15+
Finalizer.Instance.ErrorHandler += FinalizerErrorHandler;
16+
}
17+
18+
private void FinalizerErrorHandler(object sender, Finalizer.ErrorArgs e)
19+
{
20+
if (e.Error is RuntimeShutdownException)
21+
{
22+
// allow objects to leak after the python runtime run
23+
// they were created in is gone
24+
e.Handled = true;
25+
}
26+
}
27+
1228
[OneTimeTearDown]
1329
public void FinalCleanup()
1430
{

src/embed_tests/Inheritance.cs

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public void SetUp()
2424
[OneTimeTearDown]
2525
public void Dispose()
2626
{
27+
ExtraBaseTypeProvider.ExtraBase.Dispose();
2728
PythonEngine.Shutdown();
2829
}
2930

src/embed_tests/TestDomainReload.cs

-110
Original file line numberDiff line numberDiff line change
@@ -179,116 +179,6 @@ public static void CrossDomainObject()
179179

180180
#endregion
181181

182-
#region Tempary tests
183-
184-
// https://github.com/pythonnet/pythonnet/pull/1074#issuecomment-596139665
185-
[Test]
186-
public void CrossReleaseBuiltinType()
187-
{
188-
void ExecTest()
189-
{
190-
try
191-
{
192-
PythonEngine.Initialize();
193-
var numRef = CreateNumReference();
194-
Assert.True(numRef.IsAlive);
195-
PythonEngine.Shutdown(); // <- "run" 1 ends
196-
PythonEngine.Initialize(); // <- "run" 2 starts
197-
198-
GC.Collect();
199-
GC.WaitForPendingFinalizers(); // <- this will put former `num` into Finalizer queue
200-
Finalizer.Instance.Collect();
201-
// ^- this will call PyObject.Dispose, which will call XDecref on `num.Handle`,
202-
// but Python interpreter from "run" 1 is long gone, so it will corrupt memory instead.
203-
Assert.False(numRef.IsAlive);
204-
}
205-
finally
206-
{
207-
PythonEngine.Shutdown();
208-
}
209-
}
210-
211-
var errorArgs = new List<Finalizer.ErrorArgs>();
212-
void ErrorHandler(object sender, Finalizer.ErrorArgs e)
213-
{
214-
errorArgs.Add(e);
215-
}
216-
Finalizer.Instance.ErrorHandler += ErrorHandler;
217-
try
218-
{
219-
for (int i = 0; i < 10; i++)
220-
{
221-
ExecTest();
222-
}
223-
}
224-
finally
225-
{
226-
Finalizer.Instance.ErrorHandler -= ErrorHandler;
227-
}
228-
Assert.AreEqual(errorArgs.Count, 0);
229-
}
230-
231-
[Test]
232-
public void CrossReleaseCustomType()
233-
{
234-
void ExecTest()
235-
{
236-
try
237-
{
238-
PythonEngine.Initialize();
239-
var objRef = CreateConcreateObject();
240-
Assert.True(objRef.IsAlive);
241-
PythonEngine.Shutdown(); // <- "run" 1 ends
242-
PythonEngine.Initialize(); // <- "run" 2 starts
243-
GC.Collect();
244-
GC.WaitForPendingFinalizers();
245-
Finalizer.Instance.Collect();
246-
Assert.False(objRef.IsAlive);
247-
}
248-
finally
249-
{
250-
PythonEngine.Shutdown();
251-
}
252-
}
253-
254-
var errorArgs = new List<Finalizer.ErrorArgs>();
255-
void ErrorHandler(object sender, Finalizer.ErrorArgs e)
256-
{
257-
errorArgs.Add(e);
258-
}
259-
Finalizer.Instance.ErrorHandler += ErrorHandler;
260-
try
261-
{
262-
for (int i = 0; i < 10; i++)
263-
{
264-
ExecTest();
265-
}
266-
}
267-
finally
268-
{
269-
Finalizer.Instance.ErrorHandler -= ErrorHandler;
270-
}
271-
Assert.AreEqual(errorArgs.Count, 0);
272-
}
273-
274-
private static WeakReference CreateNumReference()
275-
{
276-
var num = 3216757418.ToPython();
277-
Assert.AreEqual(num.Refcount, 1);
278-
WeakReference numRef = new WeakReference(num, false);
279-
return numRef;
280-
}
281-
282-
private static WeakReference CreateConcreateObject()
283-
{
284-
var obj = new Domain.MyClass().ToPython();
285-
Assert.AreEqual(obj.Refcount, 1);
286-
WeakReference numRef = new WeakReference(obj, false);
287-
return numRef;
288-
}
289-
290-
#endregion Tempary tests
291-
292182
/// <summary>
293183
/// This is a magic incantation required to run code in an application
294184
/// domain other than the current one.

src/embed_tests/pyinitialize.cs

+12-4
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ public static void LoadSpecificArgs()
4040
{
4141
using (var argv = new PyList(Runtime.Runtime.PySys_GetObject("argv")))
4242
{
43-
Assert.AreEqual(args[0], argv[0].ToString());
44-
Assert.AreEqual(args[1], argv[1].ToString());
43+
using var v0 = argv[0];
44+
using var v1 = argv[1];
45+
Assert.AreEqual(args[0], v0.ToString());
46+
Assert.AreEqual(args[1], v1.ToString());
4547
}
4648
}
4749
}
@@ -54,12 +56,16 @@ public void ImportClassShutdownRefcount()
5456

5557
PyObject ns = Py.Import(typeof(ImportClassShutdownRefcountClass).Namespace);
5658
PyObject cls = ns.GetAttr(nameof(ImportClassShutdownRefcountClass));
59+
BorrowedReference clsRef = cls.Reference;
60+
#pragma warning disable CS0618 // Type or member is obsolete
61+
cls.Leak();
62+
#pragma warning restore CS0618 // Type or member is obsolete
5763
ns.Dispose();
5864

59-
Assert.Less(cls.Refcount, 256);
65+
Assert.Less(Runtime.Runtime.Refcount(clsRef), 256);
6066

6167
PythonEngine.Shutdown();
62-
Assert.Greater(cls.Refcount, 0);
68+
Assert.Greater(Runtime.Runtime.Refcount(clsRef), 0);
6369
}
6470

6571
/// <summary>
@@ -176,6 +182,7 @@ public static void TestRunExitFuncs()
176182
{
177183
Assert.Fail(msg);
178184
}
185+
PythonEngine.InteropConfiguration = InteropConfiguration.MakeDefault();
179186
return;
180187
}
181188
bool called = false;
@@ -187,6 +194,7 @@ public static void TestRunExitFuncs()
187194
atexit.Dispose();
188195
Runtime.Runtime.Shutdown();
189196
Assert.True(called);
197+
PythonEngine.InteropConfiguration = InteropConfiguration.MakeDefault();
190198
}
191199
}
192200

src/runtime/Codecs/DecoderGroup.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Python.Runtime.Codecs
88
/// <summary>
99
/// Represents a group of <see cref="IPyObjectDecoder"/>s. Useful to group them by priority.
1010
/// </summary>
11-
public sealed class DecoderGroup: IPyObjectDecoder, IEnumerable<IPyObjectDecoder>
11+
public sealed class DecoderGroup: IPyObjectDecoder, IEnumerable<IPyObjectDecoder>, IDisposable
1212
{
1313
readonly List<IPyObjectDecoder> decoders = new List<IPyObjectDecoder>();
1414

@@ -46,6 +46,15 @@ public bool TryDecode<T>(PyObject pyObj, out T value)
4646
/// <inheritdoc />
4747
public IEnumerator<IPyObjectDecoder> GetEnumerator() => this.decoders.GetEnumerator();
4848
IEnumerator IEnumerable.GetEnumerator() => this.decoders.GetEnumerator();
49+
50+
public void Dispose()
51+
{
52+
foreach (var decoder in this.decoders.OfType<IDisposable>())
53+
{
54+
decoder.Dispose();
55+
}
56+
this.decoders.Clear();
57+
}
4958
}
5059

5160
public static class DecoderGroupExtensions

src/runtime/Codecs/EncoderGroup.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Python.Runtime.Codecs
88
/// <summary>
99
/// Represents a group of <see cref="IPyObjectDecoder"/>s. Useful to group them by priority.
1010
/// </summary>
11-
public sealed class EncoderGroup: IPyObjectEncoder, IEnumerable<IPyObjectEncoder>
11+
public sealed class EncoderGroup: IPyObjectEncoder, IEnumerable<IPyObjectEncoder>, IDisposable
1212
{
1313
readonly List<IPyObjectEncoder> encoders = new List<IPyObjectEncoder>();
1414

@@ -47,6 +47,15 @@ public PyObject TryEncode(object value)
4747
/// <inheritdoc />
4848
public IEnumerator<IPyObjectEncoder> GetEnumerator() => this.encoders.GetEnumerator();
4949
IEnumerator IEnumerable.GetEnumerator() => this.encoders.GetEnumerator();
50+
51+
public void Dispose()
52+
{
53+
foreach (var encoder in this.encoders.OfType<IDisposable>())
54+
{
55+
encoder.Dispose();
56+
}
57+
this.encoders.Clear();
58+
}
5059
}
5160

5261
public static class EncoderGroupExtensions

src/runtime/InteropConfiguration.cs

+11-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ namespace Python.Runtime
22
{
33
using System;
44
using System.Collections.Generic;
5+
using System.Linq;
56

67
using Python.Runtime.Mixins;
78

8-
public sealed class InteropConfiguration
9+
public sealed class InteropConfiguration: IDisposable
910
{
1011
internal readonly PythonBaseTypeProviderGroup pythonBaseTypeProviders
1112
= new PythonBaseTypeProviderGroup();
@@ -24,5 +25,14 @@ public static InteropConfiguration MakeDefault()
2425
},
2526
};
2627
}
28+
29+
public void Dispose()
30+
{
31+
foreach (var provider in PythonBaseTypeProviders.OfType<IDisposable>())
32+
{
33+
provider.Dispose();
34+
}
35+
PythonBaseTypeProviders.Clear();
36+
}
2737
}
2838
}

src/runtime/Mixins/CollectionMixinsProvider.cs

+9-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace Python.Runtime.Mixins
66
{
7-
class CollectionMixinsProvider : IPythonBaseTypeProvider
7+
class CollectionMixinsProvider : IPythonBaseTypeProvider, IDisposable
88
{
99
readonly Lazy<PyObject> mixinsModule;
1010
public CollectionMixinsProvider(Lazy<PyObject> mixinsModule)
@@ -86,5 +86,13 @@ static Type[] NewInterfaces(Type type)
8686

8787
static Type GetDefinition(Type type)
8888
=> type.IsGenericType ? type.GetGenericTypeDefinition() : type;
89+
90+
public void Dispose()
91+
{
92+
if (this.mixinsModule.IsValueCreated)
93+
{
94+
this.mixinsModule.Value.Dispose();
95+
}
96+
}
8997
}
9098
}

src/runtime/converterextensions.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,8 @@ internal static void Reset()
166166
{
167167
clrToPython.Clear();
168168
pythonToClr.Clear();
169-
encoders.Clear();
170-
decoders.Clear();
169+
encoders.Dispose();
170+
decoders.Dispose();
171171
}
172172
}
173173

0 commit comments

Comments
 (0)