python / cpython

The Python programming language
https://www.python.org
Other
63.3k stars 30.3k forks source link

Keep Track of Where Each Python Thread Was Started #125928

Open ericsnowcurrently opened 6 days ago

ericsnowcurrently commented 6 days ago

Feature or enhancement

Proposal:

Debugging threads can be tricky. One thing that could sometimes help would be knowing where Thread.start() was called. We could achieve this by capturing the traceback of the call and attaching it to the thread. We probably wouldn't want to keep the actual traceback, due to how that would leak all the frames, etc. Instead, we'd probably want to use traceback.TracebackSummary or perhaps even something more lightweight. (FYI, there are other places where such a lightweight traceback snapshot would be useful.)

FWIW, the same sort of tracking might be helpful for coroutines (asyncio), though this is partly addressed by https://github.com/python/cpython/pull/124640.

Has this already been discussed elsewhere?

No response given

Links to previous discussion of this feature:

No response

rruuaanng commented 6 days ago

That is, is it similar to lineno when a thread starts? Or maybe it's its parent thread?

ericsnowcurrently commented 5 days ago

My (vague) idea is that we store a lightweight snapshot of the traceback on the thread object when start() is called. I'm not aware of any relationship with lineno or parent threads.

rruuaanng commented 4 days ago

I'm not sure if the FrameSummary in the traceback meets your needs, maybe the description is a bit too much.

from FrameSummary

    def __init__(self, filename, lineno, name, *, lookup_line=True,
            locals=None, line=None,
            end_lineno=None, colno=None, end_colno=None):
        """Construct a FrameSummary.

        :param lookup_line: If True, `linecache` is consulted for the source
            code line. Otherwise, the line will be looked up when first needed.
        :param locals: If supplied the frame locals, which will be captured as
            object representations.
        :param line: If provided, use this instead of looking up the line in
            the linecache.
        """
        self.filename = filename
        self.lineno = lineno
        self.end_lineno = lineno if end_lineno is None else end_lineno
        self.colno = colno
        self.end_colno = end_colno
        self.name = name
        self._lines = line
        self._lines_dedented = None
        if lookup_line:
            self.line
        self.locals = {k: _safe_string(v, 'local', func=repr)
            for k, v in locals.items()} if locals else None
pablogsal commented 22 hours ago

Hummmm, I think that we can do this using a tool more than doing it all the time (even if is not needed). The profiler/debugger can capture the events on demand.