patch = subprocess: SIGINT-killed child exits 1 instead of -2 on Python 3.13 (regression in 7.17.1)

Abierto
#2,362 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
3/5
Tiempo estimado
1-2 días
Aptitud para principiantes
68/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
python

Línea de trabajo

Start by reading the subprocess patch implementation and its tests, then reproduce the reported return-code difference with the supplied parent script on Python 3.13 and coverage 7.17.2. Compare behavior with 7.17.0 and without the patch to narrow the regression. Done means the patched child exits with return code -2 on SIGINT while the existing subprocess tests continue to pass.

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

Descripción

Describe the bug

With [run] patch = subprocess, a Python child process that is interrupted by SIGINT exits with return code 1 instead of dying from the signal (return code -2, as seen by subprocess). This started in 7.17.1 and is still there in 7.17.2. It only happens on Python 3.13; 3.12 and 3.14 are fine. Without patch = subprocess the child exits correctly.

The child still prints the usual KeyboardInterrupt traceback, so the interrupt is raised. Only the process exit status changes. Python normally re-raises an unhandled KeyboardInterrupt as a real SIGINT death at shutdown, and that seems to be skipped in the patched child.

This breaks callers that map a signal death to exit code 130. We pinned 7.17.0 in the meantime.

To Reproduce

.coveragerc:

[run]
patch = subprocess

parent.py:

import os, signal, subprocess, sys, time

child = subprocess.Popen(
    [sys.executable, "-c", "import time; open('ready', 'w').close(); time.sleep(60)"]
)
while not os.path.exists("ready"):
    time.sleep(0.05)
child.send_signal(signal.SIGINT)
print("child returncode:", child.wait())
os.remove("ready")

Commands, in an empty directory holding the two files above:

python3.13 -m venv venv
venv/bin/pip install coverage==7.17.2
venv/bin/coverage run parent.py
  1. Python: CPython 3.13.14 (3.12.13 and 3.14.6 are not affected)
  2. coverage.py: 7.17.1 and 7.17.2; 7.17.0 is fine
  3. Installed packages: only coverage
  4. Code: the two files above; no repo needed
  5. Commands: above

Expected: child returncode: -2
Actual on Python 3.13 with coverage 7.17.1 or 7.17.2: child returncode: 1

Results (Linux x86_64, CPython from python-build-standalone via uv), deterministic over repeated runs:

Python 7.17.0 7.17.1 7.17.2 7.17.2 without patch = subprocess
3.12.13 -2 -2 -2 -2
3.13.14 -2 1 1 -2
3.14.6 -2 -2 -2 -2

A child that signals itself (os.kill(os.getpid(), signal.SIGINT) at module level) reproduced it only intermittently. An external SIGINT delivered while the child is blocked in time.sleep fails every time.

Expected behavior

The measured child's exit status is the same as without coverage: death by SIGINT (return code -2).

Additional context
  • Linux 7.0 x86_64, glibc 2.43
  • Same result with COVERAGE_CORE unset, ctrace, sysmon and pytrace
Lenguaje dominante
Python
Estrellas
3.4k
Forks
554
Merge medio
1 d 10 h
PR fusionados (30 d)
13

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

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.

Más de coveragepy/coveragepy

Todos los issues de coveragepy/coveragepy

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.