Skip to content
Open
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
3 changes: 2 additions & 1 deletion .github/workflows/docs.yml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
name: Documentation

on: [push, pull_request]
on:
workflow_dispatch:

jobs:
build:
Expand Down
5 changes: 1 addition & 4 deletions .github/workflows/main.yml
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
name: Main

on:
push:
branches:
- master
pull_request:
workflow_dispatch:

jobs:
build-test:
Expand Down
57 changes: 57 additions & 0 deletions .github/workflows/windows-build-test-3.14.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
name: Windows Build and Test (3.14)

on:
push:
branches:
- master
pull_request:
workflow_dispatch:

jobs:
build-test:
name: Build and Test
runs-on: windows-latest
timeout-minutes: 15

steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: '8.0.x'

- name: Set up Python 3.14
uses: astral-sh/setup-uv@v7
with:
architecture: x64
python-version: '3.14'
cache-python: true
activate-environment: true
enable-cache: true

- name: Synchronize the virtual environment
run: uv sync --managed-python

- name: Show pyvenv.cfg
run: cat .venv/pyvenv.cfg

- name: Embedding tests (Mono/.NET Framework)
run: dotnet test --runtime any-x64 --framework net472 --logger "console;verbosity=detailed" src/embed_tests/
if: always()
env:
MONO_THREADS_SUSPEND: preemptive # https://github.com/mono/mono/issues/21466

- name: Embedding tests (.NET Core)
run: dotnet test --runtime any-x64 --framework net8.0 --logger "console;verbosity=detailed" src/embed_tests/
if: always()

- name: Python Tests (.NET Core)
run: pytest --runtime coreclr

- name: Python Tests (.NET Framework)
run: pytest --runtime netfx

- name: Python tests run from .NET
run: dotnet test --runtime any-x64 src/python_tests_runner/
25 changes: 25 additions & 0 deletions src/runtime/PythonEngine.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
Expand DownExpand Up@@ -143,6 +145,29 @@ public static string Version
get{return Marshal.PtrToStringAnsi(Runtime.Py_GetVersion())}
}

internal static Version GetPythonVersion()
{
string? versionText = Version;
if (string.IsNullOrWhiteSpace(versionText))
{
return new Version(0, 0);
}

Match match = Regex.Match(versionText, @"^(\\d+)\\.(\\d+)(?:\\.(\\d+))?");
if (!match.Success)
{
return new Version(0, 0);
}

int major = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
int minor = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
int patch = match.Groups[3].Success
? int.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture)
: 0;

return new Version(major, minor, patch);
}

public static string BuildInfo
{
get{return Marshal.PtrToStringAnsi(Runtime.Py_GetBuildInfo())}
Expand Down
9 changes: 4 additions & 5 deletions src/runtime/Runtime.Delegates.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,14 +25,13 @@ static Delegates()
PyThreadState_Get = (delegate* unmanaged[Cdecl]<PyThreadState*>)GetFunctionByName(nameof(PyThreadState_Get), GetUnmanagedDll(_PythonDll));
try
{
// Up until Python 3.13, this function was private and named
// slightly differently.
PyThreadState_GetUnchecked = (delegate* unmanaged[Cdecl]<PyThreadState*>)GetFunctionByName("_PyThreadState_UncheckedGet", GetUnmanagedDll(_PythonDll));
PyThreadState_GetUnchecked = (delegate* unmanaged[Cdecl]<PyThreadState*>)GetFunctionByName(nameof(PyThreadState_GetUnchecked), GetUnmanagedDll(_PythonDll));
}
catch (MissingMethodException)
{

PyThreadState_GetUnchecked = (delegate* unmanaged[Cdecl]<PyThreadState*>)GetFunctionByName(nameof(PyThreadState_GetUnchecked), GetUnmanagedDll(_PythonDll));
// Up until Python 3.12, this function was private and named
// slightly differently.
PyThreadState_GetUnchecked = (delegate* unmanaged[Cdecl]<PyThreadState*>)GetFunctionByName("_PyThreadState_UncheckedGet", GetUnmanagedDll(_PythonDll));
}
try
{
Expand Down
35 changes: 29 additions & 6 deletions src/runtime/Runtime.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -262,7 +262,7 @@
if (!HostedInPython && !ProcessIsTerminating)
{
// avoid saving dead objects
TryCollectingGarbage(runs: 3);
TryCollectingGarbage(runs: 3, pythonGC: !ShouldSkipPythonGcOnShutdown);

Check failure on line 265 in src/runtime/Runtime.cs

View workflow job for this annotation

GitHub Actions/ Build and Test

There is no argument given that corresponds to the required parameter 'forceBreakLoops' of 'Runtime.TryCollectingGarbage(int, bool, bool, bool, bool, bool)'

Check failure on line 265 in src/runtime/Runtime.cs

View workflow job for this annotation

GitHub Actions/ Build and Test

There is no argument given that corresponds to the required parameter 'forceBreakLoops' of 'Runtime.TryCollectingGarbage(int, bool, bool, bool, bool, bool)'

RuntimeData.Stash();
}
Expand All@@ -275,7 +275,8 @@
RemoveClrRootModule();

TryCollectingGarbage(MaxCollectRetriesOnShutdown, forceBreakLoops: true,
obj: true, derived: false, buffer: false);
obj: true, derived: false, buffer: false,
pythonGC: !ShouldSkipPythonGcOnShutdown);
CLRObject.creationBlocked = true;

NullGCHandles(ExtensionType.loadedExtensions);
Expand All@@ -294,8 +295,12 @@
DisposeLazyObject(hexCallable);
PyObjectConversions.Reset();

PyGC_Collect();
bool everythingSeemsCollected = TryCollectingGarbage(MaxCollectRetriesOnShutdown);
if (!ShouldSkipPythonGcOnShutdown)
{
PyGC_Collect();
}
bool everythingSeemsCollected = TryCollectingGarbage(MaxCollectRetriesOnShutdown,

Check failure on line 302 in src/runtime/Runtime.cs

View workflow job for this annotation

GitHub Actions/ Build and Test

There is no argument given that corresponds to the required parameter 'forceBreakLoops' of 'Runtime.TryCollectingGarbage(int, bool, bool, bool, bool, bool)'

Check failure on line 302 in src/runtime/Runtime.cs

View workflow job for this annotation

GitHub Actions/ Build and Test

There is no argument given that corresponds to the required parameter 'forceBreakLoops' of 'Runtime.TryCollectingGarbage(int, bool, bool, bool, bool, bool)'
pythonGC: !ShouldSkipPythonGcOnShutdown);
Debug.Assert(everythingSeemsCollected);

Finalizer.Shutdown();
Expand DownExpand Up@@ -328,7 +333,8 @@
const int MaxCollectRetriesOnShutdown = 20;
internal static int _collected;
static bool TryCollectingGarbage(int runs, bool forceBreakLoops,
bool obj = true, bool derived = true, bool buffer = true)
bool obj = true, bool derived = true, bool buffer = true,
bool pythonGC = true)
{
if (runs <= 0) throw new ArgumentOutOfRangeException(nameof(runs));

Expand All@@ -340,7 +346,10 @@
{
GC.Collect();
GC.WaitForPendingFinalizers();
pyCollected += PyGC_Collect();
if (pythonGC)
{
pyCollected += PyGC_Collect();
}
pyCollected += Finalizer.Instance.DisposeAll(disposeObj: obj,
disposeDerived: derived,
disposeBuffer: buffer);
Expand All@@ -366,6 +375,20 @@
public static bool TryCollectingGarbage(int runs)
=> TryCollectingGarbage(runs, forceBreakLoops: false);

static bool ShouldSkipPythonGcOnShutdown
{
get
{
if (!IsWindows)
{
return false;
}

Version version = PythonEngine.GetPythonVersion();
return version >= new Version(3, 14);
}
}

static void DisposeLazyObject(Lazy<PyObject> pyObject)
{
if (pyObject.IsValueCreated)
Expand Down
Loading