bobfrank / pydebug

making python debuggable
5 stars 0 forks source link

Feature request: Consider making ServerHandler.__setup_thread_tracer a public interface #1

Open posita opened 7 years ago

posita commented 7 years ago

Given your own StackOverflow question (and answer), it might be cool to provide a "generic" version of your solution that others could use. For example, I could see doing something like this (untested):

# …
import platform
import warn
# …
_EMPTY_OBJ = ctypes.py_object()
# …

def install_thread_tracer(trace_function, arg=None, skip_thread_ids=()):
    trace_f = Py_tracefunc(trace_function)
    trace_f_arg = arg
    _set_thread_tracer(trace_f, trace_f_arg, skip_thread_ids)

def uninstall_thread_tracer(skip_thread_ids=()):
    trace_f = ctypes.cast(None, Py_tracefunc)
    trace_f_arg = _EMPTY_OBJ
    _set_thread_tracer(trace_f, trace_f_arg, skip_thread_ids)

def _set_thread_tracer_cpython(trace_f, trace_f_arg, skip_thread_ids):
    interp = ctypes.pythonapi.PyInterpreterState_Head()
    t = ctypes.pythonapi.PyInterpreterState_ThreadHead(interp)
    while t != 0:
        t_p = ctypes.cast(t, ctypes.POINTER(PyThreadState))
        if t_p[0].thread_id != _thread.get_ident() and t_p[0].thread_id not \
                in skip_thread_ids:
            try:
                temp = t_p[0].c_traceobj
            except ValueError:
                temp = None
            if trace_f_arg != _EMPTY_OBJ:  # Py_XINCREF
                # ctypes.pythonapi._Total
                refcount = ctypes.c_long.from_address(id(trace_f_arg))
                refcount.value += 1
            t_p[0].c_tracefunc = ctypes.cast(None, Py_tracefunc)
            t_p[0].c_traceobj = _EMPTY_OBJ
            t_p[0].use_tracing = int(t_p[0].c_profilefunc is not None)
            if temp is not None:  # Py_XDECREF
                refcount = ctypes.c_long.from_address(id(temp))
                # Don't need to dealloc since we have a ref in here and it'll
                # always be >0
                refcount.value -= 1
            t_p[0].c_tracefunc = trace_f
            t_p[0].c_traceobj = trace_f_arg
            t_p[0].use_tracing = int(trace_f is not None or
                t_p[0].c_profilefunc is not None)
        t = ctypes.pythonapi.PyThreadState_Next(t)

_THREAD_TRACER_SETTERS = {
    'CPython': _set_thread_tracer_cpython,
    # Future implementations, maybe?
}

def _set_thread_tracer_noop(*arg, **kw):  # pylint: disable=unused-arg
    pass

def _set_thread_tracer(trace_f, trace_f_arg, skip_thread_ids):
    _thread_tracer_setter = _THREAD_TRACER_SETTERS.get(
        platform.python_implementation(), _set_thread_tracer_noop)
    if _thread_tracer_setter is _set_thread_tracer_noop:
        warnings.warn('no thread tracer found for {}'.format(
            platform.python_implementation()), RuntimeWarning)
    _thread_tracer_setter(trace_f, trace_f_arg, skip_thread_ids)
    # Skip lookup next time
    global _set_thread_tracer
    _set_thread_tracer = _thread_tracer_setter

class ServerHandler(object):
    # …
    def __setup_thread_tracer(self, setting):
        global debug__has_loaded_thread
        if setting:
            install_thread_tracer(breakpoint_tracer, self,
                skip_threads=(debug__has_loaded_thread,))
        else:
            uninstall_thread_tracer(skip_threads=(debug__has_loaded_thread,))
    # …

I'd be happy to submit a PR if you think the idea has merit.

posita commented 7 years ago

Another possibility is to put that functionality into an entirely separate library and publish that on PyPI? I could draft a repo for that and transfer it to you?

bobfrank commented 7 years ago

I don't know what use cases it would be good for, but if its for debugging mostly, then probably belongs here, and if it has use outside probably belongs elsewhere (but I don't have particularly strong feelings). If you make some changes and submit a pull request I'll review and pull, or if you make it into a library I'll link to it from here (or possibly use it if I find time or you submit a pull request for that) More recent versions of centos made it easier for me to use gdb+pystack so I haven't used this library in a few years