Why don't pydevd breakpoints work in callbacks?

107 Views Asked by At

I've tried this in both VS Code and PyCharm, and both behave the same way. I've got a python application that uses pyads, but I'm not sure there's anything interesting about pyads in particular. The problem is that in either IDE, if I put a breakpoint on a line that will execute as a pyads callback/notification, the code executes but does NOT break.

In the case of pyads, I set up notifications for particular PLC tags. This associates a callback function with that tag, so if the tag's value changes, my callback executes. pyads is just a wrapper around TcAdsDll.dll, and it looks like pyads uses ctypes to do a lot of the heavy lifting. When I place a breakpoint inside the callback and run the code in VS Code or PyCharm (both of which use pydevd), the code executes but the breakpoint does not get hit.

Furthermore, when I print process IDs, thread IDs, and stack traces at different places in the application, I can see that the process ID is the same, but the thread ID is different inside the pyads callback. The stacktrace is particularly interesting. Outside of the pyads callback, the call chain looks like:

runpy.py -> __main__.py -> cli.py -> pydevd_runpy.py -> my actual code

Inside the callback, though, the call chain looks like

pyads\pyads_ex.py -> my callback

I don't know much about pydevd, but I know that all the runpy.py -> __main__.py -> cli.py -> pydevd_runpy.py stuff is done by VS Code to set up the debugger. I would expect (though could easily be wrong) that pydevd would also need to be part of the call chain in the callback in order for my breakpoints to work.

Does anyone have any more insight on this, or is there something that I've misunderstood completely? Any thoughts on how I can set breakpoints in callbacks like this? Thanks!

Edit 07/06/2023 08:19 AM:

My VS Code launch configuration is very basic:

{
    "name": "run-my-thing",
    "type": "python",
    "request": "launch",
    "program": "${workspaceFolder}/run-my-thing.py",
    "console": "integratedTerminal",
    "justMyCode": true
}
1

There are 1 best solutions below

0
Roberto On

You got it right: pyads wraps your callback with the ctypes module and it gets called from the C DLL, I guess the Python debugger does not pick it up.

I managed to break inside a callback though with two different methods:

  1. With breakpoint(): by adding this as code inside the callback a console with pdb is popped up, where you can do a console debug. However, this doesn't seem to play together with PyCharm.
  2. By setting pydevd.settrace(): this does work with the PyCharm debugger.

To expand on 2, explicitly set the trace inside every callback thread and breakpoints will work as normal.

A complete example:

from pyads import Connection
import pyads
from time import sleep
import threading
import multiprocessing
import traceback
import sys

try:
    import pydevd
except ModuleNotFoundError:
    pydevd = None


def print_info():
    print("Trace:", threading.gettrace())
    print("Thread ID:", threading.get_ident())
    print("PID:", multiprocessing.current_process().pid)
    print("Callstack:")
    traceback.print_stack(file=sys.stdout)
    print()


def main():
    plc = Connection(
        ams_net_id="169.254.83.50.1.1",
        ams_net_port=851,
        ip_address="192.168.56.10",
    )

    print_info()

    @plc.notification(pyads.PLCTYPE_UDINT)
    def my_callback(handle, name, timestamp, value):
        if pydevd:
            pydevd.settrace()

        print("New value:", value)
        print_info()

    with plc:
        symbol = plc.get_symbol("MAIN.counter")

        symbol.add_device_notification(my_callback)

        sleep(10)

    return


if __name__ == "__main__":
    main()