add_hookcall_monitoring undo breaks when called out of LIFO order

Offen
#815 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Maintainer antworten meist innerhalb von 1 Tag

@AlvaroBalbin arbeitet bereits daran.

Seit 03.11.2026.

  • #816 von @mikegit123git — ohne Merge geschlossen
  • #825 von @AlvaroBalbin — offen

Bewertung

Schwierigkeit
3/5
Geschätzter Aufwand
1-2 Tage
Anfängerfreundlichkeit
55/100
Issue-Typ
Bug
Klarheit
Klar beschrieben
Aktivitätsstatus
Aktiv
Tech-Stack
python
Bereich
tooling

Rechercherichtung

Start in src/pluggy/_manager.py, at add_hookcall_monitoring() and its undo(), which restores self._inner_hookexec to a saved value. Run the reproducer from the issue, which adds monitors A and B, undoes A, then B, and prints the recorded events. enable_tracing() is built on the same function, so check it too. Done means undoing in any order removes only that monitor, repeated undo is harmless, and in-flight calls keep their original chain. Add a test for out-of-order undo next to the existing monitoring tests.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

🤖 Written by Claude Opus 5.5 via Claude Code for the pluggy maintainers; I prompted it, it did the work, I read it.

add_hookcall_monitoring() returns an undo that restores the _inner_hookexec it saw at add time (src/pluggy/_manager.py, undo() sets self._inner_hookexec = oldcall). So undo only works in strict LIFO order:

  • undoing an older monitor also removes every monitor added after it
  • a later undo can bring back a monitor that was already removed

This applies to enable_tracing() too, since it is built on add_hookcall_monitoring().

Reproducer:

import pluggy

hs = pluggy.HookspecMarker("x")
hi = pluggy.HookimplMarker("x")

class S:
    @hs
    def h(self): ...

class P:
    @hi
    def h(self): return 1

pm = pluggy.PluginManager("x")
pm.add_hookspecs(S)
pm.register(P())
ev = []

def mon(label):
    return pm.add_hookcall_monitoring(lambda *a: ev.append(label), lambda *a: None)

undo_a = mon("A")
undo_b = mon("B")

undo_a(); pm.hook.h(); print(ev)  # [] -- expected ['B']
ev.clear()
undo_b(); pm.hook.h(); print(ev)  # ['A'] -- expected []

The same thing happens on current main. pytest's own uses (--debug, pytester's HookRecorder) undo in LIFO order, so as far as we know nobody has hit this in practice.

Possible fix: keep the active (before, after) pairs in a list on the PluginManager. Each undo removes its own entry by identity, which makes repeated undo harmless, and then rebuilds _inner_hookexec from the list. Calls already in flight keep the chain they started with.

Found while reviewing #814, which fixed this with a much larger change and was closed.

Vorherrschende Sprache
Python
Sterne
1.7k
Forks
200
Ø Merge
10 Std. 4 Min.
Gemergte PRs (30 T.)
13

Entwicklungsumgebung

Dieses Projekt bietet weder Dev-Container noch Dockerfile noch Beitragsleitfaden – die Einrichtung liegt bei Ihnen. Beginnen Sie mit der README; die allgemeinen Schritte stehen in unserem Leitfaden für den ersten Beitrag.

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Mehr aus pytest-dev/pluggy

Alle Issues in pytest-dev/pluggy

Ähnliche Issues

Weitere Issues zu Python

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.