Skip to content

Prevent reentrant Python apartment teardown during native callbacks - #205

Open
leileizhang (lei9444) wants to merge 2 commits into
mainfrom
lei9444-guard-reentrant-apartment-close
Open

leileizhang (lei9444) wants to merge 2 commits into
mainfrom
lei9444-guard-reentrant-apartment-close

Conversation

@lei9444

@lei9444 leileizhang (lei9444) commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Closing the final Python RoApartment inside a synchronous native callback can call RoUninitialize while the outer native call still owns temporary COM references. A real PropertySet.MapChanged handler reproduced 0xC0000005 when the native call unwound; this predates #198.

Fix

Track synchronous native-to-Python callback depth per OS thread. A final RoApartment.close()/__exit__() or matched manual ro_uninitialize() inside a callback now raises an actionable, retryable RuntimeError before native teardown; non-final nested closes remain balanced. The caller may retry after the callback and outer native call return and retained references are released.

If an anonymous guard is dropped during that callback, retain its initialization for explicit same-thread RoApartment.recover_pending() rather than auto-closing it on unwind. Explicit wrong-thread close leaves state unchanged; foreign implicit Drop only queues an owner-thread-recoverable token and emits a native diagnostic—no foreign-thread COM cleanup or Python attachment.

Verification and integration

Isolated STA/MTA PropertySet.MapChanged children reproduced the baseline crash; the fix passes 26 bounded callback/lifetime cases per architecture on x64 and ARM64, generated Python WinRT E2E, native binding regressions, and exact-head Build, mixed-language coverage, and Python-release checks. Optional WinUI fixture coverage is separate.

This PR is independent of #198 and does not resolve #189's ordinary object-outliving-apartment crash. If either PR merges first, reconcile the managed initialization counts and pending-close recovery paths, then rerun combined native and generated Python tests before landing the other. Human review is still required.

Reject final apartment close during synchronous native callbacks and preserve anonymously dropped contexts for explicit same-thread recovery. Cover generated PropertySet MapChanged callbacks in isolated STA/MTA subprocesses.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Mixed-language test coverage

Workflow status: ✅ Passed

Layer Lines Functions Branches/regions
Rust, including native .pyd/.node 86.94% 82.51% 86.69% regions
Python aggregate 72.35% n/a 39.49% branches
Python runtime 98.17% n/a 94.57% branches
Generated Python WinRT projections 71.09% n/a 33.61% branches
Generated Python WinRT implementations 71.97% n/a 46.3% branches
JavaScript aggregate 21.91% 25.18% 57.61% branches
JavaScript runtime 44.27% 45.76% 78.99% branches
Generated WinRT projections 22.8% 18.7% 54.84% branches
Generated WinRT implementations 45.99% 59.19% 60.97% branches
Generated Classic COM projections 11.88% 23.97% 53.4% branches

View workflow run and download full HTML/LCOV/XML reports

Reject foreign-thread apartment operations before native teardown. Retain foreign drops in an owner-recoverable queue without attaching Python, and fail closed during owner TLS teardown. Cover both architecture callback paths and exceptional queue states.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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: projected objects that outlive RoApartment crash the process (0xC0000005) on release or interpreter exit

1 participant