You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
test_profiling.test_sampling_profiler: test_is_python_process_non_python_subprocess() failed on AMD64 Alpine Linux NoGIL PR #159534
FAIL: test_is_python_process_non_python_subprocess (test.test_profiling.test_sampling_profiler.test_children.TestIsPythonProcess.test_is_python_process_non_python_subprocess)
Test that a non-Python subprocess is not detected as Python.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/buildbot/buildarea/pull_request.ware-alpine.nogil/build/Lib/test/test_profiling/test_sampling_profiler/test_children.py", line 649, in test_is_python_process_non_python_subprocess
self.assertFalse(
~~~~~~~~~~~~~~~~^
is_python_process(proc.pid),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
f"Non-Python subprocess 'sleep' (PID {proc.pid}) should NOT be "
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
f"detected as Python",
^^^^^^^^^^^^^^^^^^^^^^
)
^
AssertionError: True is not false : Non-Python subprocess 'sleep' (PID 1478327) should NOT be detected as Python
search_linux_map_for_section() open /proc/{pid}/maps to read "sleep" process maps.
When the bug occurs, /proc/{pid}/maps is not the sleep maps, but maps of a Python process. If I reopen the same path, I get the sleep maps.
Example where f file is the search_linux_map_for_section() maps_file and f2 is a fresh file opening the same path. You can see that the two files don't return the same contents, but os.fstat() pretends that it's the same file!?
importsysimportsubprocessimportos.pathimporttimeGLOBAL_PIDS=set()
def_wait_for_process_ready(proc, timeout):
# Wait for a subprocess to be ready using polling instead of fixed sleep.# Returns True if process is ready, False if it exited or timeout.deadline=time.time() +timeoutpoll_interval=0.01whiletime.time() <deadline:
ifproc.poll() isnotNone:
returnFalsetry:
ifsys.platform=="linux":
ifos.path.exists(f"/proc/{proc.pid}/exe"):
returnTrueelse:
returnTrueexceptOSError:
passtime.sleep(poll_interval)
poll_interval=min(poll_interval*2, 0.1)
returnproc.poll() isNonedef_cleanup_process(proc, timeout=30):
print("terminate", proc.pid)
proc.terminate()
proc.wait(timeout=timeout)
defis_python_process(pid):
filename=f"/proc/{pid}/maps"fortestinrange(8):
f=open(filename)
data=f.read()
if"python"indata:
print(f". test {test}")
os.system(f"cat {filename}")
print("BUG! FOUND PYTHON IN MAPS")
f2=open(filename)
data2=f2.read()
fd1=f.fileno()
fd2=f2.fileno()
print(f"fd1={fd1} fd2={fd2}")
print("reopen()+read(): same data?", data2==data)
print("same fstat for the 2 lines?", os.fstat(fd1) ==os.fstat(fd2))
print(f"first line of data: {data.splitlines()[0]!r}")
print(f"first line of data2: {data2.splitlines()[0]!r}")
breakpoint()
returnTruef.close()
returnFalsedeffunc():
proc=subprocess.Popen(
["sleep", "3600"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
try:
# Wait for process to be ready using pollingpid=proc.pidifpidinGLOBAL_PIDS:
print("PID RECYCLED "+"@"*40)
print("PID RECYCLED "+"@"*40)
print("PID RECYCLED "+"@"*40)
GLOBAL_PIDS.add(pid)
print(f"non-python process: pid {pid}")
ifnot_wait_for_process_ready(proc, 30):
print(f"Process {proc.pid} should be ready within {SHORT_TIMEOUT}s")
sys.exit(1)
python=is_python_process(proc.pid)
ifpython:
print("BUG!")
breakpoint()
finally:
_cleanup_process(proc)
i=0whileTrue:
i+=1print(f"Run #{i}")
func()
So when the 463th process is created, reading /proc/28129/maps gets the maps of the current Python process, rather than reading maps of the sleep process.
If /proc/28129/maps is open again, sleep maps is read as expected.
Also, running cat /proc/28129/maps also shows the sleep maps (as expected).
Ah wait, I understood the root cause. is_python_process(proc.pid) is called as soon as the sleep 10 process is created. Sometimes, the test process can still see Python mappings in /proc/{pid}/maps just because the sleep 10 process is not sleep executable yet, but is still a freshly forked Python process. The problem is that we don't wait until exec() completed in the child process.
Activity
vstinner commentedon Oct 16, 2026
Oh, it seems like the test doesn't fail on AMD64 Alpine Linux NoGIL 3.x: https://buildbot.python.org/#/builders/1819/builds/3396.
vstinner commentedon Oct 21, 2026
test.pythoninfo:
vstinner commentedon Oct 21, 2026
I can reproduce the test failure on a Debian 12 VM by running the stress test:
./python -m test test_profiling.test_sampling_profiler.test_children -v -m test_is_python_process_non_python_subprocess -FIt takes 9 minutes and 5297 tries to reproduce the issue!
vstinner commentedon Oct 21, 2026
On my Fedora 44 laptop, I failed to reproduce the issue (I ran the stress test for 10 minutes, 7572 runs).
vstinner commentedon Oct 21, 2026
search_linux_map_for_section() open
/proc/{pid}/mapsto read "sleep" process maps.When the bug occurs,
/proc/{pid}/mapsis not the sleep maps, but maps of a Python process. If I reopen the same path, I get the sleep maps.Example where f file is the search_linux_map_for_section() maps_file and f2 is a fresh file opening the same path. You can see that the two files don't return the same contents, but os.fstat() pretends that it's the same file!?
and
vstinner commentedon Oct 21, 2026
Script to reproduce the issue:
Example of output on my Debian 12 VM:
So when the 463th process is created, reading
/proc/28129/mapsgets the maps of the current Python process, rather than reading maps of the sleep process.If
/proc/28129/mapsis open again, sleep maps is read as expected.Also, running
cat /proc/28129/mapsalso shows the sleep maps (as expected).vstinner commentedon Oct 21, 2026
I can quickly reproduce the issue on the AMD64 Debian root 3.x buildbot: https://buildbot.python.org/#/builders/345/builds/15743
For example, I reproduced the issue after only 17 runs. The buildbot runs on Linux kernel 6.1.0-54-amd64.
My Debian 12 VM runs on the same Linux kernel 6.1.0-54-amd64.
vstinner commentedon Oct 21, 2026
Ah wait, I understood the root cause.
is_python_process(proc.pid)is called as soon as thesleep 10process is created. Sometimes, the test process can still see Python mappings in/proc/{pid}/mapsjust because thesleep 10process is notsleepexecutable yet, but is still a freshly forked Python process. The problem is that we don't wait until exec() completed in the child process.pythongh-159534: Fix race condition in test_sampling_profiler
pythongh-159534: Fix race condition in test_sampling_profiler
gh-159534: Fix race condition in test_sampling_profiler (#160028)
[3.15] gh-159534: Fix race condition in test_sampling_profiler (GH-16…
thomgree commentedon Oct 29, 2026
I think this left the test broken on Alpine, and also broke it on RHEL-UBI9.
See #160881