Skip to content

profiling.sampling run --blocking hangs and writes an empty profile on Windows #152356

Closed

Description

@heejaechang

Bug report

Bug description

On Windows, python -m profiling.sampling run ... --blocking hangs indefinitely and writes a 0-byte profile, even for a trivial script that runs in well under a second natively. The same command without --blocking works and produces a small, loadable binary profile.

Reproducer

tiny_cpu.py:

def work(n):
    total = 0
    for i in range(n):
        total += i * i
    return total

if __name__ == "__main__":
    print(work(2_000_000))

Working (no --blocking):

python -m profiling.sampling run --binary -o ok.bin --mode=cpu tiny_cpu.py

→ exits 0 in ~1s; ok.bin is ~1.4 KB (~60 samples) and replays fine.

Broken (--blocking):

python -m profiling.sampling run --binary -o blocking.bin --mode=cpu --blocking tiny_cpu.py

→ the target script's own work completes (its print output appears), but the profiler process never exits. After 25s (the script runs in <0.2s natively) it was still running and had to be killed. blocking.bin is 0 bytes, so it cannot be replayed (replay rejects it as "not a binary sampling profile").

Observed vs expected

  • Observed: on Windows, --blocking hangs (well over 100× the target's native runtime) and leaves an empty, unreplayable artifact.
  • Expected: --blocking finishes near native runtime and writes a loadable binary profile, like the non-blocking path.

CPython versions tested on

3.15.0b3 (Windows, x86_64). Please verify against current main in case this is already addressed.

Operating systems tested on

Windows

Linked PRs

Activity

  1. zainnadeem786 commented on Jun 28, 2026

    @zainnadeem786
    Contributor

    Hi @heejaechang @StanFromIreland ,

    I investigated this on the latest upstream/main using a local Windows debug build and was able to reproduce the issue consistently.

    The non-blocking path exits normally and produces a valid binary profile, while the --blocking path hangs after the target process has already finished and leaves a 0-byte profile.

    After tracing both code paths, the root cause appears to be an exception classification mismatch in the Windows blocking implementation.

    The blocking path eventually calls _Py_RemoteDebug_StopAllThreads() via RemoteUnwinder.pause_threads(). Once the target process has already exited, NtSuspendProcess() fails and Windows currently raises a RuntimeError. The sampler treats that exception as a recoverable sampling failure and continues retrying indefinitely, so the profiling loop never terminates and the binary writer is never finalized.

    By comparison, the non-blocking path reaches the process-exit condition through ProcessLookupError, which is already treated as a terminal condition by the sampling loop. The macOS and Linux blocking implementations also map terminated processes to ProcessLookupError, so Windows appears to be inconsistent here.

    A minimal fix may be to have the Windows remote debugging layer raise ProcessLookupError when NtSuspendProcess() fails because the target process has already terminated, while preserving the existing RuntimeError behavior for genuine suspend failures.

    I'm preparing a focused PR with the implementation and regression test unless anyone sees an issue with this approach.

  2. StanFromIreland commented on Jun 28, 2026

    @StanFromIreland
    Member

    Can you please not ping triagers, we need not be the experts for the module.

  3. zainnadeem786 commented on Jun 28, 2026

    @zainnadeem786
    Contributor

    Thanks for the clarification, and apologies about that.

    I won't ping triagers on future issues unless specifically requested. Thanks for letting me know.

  4. added a commit that references this issue on Jun 28, 2026
  5. added 2 commits that reference this issue on Jun 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions