Skip to content

Feature detection caches features as not available if a builder is polled before it is fully initialized #3266

@cowwoc

Description

@cowwoc

Contributing guidelines

I've found a bug and checked that ...

  • ... the documentation does not mention anything about my problem
  • ... there are no open or closed issues that are related to my problem

Description

When running docker buildx build --platform linux/amd64 --platform linux/arm64 --type=docker . against a docker:dind container, I sometimes get:

ERROR: failed to build: docker exporter does not currently support exporting manifest lists

But this happens intermittently, and only under load, which seems to imply that the error message is incorrect. Maybe the builder isn't fully initialized or something, but it's returning an error message implying that the driver is incompatible. This can't be given that most of the time the same test passes just fine and I am using the default builder driver every time.

Per @thaJeztah:

Looks like it does feature detection here;

buildx/build/opt.go

Lines 224 to 233 in 1d7cda1

features := docker.Features(ctx, e.Attrs["context"])
if features[dockerutil.OCIImporter] && e.Output == nil {
// rely on oci importer if available (which supports
// multi-platform images), otherwise fall back to docker
opt.Exports[i].Type = "oci"
} else if len(opt.Platforms) > 1 || len(attests) > 0 {
if e.Output != nil {
return nil, nil, errors.Errorf("docker exporter does not support exporting manifest lists, use the oci exporter instead")
}
return nil, nil, errors.Errorf("docker exporter does not currently support exporting manifest lists")

Looking at that code, it uses a sync.Once, and discards errors (if ... err == nil), so if it has a failure when trying to detect features the first time, it will discard the error, and considers it “not supported” for the duration of the buildx instance ;
func (c *Client) Features(ctx context.Context, name string) map[Feature]bool {
c.featuresOnce.Do(func() {
c.featuresCache = c.features(ctx, name)
})
return c.featuresCache
}
func (c *Client) features(ctx context.Context, name string) map[Feature]bool {
features := make(map[Feature]bool)
if dapi, err := c.API(name); err == nil {
if info, err := dapi.Info(ctx); err == nil {
for _, v := range info.DriverStatus {
switch v[0] {
case "driver-type":
if v[1] == "io.containerd.snapshotter.v1" {
features[OCIImporter] = true
}
}

If the only way to check whether a builder is ready for use is a retry-loop then this will result in the builder's feature getting stripped away for the lifetime of the builder which is bad.

Expected behaviour

The result of features detection should only be cached if the builder is fully initialized

Actual behaviour

If an error occurs while communicating with a builder, a feature is marked as "not available" forever

Buildx version

github.com/docker/buildx v0.25.0 faaea65

Docker info


Builders list

NAME/NODE           DRIVER/ENDPOINT     STATUS    BUILDKIT   PLATFORMS
default             docker
 \_ default          \_ default         running   v0.22.0    linux/amd64 (+3), linux/arm64, linux/arm (+2), linux/ppc64le, (2 more)
desktop-linux*      docker
 \_ desktop-linux    \_ desktop-linux   running   v0.22.0    linux/amd64 (+3), linux/arm64, linux/arm (+2), linux/ppc64le, (2 more)

Configuration

N/A

Build logs


Additional info

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions