From 0958ef5b54a9b54a89f70f7c4da978580c1ec1d8 Mon Sep 17 00:00:00 2001 From: Tom Atwood Date: Tue, 6 Oct 2026 04:54:22 -0400 Subject: [PATCH 1/2] Skip publishing sys.clr_data when BinaryFormatter is unavailable (#2282) NoopFormatter serializes nothing. An empty capsule makes the next Initialize call Deserialize and throw. Leave sys.clr_data unset. --- AUTHORS.md | 1 + CHANGELOG.md | 2 + src/embed_tests/Python.EmbeddingTest.csproj | 5 ++ .../StateSerialization/ShutdownStash.cs | 57 +++++++++++++++++++ src/runtime/StateSerialization/RuntimeData.cs | 14 ++++- 5 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 src/embed_tests/StateSerialization/ShutdownStash.cs diff --git a/AUTHORS.md b/AUTHORS.md index 723520f3c..8ace02e93 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -64,6 +64,7 @@ - Sean Freitag ([@cowboygneox](https://github.com/cowboygneox)) - Serge Weinstock ([@sweinst](https://github.com/sweinst)) - Simon Mourier ([@smourier](https://github.com/smourier)) +- Tom Atwood ([@AtwoodTM](https://github.com/AtwoodTM)) - Tom Minka ([@tminka](https://github.com/tminka)) - Viktoria Kovescses ([@vkovec](https://github.com/vkovec)) - Ville M. Vainio ([@vivainio](https://github.com/vivainio)) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02b4a7cf2..714052893 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index 8697e7aaf..eb23a755a 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -19,6 +19,11 @@ + + + + + $(DefineConstants);$(ConfiguredConstants) diff --git a/src/embed_tests/StateSerialization/ShutdownStash.cs b/src/embed_tests/StateSerialization/ShutdownStash.cs new file mode 100644 index 000000000..bd61a951b --- /dev/null +++ b/src/embed_tests/StateSerialization/ShutdownStash.cs @@ -0,0 +1,57 @@ +using System; + +using NUnit.Framework; + +using Python.Runtime; + +namespace Python.EmbeddingTest.StateSerialization; + +/// +/// 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. +/// +public class ShutdownStash +{ + [Test] + public void ShutdownLeavesNoClrData() + { + if (!PythonEngine.IsInitialized) + { + PythonEngine.Initialize(); + } + + Assert.That(RuntimeData.CreateFormatter(), Is.InstanceOf()); + + 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(); + } + } + } +} diff --git a/src/runtime/StateSerialization/RuntimeData.cs b/src/runtime/StateSerialization/RuntimeData.cs index c3e5d2287..e6e0a8f9e 100644 --- a/src/runtime/StateSerialization/RuntimeData.cs +++ b/src/runtime/StateSerialization/RuntimeData.cs @@ -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(), @@ -93,7 +106,6 @@ internal static void Stash() SharedObjects = SaveRuntimeDataObjects(), }; - IFormatter formatter = CreateFormatter(); var ms = new MemoryStream(); formatter.Serialize(ms, runtimeStorage); From 06f9c1de8158ef96621875ea7e44456f1b072a98 Mon Sep 17 00:00:00 2001 From: Tom Atwood Date: Tue, 6 Oct 2026 04:55:40 -0400 Subject: [PATCH 2/2] Leave AUTHORS.md unchanged A single fix does not need a new credits entry. --- AUTHORS.md | 1 - 1 file changed, 1 deletion(-) diff --git a/AUTHORS.md b/AUTHORS.md index 8ace02e93..723520f3c 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -64,7 +64,6 @@ - Sean Freitag ([@cowboygneox](https://github.com/cowboygneox)) - Serge Weinstock ([@sweinst](https://github.com/sweinst)) - Simon Mourier ([@smourier](https://github.com/smourier)) -- Tom Atwood ([@AtwoodTM](https://github.com/AtwoodTM)) - Tom Minka ([@tminka](https://github.com/tminka)) - Viktoria Kovescses ([@vkovec](https://github.com/vkovec)) - Ville M. Vainio ([@vivainio](https://github.com/vivainio))