Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
id: 01a177f4-63f1-7e3d-a389-f72fcfbb888d
title: 'tentacle: cephadm/tests: pre-import tracemalloc to avoid pyfakefs setup race'
component: build
category: fix
provenance:
type: upstream_pr
upstream_prs:
- https://github.com/ceph/ceph/pull/71189
- https://github.com/ceph/ceph/pull/71163
upstream_pr_state: merged-main
upstream_commits:
- 44eff9c1937ea71ac3cd272b08c9dc16660aff10
- 79898177cb5f13da1631bb7e22f41248beeb54dc
upstream_trackers:
- https://tracker.ceph.com/issues/79677
justification:
type: engineering
refs:
- cobaltcore-dev/cloud-storage#627
- ceph/ceph#71189
- ceph/ceph#71163
- https://tracker.ceph.com/issues/79677
risk:
# Stage-B — fill these during PR prep. See release-management/backports/RISK-RUBRIC.md.
blast: cosmetic
conflict: clean
coverage: weak
---

## Justification (public)

Fixes an intermittent failure in the cephadm unit test suite that caused
`make check` to fail spuriously. No runtime behaviour changes.

## Justification (internal)

`run_tox_cephadm` fails on ~25% of arm64 and ~8% of x86 `make check` runs with
`RuntimeError: dictionary changed size during iteration` in pyfakefs
`Patcher._find_modules`, followed by ~80 cascading
`'NoneType' object has no attribute 'add_real_directory'` errors (the Patcher
is a ref-counted singleton, so a failed `setUp()` never gets its `tearDown()`).

Root cause: pytest >= 8.4's unraisable-exception hook lazily imports
`tracemalloc` from inside a GC pass triggered by leaked `NamedTemporaryFile`
finalizers left on the fake filesystem; that mutates `sys.modules` while
pyfakefs 5.3.5 iterates it. pyfakefs >= 5.10 snapshots with `dict.copy()` and
is immune, but is not a drop-in upgrade here. Fix pre-imports `tracemalloc` in
conftest — one line, no production code touched.

We carry the same pyfakefs 5.3.5 pin and unpinned pytest, so we are equally
exposed; without this, CI noise on the release branch is indistinguishable from
real regressions.

Upstream state, checked 2026-10-26: #71163 is merged into `main` as
78193dc918711dee7ecb17f8f206659a3cbaa624. #71189 is its tentacle backport,
approved and labelled `releng-audit-pass`, but not yet merged.

## Risk notes

(Stage-B rationale: blast / conflict / coverage. Filled during PR prep.)

## Behavior change

(What changes for users? Empty if pure internal fix.)

## Upgrade notes

(Operator instructions, if any.)
13 changes: 13 additions & 0 deletions src/cephadm/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Pre-import tracemalloc so that pytest's unraisable-exception hook
# (_pytest.unraisableexception.unraisable_hook -> tracemalloc_message) does not
# lazily import it from inside a garbage-collection pass.
#
# Some tests (e.g. TestBootstrap) leave NamedTemporaryFile objects created on
# the pyfakefs fake filesystem to be garbage collected after the fake fs has
# been torn down; their __del__ raises, pytest's hook runs during GC and, the
# first time, imports tracemalloc. If that GC happens while pyfakefs'
# Patcher._find_modules() is iterating sys.modules, the `fs` fixture setup
# fails with "RuntimeError: dictionary changed size during iteration" and,
# because Patcher is a ref-counted singleton, every later fs-based test errors
# with "'NoneType' object has no attribute 'add_real_directory'".
import tracemalloc # noqa: F401
Loading