Skip to content

Fix Python runtime shutdown GIL state leak - Bug 9708 - #9786

Closed
Farrell-A wants to merge 3 commits into
QuantConnect:masterfrom
Quantca:bug-9708-GILState
Closed

Fix Python runtime shutdown GIL state leak - Bug 9708#9786
Farrell-A wants to merge 3 commits into
QuantConnect:masterfrom
Quantca:bug-9708-GILState

Conversation

@Farrell-A

Copy link
Copy Markdown

Description

  • Remove the redundant outer Py.GIL() acquisition from PythonInitializer.Shutdown().
  • Add a process-isolated regression test that runs the production shutdown path through Isolator, verifies Python is shut down, forces pending finalizers, and requires both a successful child exit and an explicit completion marker.

Related Issue

Fixes #9708

Motivation and Context

PythonEngine.Shutdown() manages its own GIL state. LEAN's additional GIL handle is not disposed before the Python runtime shuts down. When that handle is finalized, Py.GILState.Finalize() throws on the CLR finalizer thread and aborts an otherwise successful algorithm or test process.

Removing the redundant acquisition leaves Python.NET's internally paired shutdown behavior intact and prevents the leaked handle from reaching its throwing finalizer. The regression test uses a child process because the pre-fix exception terminates the test host and cannot be caught by the parent test. The completion marker also proves that the child executed the entire shutdown/finalizer path, preventing a zero-test or premature-exit false positive.

Requires Documentation Change

No. This corrects an internal shutdown lifecycle defect without changing user-facing behavior or public APIs.

How Has This Been Tested?

Validated after merging QuantConnect master at 9bf283685, which uses QuantConnect.pythonnet 2.0.66. Test environment: Ubuntu 26.04 x86-64, .NET SDK 10.0.112, and CPython 3.14.4 (PYTHONHOME=/usr, PYTHONNET_PYDLL=/usr/lib/x86_64-linux-gnu/libpython3.14.so.1.0).

  • ShutdownCompletesWithoutLeakingGil: 1/1 passed with the fix. The child completed shutdown, forced pending finalizers, emitted the required completion marker, and exited normally.
  • Negative control in an isolated temporary worktree: restoring the removed var pyLock = Py.GIL(); made the same test fail 1/1. The child test host aborted in Python.Runtime.Py.GILState.Finalize() with GIL must always be released, and it must be released from the same thread that acquired it. and exit code 1.
  • PythonInitializerTests plus IsolatorTests: 8/8 passed.
  • dotnet build QuantConnect.Lean.sln -c Release --no-restore --maxcpucount:1: succeeded with 0 errors (existing repository warnings remain).

The complete repository test suite was not run locally.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • Refactor (non-breaking change which improves implementation)
  • Performance (non-breaking change which improves performance. Please add associated performance test and results)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Non-functional change (xml comments/documentation/etc)

Checklist:

  • My code follows the code style of this project.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed. The focused affected tests passed; the complete repository test suite was not run locally.
  • My branch follows the naming convention bug-<issue#>-<description> or feature-<issue#>-<description>

@Martin-Molinero

Copy link
Copy Markdown
Member

Closing for now, replied in associated issue #9708 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python runtime shutdown aborts the process in Py.GILState.Finalize()

3 participants