Skip to content

debugpy 1.8.19 creates _DummyThread when inspecting PySide6 QThread under Python 3.13, causing teardown crash #1985

@Erriez

Description

@Erriez

Summary

When debugging a PySide6 application using debugpy 1.8.19 (via VS Code Python Debugger extension 2025.18.0) on Python 3.13.4, debugpy’s sys‑monitoring creates _DummyThread objects while inspecting a QThread that performs HTTP requests (requests/urllib3).
During interpreter shutdown, Python 3.13 attempts to finalize these _DummyThread objects and crashes with:

Exception ignored in: <function _DeleteDummyThreadOnDel.__del__>
TypeError: 'NoneType' object does not support the context manager protocol

This does not occur:

  • when running the same code outside VS Code (no debugpy)
  • when using only Python threads
  • when using QThread without debugpy
  • when using requests/urllib3 without debugpy

The issue is reproducible with a minimal PySide6 + requests script.

I have not tested this on Python 3.12, so I cannot confirm whether the issue is specific to Python 3.13 or also affects earlier versions


Environment

  • Python: 3.13.4 (Windows x64)
  • VS Code Python Debugger extension: 2025.18.0
  • debugpy version: 1.8.19
    (verified via import debugpy; debugpy.__version__)
  • OS: Windows 11
  • PySide6: 6.10.1
  • requests: 2.32.5
  • urllib3: 2.6.0

Minimal pip freeze:

certifi==2025.11.12
charset-normalizer==3.4.4
idna==3.11
PySide6==6.10.1
PySide6_Addons==6.10.1
PySide6_Essentials==6.10.1
requests==2.32.5
shiboken6==6.10.1
urllib3==2.6.0

Minimal Reproduction

qt_debugpy_repro.py:

import sys
import time
import threading
import requests
from PySide6.QtCore import QObject, QThread, Signal
from PySide6.QtWidgets import QApplication

import debugpy
print(f"debugpy version: {debugpy.__version__}")

class Worker(QObject):
    tick = Signal()

    def run(self):
        # Simulate Spotipy/urllib3 behavior: property access on HTTPResponse
        for _ in range(3):
            r = requests.get("https://httpbin.org/get")
            _ = r.raw.closed  # <-- triggers urllib3.response.closed
            time.sleep(0.1)
        print("worker done")


def dump_threads(label):
    print(f"\nTHREADS {label}:")
    for t in threading.enumerate():
        print(" -", t, "daemon=", t.daemon, "type=", type(t))


def main():
    app = QApplication(sys.argv)

    worker = Worker()
    thread = QThread()
    worker.moveToThread(thread)
    thread.started.connect(worker.run)

    thread.start()

    QThread.msleep(500)

    dump_threads("BEFORE SHUTDOWN")

    # Stop QThread without joining (mirrors real-world teardown timing)
    thread.quit()

    dump_threads("AFTER QUIT")

    print("exiting app")
    sys.exit(0)


if __name__ == "__main__":
    main()

Steps to Reproduce

  1. Open the script in VS Code
  2. Select Python 3.13.4 interpreter
  3. Use this launch.json:
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python Debug Repro",
            "type": "python",
            "request": "launch",
            "program": "${workspaceFolder}/qt_debugpy_repro.py",
            "console": "integratedTerminal",
            "python": "${workspaceFolder}/venv/Scripts/python"
        }
    ]
}
  1. Run with Start Debugging
  2. Observe thread list and shutdown behavior

Observed Behavior (under debugpy)

In the minimal reproduction code listed above, debugpy creates a _DummyThread when inspecting a PySide6 QThread.
The minimal repro does not crash Python, but it clearly shows the unexpected _DummyThread created by debugpy:

THREADS BEFORE SHUTDOWN:
 - <_MainThread(MainThread, ...)>
 - <_DummyThread(Dummy-6, ...)>

THREADS AFTER QUIT:
 - <_MainThread(MainThread, ...)>
 - <_DummyThread(Dummy-6, ...)>

exiting app
QThread: Destroyed while thread '' is still running

Behavior in the full application

In my real application (which uses the same pattern but performs more work inside the QThread), the _DummyThread created by debugpy survives until interpreter shutdown.
On Python 3.13.4 this leads to a teardown crash:

Exception ignored in: <function _DeleteDummyThreadOnDel.__del__>
TypeError: 'NoneType' object has no support for the context manager protocol

This crash does not occur outside debugpy.

The _DummyThread is created by debugpy’s sys‑monitoring:

Example captured stack:

=== DummyThread created ===
  File "qt_debugpy_repro.py", line XX, in run
  File "...debugpy/_pydevd_sys_monitoring.py", line 330, in _create_thread_info
    t = threading.current_thread()
  File "threading.py", line 1439, in current_thread
    return _DummyThread()
=== end DummyThread stack ===

Expected Behavior

  • No _DummyThread should be created for QThreads
  • No crash during interpreter shutdown
  • Behavior should match Python 3.12 and non-debugpy execution

Analysis

  • QThread is not a Python threading.Thread
  • debugpy’s sys‑monitoring inspects thread state during property access (urllib3.response.closed)
  • threading.current_thread() returns _DummyThread() when the thread is not in threading._active
  • Python 3.13 changed teardown order
  • _DummyThread.__del__ now runs after parts of threading are already destroyed
  • This leads to the observed TypeError during shutdown

This issue does not occur with:

  • Python threads
  • QThreads without debugpy
  • requests/urllib3 without debugpy

Only the combination of debugpy + QThread + urllib3 + Python 3.13 triggers it.


Possible Fix Direction

  • Avoid calling threading.current_thread() on non‑Python threads
  • Or guard _create_thread_info() against QThreads
  • Or avoid creating _DummyThread objects during sys‑monitoring
  • Or ensure _DummyThread is not left alive until interpreter teardown

Thank you

Happy to test patches or provide additional instrumentation if needed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions