Visitar URL original
Skip publishing sys.clr_data when BinaryFormatter is unavailable (#2282) by AtwoodTM · Pull Request #2749 · pythonnet/pythonnet · GitHub
Skip to content
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].

### Fixed

- When `BinaryFormatter` is unavailable, skip publishing `sys.clr_data` during
`Shutdown` so the next `Initialize` does not deserialize an empty capsule (#2282)

## 3.2.0 - 2026-09-29

Expand Down
5 changes: 5 additions & 0 deletions src/embed_tests/Python.EmbeddingTest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
<Compile Remove="NeedsReinit/*.cs" />
</ItemGroup>

<!-- net472 still stashes with BinaryFormatter. This test covers the Noop path. -->
<ItemGroup Condition="'$(TargetFramework)' == 'net472'">
<Compile Remove="StateSerialization/ShutdownStash.cs" />
</ItemGroup>

<PropertyGroup>
<DefineConstants>$(DefineConstants);$(ConfiguredConstants)</DefineConstants>
</PropertyGroup>
Expand Down
57 changes: 57 additions & 0 deletions src/embed_tests/StateSerialization/ShutdownStash.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;

using NUnit.Framework;

using Python.Runtime;

namespace Python.EmbeddingTest.StateSerialization;

/// <summary>
/// Regression for https://github.com/pythonnet/pythonnet/issues/2282.
/// When BinaryFormatter cannot run, Shutdown must not throw and must not
/// leave a sys.clr_data capsule behind.
/// </summary>
public class ShutdownStash
{
[Test]
public void ShutdownLeavesNoClrData()
{
if (!PythonEngine.IsInitialized)
{
PythonEngine.Initialize();
}

Assert.That(RuntimeData.CreateFormatter(), Is.InstanceOf<NoopFormatter>());

using (Py.GIL())
{
using var wrapped = new UriBuilder().ToPython();
Assert.That(RuntimeData.HasStashData(), Is.False);
}

try
{
PythonEngine.Shutdown();

using (Py.GIL())
{
Assert.That(RuntimeData.HasStashData(), Is.False);
}
}
finally
{
// Shutdown leaves the interpreter alive. A second Shutdown in this
// process then deallocates types from the previous runtime and
// trips MetaType.tp_dealloc. Finish the interpreter first so the
// suite, and GlobalTestsSetup, start from a new one.
if (Runtime.Runtime.Py_IsInitialized() != 0)
{
Runtime.Runtime.Py_Finalize();
}
if (!PythonEngine.IsInitialized)
{
PythonEngine.Initialize();
}
}
}
}
14 changes: 13 additions & 1 deletion src/runtime/StateSerialization/RuntimeData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ static void ClearCLRData ()

internal static void Stash()
{
IFormatter formatter = CreateFormatter();
// NoopFormatter.Serialize writes nothing. An empty sys.clr_data capsule
// makes the next Initialize call Deserialize and throw (#2282).
if (formatter is NoopFormatter)
{
if (HasStashData())
{
ClearCLRData();
ClearStash();
}
return;
}

var runtimeStorage = new PythonNetState
{
Metatype = MetaType.SaveRuntimeData(),
Expand All @@ -93,7 +106,6 @@ internal static void Stash()
SharedObjects = SaveRuntimeDataObjects(),
};

IFormatter formatter = CreateFormatter();
var ms = new MemoryStream();
formatter.Serialize(ms, runtimeStorage);

Expand Down