Skip to content

Conversation

@Erotemic
Copy link
Member

Fix Python 3.14.2 multiprocessing shutdown behavior by ensuring only the owner process registers the atexit reporting hook.

Problem

When running the complex example with LINE_PROFILE=1 and PROFILE_TYPE=explicit on Python 3.14.2, multiprocessing using the forkserver start method can cause non-owner processes (e.g., the forkserver process / child processes) to also register the atexit hook. At interpreter shutdown, those processes may try to print/write profiling results, resulting in:

  • duplicated “Wrote profile results …” messages,
  • empty or truncated profile_output.txt,
  • output that continues after the shell prompt appears.

Root cause

atexit.register(...) was executed in processes that should not own reporting. Even if show() includes guards, shutdown ordering and partially-torn-down globals can make those guards unreliable. The safest approach is to avoid registering the hook outside the owner process.

Fix

Move/guard atexit.register(...) so it occurs only after establishing ownership (PID/parent/process role) and only in the designated owner process. Non-owner processes never register the shutdown reporter.


The above is a GPT description of the patch, which I used heavily to find the problem. I believe the diagnosis is correct, but I don't understand what changed in 3.14.2 to cause the problem. Pushing up the patch as is to see if it fixes the dashboard.

@Erotemic
Copy link
Member Author

I think I see the issue now. It's a combination of 2 things, which is why 3.14.0 worked, but 3.14.2 broke.

First: In 3.14 they changed the default process pool start method

Changed in version 3.14: The default process start method (see Contexts and start methods) changed away from fork. If you require the fork start method for ProcessPoolExecutor you must explicitly pass mp_context=multiprocessing.get_context("fork").

However, there was an issue that prevented forkserver from setting sys.argv correctly, and when that was fixed:

gh-143706: Fix multiprocessing forkserver so that sys.argv is correctly set before main is preloaded. Previously, sys.argv was empty during main module import in forkserver child processes. This fixes a regression introduced in 3.13.8 and 3.14.1. Root caused by Aaron Wieczorek, test provided by Thomas Watson, thanks!)

Then the failure started showing up.

…iprocessing / Py3.14 regression) (#5)

* Fix explicit profiler ownership for multiprocessing

* Avoid helper process atexit registration

* Skip atexit output in forked children

* Refine ownership checks for explicit profiler

* Add debug hooks and reduce CI matrix

* Skip orphaned forkserver output

* Restore full CI matrix
@codecov
Copy link

codecov bot commented Jan 24, 2026

Codecov Report

❌ Patch coverage is 40.59406% with 60 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.98%. Comparing base (60e928f) to head (e7151e5).
⚠️ Report is 26 commits behind head on main.

Files with missing lines Patch % Lines
line_profiler/explicit_profiler.py 40.59% 49 Missing and 11 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #415      +/-   ##
==========================================
+ Coverage   87.56%   87.98%   +0.41%     
==========================================
  Files          18       20       +2     
  Lines        1641     2180     +539     
  Branches      348      467     +119     
==========================================
+ Hits         1437     1918     +481     
- Misses        149      200      +51     
- Partials       55       62       +7     
Files with missing lines Coverage Δ
line_profiler/explicit_profiler.py 64.97% <40.59%> (-28.94%) ⬇️

... and 3 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3aa7504...e7151e5. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Erotemic
Copy link
Member Author

I think overall this method looks fine. The idea is when a script starts up and the explicit profiler is enabled, it records the pid the process and puts it in an environment variable LINE_PROFILER_OWNER_PID. This lets us compare the current pid to this variable to determine if we are a child process. However, this check doesn't always for forkserver, which so we also include a check in _is_helper_process_context that uses heuristics to determine if this is the case. I'm not sure if this will always work, but this might be a good-enough sort of situation.

I'm leaving the debug statements in as they are disabled by default, and I think they could be useful if this causes more problems in the future.

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.

1 participant