Skip to content

How notebook debug cell works

Rich Chiodo edited this page Jul 22, 2022 · 16 revisions

Notebook cell debugging is similar to how python debugging works. However it uses a DebugAdapter instead of a DebugAdapterDescriptor.

There's a number of classes involved. This sequence diagram describes how they interact:

image

These different steps are described in detail below:

Debug cell clicked

The first step is the user clicking the 'Debug cell' button.

The command handler then generates a launch.json entry that looks something like so:

        {
            "type": "Python Kernel Debug Adapter",
            "name": "mynotebook.ipynb",
            "request": "attach",
            "justMyCode": true,
            "__mode": "debugCell",
            "__cellIndex": 2
        }

where

Create of Debugger with an attach

The debugging manager then creates a DebuggingManager with this configuration.

Launch attach debug session

The DebuggingManager just starts debugging with the configuration. This switches VS code into debug mode.

Creation of KernelDebugAdapter

Because a DebugAdapterDescriptorFactory was registered and its type was passed in the launch config, VS code will then attempt to create a DebugAdapterDescriptor.

The KernelDebugAdapter is created as a result of this call and passed as the implementation to the [DebugAdapterInlineImplementation] https://code.visualstudio.com/api/references/vscode-api#DebugAdapterInlineImplementation

The KernelDebugAdapter is an in memory server that is supposed to listen to DAP messages. For more information on handling DAP messages, see the description of Python debugging.

Creation of DebugCellController

The KernelDebugAdapter is shared between cell debugging and run by line, the only difference being the controller. The controllers job is to intercept certain messages from VS code (and certain responses from debugpy) to force the behavior necessary for the mode it is in. For the DebugCellController, this means behaving just like ordinary python debugging.

return DebugAdapter

After the KernelDebugAdapter and DebugCellController are created, the DebugAdapterInlineImplementation is returned to VS code. It will now use it to send all DAP messages to control the debug session.

handleMessage Initialize

The first DAP message is Initialize.

The KernelDebugAdapter's handleMessage is called with the contents of the Initialize message.

Translate file paths

The KernelDebugAdapter then parses the contents of the message and translates the cell URI's in it to the expected file name paths debugpy will be seeing in IPython.

How does it know the file paths? IPython has a new special message - dumpCell. This takes the contents of a cell and generates a temporary file for it. That temporary file is used as the translated file path.

requestDebug - Initialize

The Initialize message is then sent to IPython by making the jupyter [requestDebug](https://jupyter-client.readthedocs.io/en/latest/messaging.html#debug-request) call.

Now you might wonder did IPython implement its own debugger?

No, it actually loads debugpy into itself and forwards DAP requests to debugpy.

requestDebug - Initialize response

Since the requestDebug is just a normal jupyter message, the response is handled just like an execute.

willSendEvent?

At this point the KernelDebugAdapter has the response from IPython. It then gives the DebugCellController the chance to swallow the message (or send a different response). For Initialize the message is ignored.

onDidSendMessage - Initialize response

The KernelDebugAdapter then fires an event for VS code to handle that contains the response.

This same pattern is used to handle requests and responses until debugging is setup.

onIOPubMessage: stopped event

At some point debugpy will need to send an event to VS code to indicate the process has stopped. Since there's no 'event' mechanism in the JMP, an out of bound message is delivered on the IO Pub channel.

willSendEvent?

Just like with the responses to DAP messages, the DebugCellController is given the chance to swallow or change the stopped event. This is where the RunByLineController (if it was used here) would make a different choice and alter the behavior of the stopped event.

onDidSendMessage - stopped

The stopped event is forwarded onto VS code, indicating that the debugger should stop. After this event is received in VS code, just like with Python, VS code will then ask the KernelDebugAdapter for variables, stack frames, threads, and modules.

Clone this wiki locally