Skip to content

Proposal: Add functionality for debug adapter protocol #5938

Open
@jsternberg

Description

@jsternberg

Debug adapter protocol is a specification created by Microsoft here for the purpose of easing debugger integrations for IDE's and code editors.

It describes a protocol for an editor to communicate with a debugger. It is supported in most popular code editors including VSCode and Neovim.

Docker Build Debugging

When debugging a docker build, the most common method is to run a build up to a certain step and then use docker run to explore the image at the point where the problem occurs.

The goal of this project is to make debugging a build more natural and to facilitate a similar workflow where it is possible to inspect the resulting filesystem when an error occurs.

Requirements

  • Need some way to interact with the LLB definition.
  • Need a way to know when a specific LLB operation has failed.

Due to the way LLB is created, it is likely a requirement that the debug adapter have some ability to communicate with the frontend.

In addition, it might be needed to be able to communicate with buildkit itself to know when a specific LLB operation has failed and to be able to examine it.

One discussed alternative to these was to have the debug adapter be involved in sending the LLB to Buildkit and to send each step of a build as a separate solve request. When a solve request fails, we know which step failed and can inspect the image from before the build. While it would be possible to send chunks of LLB repeatedly, this may interact poorly with the build history and it would require the debug adapter to know about how the frontend works or interact with it more directly.

For this reason, this proposal opts to plumb the debug adapter protocol through buildkit rather than building it on top of buildkit.

Debugging Flow

At its core, the protocol causes the editor to launch a DAP server the editor interacts with. In our case, the buildkit client will act as the DAP server. When invoked in DAP mode, the client will initiate a debug session with buildkit before continuing. This will signal that any solve requests associated with that reference are intended to be handled by the debug session. This reference string is generated by the buildkit client. This reference matches the same one used for solve and status requests.

When buildkit invokes a frontend that has a debug session connected, it will initialize that frontend in debug adapter mode. Buildkit will then attempt to attach to the frontend. If the attachment succeeds, then the frontend supports the DAP protocol. If it does not, then execution will be abandoned with a message that the frontend does not support the DAP protocol.

At this point, we have initialized the connection. The editor can now use the DAP protocol to communicate with both the buildkit server and the frontend. The frontend can send information about any loaded sources and the buildkit server can communicate when execution is stopped due to hitting a breakpoint or an exception.

After the build is finished, the frontend closes. The buildkit server sends the exited event to the client which is forwarded to the editor. The session is then closed.

The below diagram represents a rough outline of how the sequence of events would be laid out for a build that had no error.

sequenceDiagram
	participant E as Editor
	participant C as Client
	participant S as Buildkit
	participant F as Frontend

	E->>+C: initialize request
	C->>+S: DebugSession
	C->>-E: initialize response
	E->>+C: launch request
	C->>+S: solve request
	S->>+F: launch frontend
	S->>+F: attach session
	F->>+S: solve request (loading dockerfile)
	S->>-F: solve response (loading dockerfile)
	F-->>E: initialized event
	E->>+F: configuration start
	F->>-E: configuration done
	C->>-E: launch response
	F->>+S: solve request
	S->>-F: solve response
	F->>-S: close session
	F->>-S: return request
	S->>-C: solve response
	S-->>-C: exited event
	C-->>E: exited event
Loading

In the case of an error, either buildkit or the frontend would be capable of sending DAP events to the editor to indicate that execution has stopped. The editor would then be able to send requests to ascertain the state of the build.

Concepts

The debug adapter protocol is primarily meant for programming languages. That causes some of the listed types to not make complete sense when referring to what is a build system. At the same time, there are some useful types that can map pretty directly and allow us to reuse the infrastructure from DAP.

StackFrame

A stack frame corresponds to the listing of an operation. A stack frame only has one frame.

Thread

A thread corresponds to the invocation of a single operation. Each operation is separated into its own thread. When an operation starts, it creates a new thread. When it finishes, the thread is terminated.

Exception

An exception is something that is caused when a build step fails. This is usually an error but it could also be a build cancellation. By default, the client adapter will set an ExceptionBreakMode to unhandled. All exceptions are considered unhandled in a dockerfile.

Scope

Corresponds to a single operation similar to the stack frame.

Variable

Corresponds to the filesystem. Accessing variables is akin to accessing the filesystem.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Blocked

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions