Isolate concurrent pretty-format streams

RU
1d ago
+95-5

Two overlapping calls to global pformat or a shared PrettyFormat can mix or truncate each other's output. An event-coordinated example produces Second[1] and 2] instead of First[1] and Second[2]. The existing save/restore logic handles reentrancy but still shares the active stream between threads.

Store each call's active stream in a ContextVar and reset its token in finally. Keep the idle instance stream so existing copy and pickle behavior is preserved. No dependency or lock is added.

Validation on macOS arm64:

  • Both deterministic thread regressions fail before the change and pass afterward.
  • Complete suites pass with extras (155 passed, four existing skips) and without extras (148 passed, 11 existing skips).
  • Ruff, strict basedpyright, docs and package builds pass.
  • Independent public API checks exercise 400 concurrent calls each on source, the installed wheel and CPython 3.14.6 with the GIL disabled, with no mismatches. Nested calls, exception recovery, pickle protocols 2–5 and deepcopy also pass.

Other supported CI platforms remain unverified locally. Existing IPython/timer skips and pickle protocol 0/1 errors are unchanged. This does not claim a complete free-threaded optional-extension suite.


Summary by cubic

Fixes concurrent pformat or PrettyFormat calls mixing or truncating output by giving each call its own stream via a ContextVar, instead of sharing the instance stream between threads.

  • Stores each call's active stream in _current_stream and resets its token in finally, keeping the idle instance stream for copy and pickle behavior.
  • Adds tests covering concurrent threads, nested and recursive formatter calls, and exception recovery.