Saturday, March 10, 2018

Thread Debugging

The next version of PyScripter will contain support for debugging multi-threaded python applications.  This blog post provides a quick preview of how thread debugging works.  The following python script will be used for demonstration purposes:

import time
import threading
import logging
logging.basicConfig(level=logging.DEBUG,
                    format='(%(threadName)-10s) %(message)s',
                    )
class MyThread(threading.Thread):
    def run(self):
        logging.debug('running')
        time.sleep(3)
        return
threads =[]
for i in range(5):
    t = MyThread()
    t.start()
    threads += [t]
for t in threads:
    t.join()

The scripts creates and starts 5 threads and waits for the threads to finish.  The following layout is suggested for debugging:


At the top you have the Call Stack and Variables windows and at the bottom the Interpreter and the Watches windows.  The Call Stack window includes a list of active threads.

We have placed a breakpoint inside the run method of our thread and one at the loop that waits for the threads to finish.  After we start debugging (F9 function key) we can see threads getting added to the Threads list as they are created.  After a short-while all 5 created threads will be stopped at the breakpoint and the Main Thread will stop at the second breakpoint.   The Call Stack Window will look like this:


The pinned thread is the active "broken" thread and the pinned frame is the active frame of the active thread.  The Variable window displays the namespace of the active frame and the Watches window evaluates watch expressions inside the active frame.   Also commands we issue in the interpreter window and debugger hints (hovering the mouse on variable names in the editor) are also evaluated inside the active frame.   We can change the active thread and the active frame be selecting with the mouse a different one.  For example this is what the Call Stack window looks like if we activate Thread-4.


The editor window shows the statement at which the active broken thread has stopped.


The Resume command (F9) resumes execution of all broken threads.  All other debug commands (e.g. Step in, Step over, Step out) resume execution of the active thread only.  For example if we select the Main Thread and select Step Over a couple of times the main thread will lock waiting for the other threads to finish.   This is what the Call Stack will look like:


The Main thread is in running state, but the other threads are broken.  We can step over or into any of the broken threads or we can resume execution of all "broken" threads pressing (F9).  If we press F9 we can observe the threads going into running state and then disappearing one after the other as they get finished. Eventually execution will stop at the breakpoint of the Main Thread which will become "broken".  After a few steps (F8) the python script will exit and debugging will be completed.

PyScripter makes thread debugging easy and fun!




No comments: