From b9de39f4228574ed914b57678ccfe7e4f75e115b Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 29 Sep 2026 12:52:50 +0200 Subject: [PATCH 1/6] Back to dev --- CHANGELOG.md | 9 +++++++++ version.txt | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 737472caa..02b4a7cf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ project adheres to [Semantic Versioning][]. This document follows the conventions laid out in [Keep a CHANGELOG][]. +## Unreleased + +### Added + +### Changed + +### Fixed + + ## 3.2.0 - 2026-09-29 ### Added diff --git a/version.txt b/version.txt index 944880fa1..6d7d65910 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.2.0 +3.3.0-dev From deca5296870c13272e7ecf7ad367ce0a8441b9ab Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 29 Sep 2026 13:58:58 +0200 Subject: [PATCH 2/6] Document MONO_THREADS_SUSPEND and use throughout CI (#2747) --- .github/workflows/main.yml | 6 +++--- doc/source/threading.rst | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bae02377f..146acadde 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -52,6 +52,8 @@ jobs: env: NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages + # Apply to every Mono host, including pytest and the .NET Python test runner. + MONO_THREADS_SUSPEND: preemptive # https://github.com/mono/mono/issues/21466 steps: - name: Checkout code uses: actions/checkout@v7 @@ -84,8 +86,6 @@ jobs: - name: Embedding tests (Mono/.NET Framework) if: always() run: dotnet test --runtime any-${{ matrix.os.platform }} --framework net472 --logger "console;verbosity=detailed" src/embed_tests/ - env: - MONO_THREADS_SUSPEND: preemptive # https://github.com/mono/mono/issues/21466 - name: Embedding tests (.NET Core) if: always() @@ -97,7 +97,7 @@ jobs: - name: Python Tests (Mono) if: always() - run: pytest --runtime mono + run: uv run pytest --runtime mono -v -o faulthandler_timeout=60 - name: Python Tests (.NET Framework) if: ${{ matrix.os.category == 'windows' }} diff --git a/doc/source/threading.rst b/doc/source/threading.rst index 02f7bc91a..4d25ac9f0 100644 --- a/doc/source/threading.rst +++ b/doc/source/threading.rst @@ -93,6 +93,20 @@ Forgetting this is the most common pythonnet threading bug. Symptoms range from immediate segfaults to subtle refcount corruption that crashes much later. +Mono thread suspension +---------------------- + +On Linux and macOS, Mono's cooperative/hybrid thread suspension can hang +when Python and managed threads interact. If affected, set +``MONO_THREADS_SUSPEND=preemptive`` before starting the process, as described +in `mono/mono#21466 `_. +CI uses this workaround for both embedding and Python-hosted tests. + +To run the Mono Python tests with per-test output and a Python thread +stack dump if a test takes longer than 60 seconds:: + + MONO_THREADS_SUSPEND=preemptive uv run pytest --runtime mono -v -o faulthandler_timeout=60 + Reference counting and finalizers --------------------------------- From 5aa8111f8470bc985168d7c5f882ede9e7522dbe Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 6 Oct 2026 13:56:05 +0200 Subject: [PATCH 3/6] Fix shutdown regression with BinaryFormatter (#2750) The `ConcurrentDictionary` fields that were introduced to support free-threaded Python builds are not serializable and thus need to be rebuilt when the runtime data is unstashed. --- CHANGELOG.md | 2 + .../StateSerialization/ModuleSerialization.cs | 68 +++++++++++++++++++ src/runtime/Types/ModuleObject.cs | 12 +++- 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 src/embed_tests/StateSerialization/ModuleSerialization.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 02b4a7cf2..10bb20697 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Fixed +- Fix shutdown serialization of CLR namespace modules on .NET 8 while preserving + namespace name tracking across runtime restarts. ## 3.2.0 - 2026-09-29 diff --git a/src/embed_tests/StateSerialization/ModuleSerialization.cs b/src/embed_tests/StateSerialization/ModuleSerialization.cs new file mode 100644 index 000000000..80e010a45 --- /dev/null +++ b/src/embed_tests/StateSerialization/ModuleSerialization.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.IO; +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest.StateSerialization; + +[NonParallelizable] +public class ModuleSerialization +{ + [TestCase(false)] + [TestCase(true)] + public void NamespaceModuleRoundtrip(bool loadNames) + { + var formatter = RuntimeData.CreateFormatter(); + if (formatter is NoopFormatter) + { + Assert.Inconclusive("NoopFormatter in use, cannot perform serialization test."); + } + + using var gil = Py.GIL(); + using var pyModule = ModuleObject.Create("System").MoveToPyObject(); + var module = (ModuleObject)ManagedType.GetManagedObject(pyModule)!; + using (var int32 = module.GetAttribute("Int32", true)) + { + Assert.That(int32.IsNull(), Is.False); + } + + string[] names = null; + if (loadNames) + { + using var all = pyModule.GetAttr("__all__"); + names = all.As(); + Assert.That(names, Does.Contain("Int32")); + } + + for (int i = 0; i < 2; i++) + { + var context = module.Save(pyModule); + using var buffer = new MemoryStream(); + formatter.Serialize(buffer, new object[] { module, context }); + buffer.Position = 0; + var restored = (object[])formatter.Deserialize(buffer); + ExtensionType.tp_clear(pyModule); + module = (ModuleObject)restored[0]; + module.Load(pyModule, (Dictionary)restored[1]); + + using (var int32 = module.GetAttribute("Int32", true)) + { + Assert.That(int32.IsNull(), Is.False); + } + using (var collections = module.GetAttribute("Collections", true)) + { + Assert.That(collections.IsNull(), Is.False); + } + + using var all = pyModule.GetAttr("__all__"); + var restoredNames = all.As(); + Assert.That(restoredNames, Does.Contain("Int32")); + Assert.That(restoredNames, Is.Unique); + if (names is not null) + { + Assert.That(restoredNames, Is.EquivalentTo(names)); + } + names = restoredNames; + } + } +} diff --git a/src/runtime/Types/ModuleObject.cs b/src/runtime/Types/ModuleObject.cs index c9508aeed..3e020e563 100644 --- a/src/runtime/Types/ModuleObject.cs +++ b/src/runtime/Types/ModuleObject.cs @@ -14,13 +14,15 @@ namespace Python.Runtime [Serializable] internal class ModuleObject : ExtensionType { - private readonly ConcurrentDictionary cache = new(); + [NonSerialized] + private ConcurrentDictionary cache = new(); internal string moduleName; internal PyDict dict; protected string _namespace; private readonly PyList __all__ = new (); - private readonly ConcurrentDictionary allNames = new(); + [NonSerialized] + private ConcurrentDictionary allNames = new(); // Attributes to be set on the module according to PEP302 and 451 // by the import machinery. @@ -378,7 +380,8 @@ public static int tp_traverse(BorrowedReference ob, IntPtr visit, IntPtr arg) protected override Dictionary? OnSave(BorrowedReference ob) { - var context = base.OnSave(ob); + var context = base.OnSave(ob) ?? new(); + context[nameof(allNames)] = allNames.Keys.ToArray(); System.Diagnostics.Debug.Assert(dict == GetObjectDict(ob)); // destroy the cache(s) foreach (var pair in cache) @@ -402,6 +405,9 @@ public static int tp_traverse(BorrowedReference ob, IntPtr visit, IntPtr arg) protected override void OnLoad(BorrowedReference ob, Dictionary? context) { + cache = new(); + allNames = new(((string[])context![nameof(allNames)]!) + .Select(name => new KeyValuePair(name, 0))); base.OnLoad(ob, context); SetObjectDict(ob, new NewReference(dict).Steal()); } From 47b2b2be4d739693740ccf32822089012627347b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:04:03 +0200 Subject: [PATCH 4/6] Bump urllib3 from 2.7.0 to 2.8.0 in the uv group across 1 directory (#2751) Bumps the uv group with 1 update in the / directory: [urllib3](https://github.com/urllib3/urllib3). Updates `urllib3` from 2.7.0 to 2.8.0 - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.7.0...2.8.0) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.8.0 dependency-type: indirect dependency-group: uv ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 0081894af..593345c81 100644 --- a/uv.lock +++ b/uv.lock @@ -926,9 +926,9 @@ wheels = [ [[package]] name = "urllib3" -version = "2.7.0" +version = "2.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, + { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, ] From c678e2c3c251f21582dd102b5de84a3d35eb189f Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 8 Oct 2026 14:22:42 +0200 Subject: [PATCH 5/6] Release 3.2.1 --- CHANGELOG.md | 8 ++------ version.txt | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10bb20697..250cd7f7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,16 +5,12 @@ project adheres to [Semantic Versioning][]. This document follows the conventions laid out in [Keep a CHANGELOG][]. -## Unreleased - -### Added - -### Changed +## 3.2.1 - 2026-10-08 ### Fixed - Fix shutdown serialization of CLR namespace modules on .NET 8 while preserving - namespace name tracking across runtime restarts. + namespace name tracking across runtime restarts (#2750) ## 3.2.0 - 2026-09-29 diff --git a/version.txt b/version.txt index 6d7d65910..e4604e3af 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.3.0-dev +3.2.1 From 89170a4aedc9d68fb65269785220bdee7b944def Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 8 Oct 2026 14:25:03 +0200 Subject: [PATCH 6/6] Back to dev --- CHANGELOG.md | 10 ++++++++++ version.txt | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 250cd7f7e..561b8a86d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ project adheres to [Semantic Versioning][]. This document follows the conventions laid out in [Keep a CHANGELOG][]. +## Unreleased + +### Added + +### Changed + +### Fixed + +### Removed + ## 3.2.1 - 2026-10-08 ### Fixed diff --git a/version.txt b/version.txt index e4604e3af..6d7d65910 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.2.1 +3.3.0-dev