python / python/cpython

Concurrent compilation changes marshal output in free-threaded builds

Abierto
#156,504 2 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

interpreter-core topic-free-threading type-bug
Lenguaje dominante
Python
Estrellas
77.2k
Forks
35.9k
Métricas de merge de PR
Métricas de PR pendientes

Descripción

Bug description:

Concurrent compilation on free-threaded CPython builds can produce marshal output that differs byte-for-byte from serial compilation of the same source.

This occurs both with the GIL disabled and with the GIL explicitly re-enabled. It reproduces on Windows and Linux/WSL. Standard CPython builds used as controls did not reproduce the difference.

This was discovered while implementing threaded bytecode compilation in pypa/pip#14286.

Reproducer

A standalone standard-library-only reproducer is available here:

It does not import or execute pip. It:

  • creates 64 copies of a minimized source fixture;
  • compiles corresponding files serially and with a ThreadPoolExecutor, in fresh child interpreters;
  • fixes logical filenames, PYTHONHASHSEED, and SOURCE_DATE_EPOCH;
  • compares checked-hash .pyc files or raw marshal.dumps(compile(...)) payloads;
  • unmarshals differing outputs and recursively compares documented code-object fields.

Example using the raw marshal backend:

python3.14t repro_threaded_pyc_determinism.py --gil off --backend marshal --workers 4 --output repro-ft-off-marshal --require-difference
python3.14t repro_threaded_pyc_determinism.py --gil on --backend marshal --workers 4 --output repro-ft-on-marshal --require-difference

The output directory must be new or empty.

Expected behavior

Serial and concurrent compilation of the same source, using the same logical filename and deterministic environment settings, should produce byte-identical marshal output.

Actual behavior

On free-threaded builds, some concurrently compiled files have different serialized bytes from their serial counterparts.

The differing payloads have:

  • equal lengths;
  • equal .pyc headers when using py_compile;
  • equal values for all recursively inspected code-object fields after unmarshalling, including bytecode, constants, names, filenames, line tables, exception tables, and nested code objects.

The raw marshal backend reproduces the behavior without py_compile or .pyc headers. Its differing offsets are exactly 16 bytes earlier than the corresponding .pyc offsets, confirming that the .pyc header is not responsible.

An empty semanticDifferences result does not establish that the object identity or alias graph is identical. The precise cause has not yet been determined.

Observed results

Windows

Windows 11 prerelease VM, eight virtual CPUs, CPython 3.15.0rc1 free-threaded build and corresponding standard CPython 3.15 build:

Build GIL Backend Result
Free-threaded Off py_compile Reproduced; 49 differing bytes in a 3,447-byte .pyc
Free-threaded On py_compile Reproduced; 49 differing bytes in a 3,447-byte .pyc
Free-threaded Off marshal Reproduced; 49 differing bytes in a 3,431-byte payload
Free-threaded On marshal Reproduced; 49 differing bytes in a 3,431-byte payload
Standard On/default Both No difference in 10 attempts
Linux under WSL2

Ubuntu 26.04 LTS, kernel 6.18.33.2-microsoft-standard-WSL2, native ext4 storage, four available CPUs:

Build GIL Backend Result
CPython 3.14.7 free-threaded Off py_compile Reproduced on attempt 1
CPython 3.14.7 free-threaded On py_compile Reproduced on attempt 1
CPython 3.14.7 free-threaded Off marshal Reproduced on attempt 1
CPython 3.14.7 free-threaded On marshal Reproduced on attempt 1
CPython 3.14.4 standard On/default Both No difference in 10 attempts

The WSL differences contained 50 differing bytes per output. All inspected decoded code-object field values were equal.

Impact

Although the decoded code objects appear semantically equivalent, byte-different outputs affect:

  • reproducible builds;
  • artifact hashing;
  • build caches;
  • deterministic CI output;
  • tools that record .pyc hashes.

Re-enabling the GIL on a free-threaded build does not prevent the behavior. As a temporary precaution, pip’s proposed threaded compilation path detects Py_GIL_DISABLED at build time and remains serial for free-threaded builds.

CPython versions tested on:

3.15

Operating systems tested on:

Linux

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Línea de trabajo

Comienza con tools/repro_threaded_pyc_determinism.py y su documento de metodología, ejecutando el backend marshal en una compilación de CPython free-threaded con el GIL activado y desactivado. Compara las salidas seriales y concurrentes; después, investiga los bytes marshal diferentes y el grafo de identidad o alias de los objetos. El trabajo estará terminado cuando la compilación concurrente y la serial produzcan una salida marshal idéntica byte a byte, con cobertura de regresión para el reproducer.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
python
Área
build-system, compilers
Tipo de issue
Error
Dificultad
5/5
Tiempo estimado
Más de una semana
Estado de actividad
Activo
Claridad
Necesita aclaración
Aptitud para principiantes
35/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.