Skip to content

Valid finalizer #532

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ matrix:
- python: 2.7
env: &xplat-env
- BUILD_OPTS=--xplat
- NUNIT_PATH=~/.nuget/packages/nunit.consolerunner/3.*/tools/nunit3-console.exe
addons: &xplat-addons
apt:
sources:
Expand Down Expand Up @@ -45,7 +44,6 @@ matrix:
- python: 2.7
env: &classic-env
- BUILD_OPTS=
- NUNIT_PATH=./packages/NUnit.*/tools/nunit3-console.exe

- python: 3.3
env: *classic-env
Expand Down Expand Up @@ -97,7 +95,7 @@ install:

script:
- python -m pytest
- mono $NUNIT_PATH src/embed_tests/bin/Python.EmbeddingTest.dll
- mono src/embed_tests/bin/Python.EmbeddingTest.exe
- if [[ $BUILD_OPTS == --xplat ]]; then dotnet src/embed_tests/bin/netcoreapp2.0_publish/Python.EmbeddingTest.dll; fi

after_script:
Expand Down
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,26 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
## [unreleased][]

### Added
- Added tool for debugging floating bugs. Stable tests are executed in the loop. ~100 cycles is enough to pop up any bugs.
Usage: Python.EmbeddingTest.exe --loop --where="cat != Unstable"
- Added support for embedding python into dotnet core 2.0 (NetStandard 2.0)
- Added new build system (pythonnet.15.sln) based on dotnetcore-sdk/xplat(crossplatform msbuild).
Currently there two side-by-side build systems that produces the same output (net40) from the same sources.
After a some transition time, current (mono/ msbuild 14.0) build system will be removed.
- NUnit upgraded to 3.7 (eliminates travis-ci random bug)
- Python.EmbeddingTest (net40) now tested through built-in NUnitLite in Travis-CI. (Solves NUnit vs Mono stability problems.)
- NUnit upgraded to 3.8.1, Python.EmbeddingTest now executable with the NUnitLite self-tester.
- Added `clr.GetClrType` (#432, #433)
- Allowed passing `None` for nullable args (#460)
- Added keyword arguments based on C# syntax for calling CPython methods (#461)

### Changed

### Fixed

- Fixed memory leaks, caused by non-working PyObject, PythonException, DelecateManager->Dispatcher finalizers.
Set environment variable PYTHONNET_GC_DEBUG=1 to force full GC on each PythonEngine.Shutdown, this allows
to esily detect GC related bugs.
- Fixed secondary PythonEngine.Initialize call, all sensitive static variables now reseted.
This is a hidden bug. Once python cleaning up enough memory, objects from previous engine run becomes corrupted.
- Fixed Visual Studio 2017 compat (#434) for setup.py
- Fixed crash on exit of the Python interpreter if a python class
derived from a .NET class has a `__namespace__` or `__assembly__`
Expand Down
2 changes: 1 addition & 1 deletion NuGet.config
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="dot-net MyGet Feed" value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json" protocolVersion="3"/>
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="dot-net MyGet Feed" value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json" protocolVersion="3"/>
</packageSources>
</configuration>
2 changes: 1 addition & 1 deletion ci/appveyor_run_tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ else{
$PY = Get-Command python

# Can't use ".\build\*\Python.EmbeddingTest.dll". Missing framework files.
$CS_TESTS = ".\src\embed_tests\bin\Python.EmbeddingTest.dll"
$CS_TESTS = ".\src\embed_tests\bin\Python.EmbeddingTest.exe"
$RUNTIME_DIR = ".\src\runtime\bin\"

# Run python tests with C# coverage
Expand Down
36 changes: 35 additions & 1 deletion src/embed_tests/Program.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,49 @@
using System;

using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using NUnit.Common;

using NUnitLite;
using Python.Runtime;

namespace Python.EmbeddingTest
{
public class Program
{
public static int Main(string[] args)
{
if (args.Contains("--loop"))
{
args = args.Where(x => x != "--loop").ToArray();
int result;
int runNumber = 0;
string pathEnv = Environment.GetEnvironmentVariable("PATH");
do
{
Console.ForegroundColor = ConsoleColor.Green;
Console.Error.WriteLine($"----- Run = {++runNumber}, MemUsage = {Process.GetCurrentProcess().WorkingSet64 / 1024 / 1024} Mb -----");
Console.ForegroundColor = ConsoleColor.Gray;

result = new AutoRun(typeof(Program).Assembly).Execute(
args,
new ExtendedTextWrapper(Console.Out),
Console.In);

// Python does not see Environment.SetEnvironmentVariable changes.
// So we needs restore PATH variable in a pythonic way.
using (new PythonEngine())
{
dynamic os = PythonEngine.ImportModule("os");
os.environ["PATH"] = new PyString(pathEnv);
}
}
while (!Console.KeyAvailable);

return result;
}

return new AutoRun(typeof(Program).Assembly).Execute(
args,
new ExtendedTextWrapper(Console.Out),
Expand Down
6 changes: 3 additions & 3 deletions src/embed_tests/Python.EmbeddingTest.15.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<TargetFrameworks>net40;netcoreapp2.0</TargetFrameworks>
<Platforms>x64;x86</Platforms>
<Configurations>DebugMono;DebugMonoPY3;ReleaseMono;ReleaseMonoPY3;DebugWin;DebugWinPY3;ReleaseWin;ReleaseWinPY3</Configurations>
<OutputType Condition="'$(TargetFramework)' != 'net40' OR '$(PYTHONNET_VS_ENV)' == 'true'">Exe</OutputType>
<OutputType>Exe</OutputType>
<GenerateProgramFile>false</GenerateProgramFile>
<AssemblyName>Python.EmbeddingTest</AssemblyName>
<RootNamespace>Python.EmbeddingTest</RootNamespace>
Expand Down Expand Up @@ -76,10 +76,10 @@


<ItemGroup>
<PackageReference Include="NUnit" Version="3.7.1" />
<PackageReference Include="NUnit" Version="3.8.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
<PackageReference Include="NUnitLite" Version="3.7.2" />
<PackageReference Include="NUnitLite" Version="3.8.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.0'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
Expand Down
12 changes: 9 additions & 3 deletions src/embed_tests/Python.EmbeddingTest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{4165C59D-2822-499F-A6DB-EACA4C331EB5}</ProjectGuid>
<OutputType>Library</OutputType>
<OutputType>Exe</OutputType>
<AssemblyName>Python.EmbeddingTest</AssemblyName>
<RootNamespace>Python.EmbeddingTest</RootNamespace>
<DocumentationFile>bin\Python.EmbeddingTest.xml</DocumentationFile>
Expand Down Expand Up @@ -70,8 +70,11 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="nunit.framework, Version=3.7.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\..\packages\NUnit.3.7.1\lib\net40\nunit.framework.dll</HintPath>
<Reference Include="nunit.framework, Version=3.8.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\..\packages\NUnit.3.8.1\lib\net40\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="nunitlite">
<HintPath>..\..\packages\NUnitLite.3.8.1\lib\net40\nunitlite.dll</HintPath>
</Reference>
<Reference Include="System" />
</ItemGroup>
Expand All @@ -84,6 +87,7 @@
<Compile Include="pyimport.cs" />
<Compile Include="pyinitialize.cs" />
<Compile Include="pyrunstring.cs" />
<Compile Include="Program.cs" />
<Compile Include="TestConverter.cs" />
<Compile Include="TestCustomMarshal.cs" />
<Compile Include="TestExample.cs" />
Expand All @@ -102,6 +106,8 @@
<Compile Include="TestPyWith.cs" />
<Compile Include="TestRuntime.cs" />
<Compile Include="TestPyScope.cs" />
<Compile Include="TestFinalizer.cs" />
<Compile Include="TestsSuite.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\runtime\Python.Runtime.csproj">
Expand Down
1 change: 1 addition & 0 deletions src/embed_tests/TestExample.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

namespace Python.EmbeddingTest
{
[Category("Unstable")]
public class TestExample
{
[OneTimeSetUp]
Expand Down
130 changes: 130 additions & 0 deletions src/embed_tests/TestFinalizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using Python.Runtime;

namespace Python.EmbeddingTest
{
public class TestFinalizer
{
[OneTimeSetUp]
public void SetUp()
{
PythonEngine.Initialize();
}

[OneTimeTearDown]
public void Dispose()
{
PythonEngine.Shutdown();
}

[Test]
public void TestClrObjectFullRelease()
{
var gs = PythonEngine.BeginAllowThreads();

WeakReference weakRef;
try
{
var weakRefCreateTask = Task.Factory.StartNew(() =>
{
using (Py.GIL())
{
byte[] testObject = new byte[100];
var testObjectWeakReference = new WeakReference(testObject);

dynamic pyList = new PyList();
pyList.append(testObject);
return testObjectWeakReference;
}
});

weakRef = weakRefCreateTask.Result;
}
finally
{
PythonEngine.EndAllowThreads(gs);
}

// Triggering C# finalizer (PyList ref should be scheduled to decref).
GC.Collect();
GC.WaitForPendingFinalizers();

// Forcing dec reference thread to wakeup and decref PyList.
PythonEngine.EndAllowThreads(PythonEngine.BeginAllowThreads());
Thread.Sleep(200);
PythonEngine.CurrentRefDecrementer.WaitForPendingDecReferences();

// Now python free up GCHandle on CLRObject and subsequent GC should fully remove testObject.
GC.Collect();
GC.WaitForPendingFinalizers();

Assert.IsFalse(weakRef.IsAlive, "Clr object should be collected.");
}

[Test]
[Ignore("For debug only")]
public void TestExceptionMemoryLeak()
{
dynamic pymodule;
PyObject gc;
dynamic testmethod;

var ts = PythonEngine.BeginAllowThreads();
IntPtr pylock = PythonEngine.AcquireLock();

#if NETCOREAPP
const string s = "../../fixtures";
#else
const string s = "../fixtures";
#endif
string testPath = Path.Combine(TestContext.CurrentContext.TestDirectory, s);

IntPtr str = Runtime.Runtime.PyString_FromString(testPath);
IntPtr path = Runtime.Runtime.PySys_GetObject("path");
Runtime.Runtime.PyList_Append(path, str);

{
PyObject sys = PythonEngine.ImportModule("sys");
gc = PythonEngine.ImportModule("gc");

pymodule = PythonEngine.ImportModule("MemoryLeakTest.pyraise");
testmethod = pymodule.test_raise_exception;
}

PythonEngine.ReleaseLock(pylock);

double floatarg1 = 5.1f;
dynamic res = null;
{
for (int i = 1; i <= 10000000; i++)
{
using (Py.GIL())
{
try
{
res = testmethod(Py.kw("number", floatarg1), Py.kw("astring", "bbc"));
}
catch (Exception e)
{
if (i % 10000 == 0)
{
TestContext.Progress.WriteLine(e.Message);
}
}
}

if (i % 10000 == 0)
{
GC.Collect();
}
}
}

PythonEngine.EndAllowThreads(ts);
}
}
}
1 change: 1 addition & 0 deletions src/embed_tests/TestPyAnsiString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public void TestCtorPtr()
const string expected = "foo";

var t = new PyAnsiString(expected);
Runtime.Runtime.XIncref(t.Handle);
var actual = new PyAnsiString(t.Handle);

Assert.AreEqual(expected, actual.ToString());
Expand Down
1 change: 1 addition & 0 deletions src/embed_tests/TestPyLong.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public void TestCtorDouble()
public void TestCtorPtr()
{
var i = new PyLong(5);
Runtime.Runtime.XIncref(i.Handle);
var a = new PyLong(i.Handle);
Assert.AreEqual(5, a.ToInt32());
}
Expand Down
1 change: 1 addition & 0 deletions src/embed_tests/TestPyString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public void TestCtorPtr()
const string expected = "foo";

var t = new PyString(expected);
Runtime.Runtime.XIncref(t.Handle);
var actual = new PyString(t.Handle);

Assert.AreEqual(expected, actual.ToString());
Expand Down
Loading