Skip to content

gh-160601: Handle surrogate characters in sampling profiler binary output - #160615

Open
zolaski333 wants to merge 1 commit into
python:mainfrom
zolaski333:codex/gh-160601-binary-profiler-surrogates
Open

zolaski333 wants to merge 1 commit into
python:mainfrom
zolaski333:codex/gh-160601-binary-profiler-surrogates

Conversation

@zolaski333

@zolaski333 zolaski333 commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Encode sampled filenames and function names using UTF-8 with backslashreplace, preventing UnicodeEncodeError when they contain surrogate characters. The string table remains valid UTF-8 and compatible with the existing reader.

Add regression coverage for surrogate characters in both filenames and function names, including mixed Unicode names and repeated samples.

Validation on Windows:

  • test_profiling: passed.
  • Binary format tests with a Debug build and -R 3:3: passed, no leaks detected.
  • End-to-end binary capture and JSONL replay: passed.
  • patchcheck and git diff --check: passed.

Zstd compression was unavailable in this Windows build.

@zolaski333
zolaski333 requested a review from pablogsal as a code owner October 24, 2026 18:53
ChrisBuilds added a commit to ChrisBuilds/terminaltexteffects that referenced this pull request Oct 28, 2026
## Resulting behavior

The Profile workflow no longer fails when a sampled frame name can't be
encoded. `tools/perf/sample_profile.py` now checks each sample's file
and function names before calling the binary writer, and drops a sample
with lone surrogates without writing it. It still counts and reports
dropped samples as before, so the "Profiler dropped samples" warning is
unchanged.

The #354 wrapper called the writer first and caught the
`UnicodeEncodeError`. That can't work, because CPython 3.15's
`BinaryWriter.write_sample` (`Modules/_remote_debugging/module.c`) sets
`state = BINARY_WRITER_BROKEN` after any failed write and refuses every
later sample with `ValueError: Writer is broken`. That's the failure on
`main` at 7bef9a9 and de137a5: one dropped sample, then the traceback.

The name check returns early for ASCII names (`str.isascii()`), so the
regex only runs on the rare non-ASCII names.

Closes #361

## Validation

- `tests/test_perf_sample_profile.py`: the fake writer now behaves like
CPython's and refuses every sample after a failed one. On Python 3.15,
the test fails against the old wrapper with `ValueError: Writer is
broken` and passes with this change. The name-detection test also passes
on 3.14.
- Ruff format and lint, and Pyright, on the touched files: passed.
- Manual Profile run on this branch with the defaults, covering
`all-large-iterator`, where #354's merge commit failed:
https://github.com/ChrisBuilds/terminaltexteffects/actions/runs/40764532996.
It passed, but no sampled name needed dropping this time, so it confirms
the wrapper runs cleanly without exercising the drop.
- I couldn't run it end to end locally: macOS needs root to attach the
sampling profiler.

## Limitations

The bad names appear only in some runs, so one green Profile run doesn't
prove the fix. The unit test reproduces the writer's broken state
directly. The wrapper can be removed once the Python used for profiling
includes python/cpython#160615.

## Review checklist

- [x] Assignee, labels, and release milestone match the linked issue.
- [x] The linked issue's acceptance criteria are met.
- [x] Changes stay within the agreed scope.
- [x] Relevant documentation and generated completions are current.
- [x] Changelog fragment added for user-facing changes, or the
`no-changelog` label applied.
- [x] Required CI checks pass for the latest revision.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant