Skip to content

Commit 7175d9e

Browse files
committed
libimage: fix manifest race during listing
I saw a flake in parallel podman testing, podman images can fail if the manifest was removed at the right time. In general listing should never be able to fail when another image or manifest is removed in parallel. Change the logic to convert to manifest and only collect the digests in the success case and ignore all other errors to make the listing more robust. I observed the following error from podman images: Error: locating image "xxx" for loading instance list: locating image with ID "xxx": image not known Signed-off-by: Paul Holzinger <pholzing@redhat.com>
1 parent 0b7e12a commit 7175d9e

File tree

5 files changed

+25
-16
lines changed

5 files changed

+25
-16
lines changed

libimage/history.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func (i *Image) History(ctx context.Context) ([]ImageHistory, error) {
2525
return nil, err
2626
}
2727

28-
layerTree, err := i.runtime.newFreshLayerTree(ctx)
28+
layerTree, err := i.runtime.newFreshLayerTree()
2929
if err != nil {
3030
return nil, err
3131
}

libimage/image.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ func (i *Image) IsDangling(ctx context.Context) (bool, error) {
197197
if err != nil {
198198
return false, err
199199
}
200-
tree, err := i.runtime.newLayerTreeFromData(ctx, images, layers, true)
200+
tree, err := i.runtime.newLayerTreeFromData(images, layers, true)
201201
if err != nil {
202202
return false, err
203203
}
@@ -267,7 +267,7 @@ func (i *Image) TopLayer() string {
267267

268268
// Parent returns the parent image or nil if there is none
269269
func (i *Image) Parent(ctx context.Context) (*Image, error) {
270-
tree, err := i.runtime.newFreshLayerTree(ctx)
270+
tree, err := i.runtime.newFreshLayerTree()
271271
if err != nil {
272272
return nil, err
273273
}
@@ -301,7 +301,7 @@ func (i *Image) Children(ctx context.Context) ([]*Image, error) {
301301
// created for this invocation only.
302302
func (i *Image) getChildren(ctx context.Context, all bool, tree *layerTree) ([]*Image, error) {
303303
if tree == nil {
304-
t, err := i.runtime.newFreshLayerTree(ctx)
304+
t, err := i.runtime.newFreshLayerTree()
305305
if err != nil {
306306
return nil, err
307307
}

libimage/image_tree.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func (i *Image) Tree(ctx context.Context, traverseChildren bool) (string, error)
3838
fmt.Fprintf(sb, "No Image Layers")
3939
}
4040

41-
layerTree, err := i.runtime.newFreshLayerTree(ctx)
41+
layerTree, err := i.runtime.newFreshLayerTree()
4242
if err != nil {
4343
return "", err
4444
}

libimage/layer_tree.go

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,17 @@ func (l *layerNode) repoTags() ([]string, error) {
9595
}
9696

9797
// newFreshLayerTree extracts a layerTree from consistent layers and images in the local storage.
98-
func (r *Runtime) newFreshLayerTree(ctx context.Context) (*layerTree, error) {
98+
func (r *Runtime) newFreshLayerTree() (*layerTree, error) {
9999
images, layers, err := r.getImagesAndLayers()
100100
if err != nil {
101101
return nil, err
102102
}
103-
return r.newLayerTreeFromData(ctx, images, layers, false)
103+
return r.newLayerTreeFromData(images, layers, false)
104104
}
105105

106106
// newLayerTreeFromData extracts a layerTree from the given the layers and images.
107107
// The caller is responsible for (layers, images) being consistent.
108-
func (r *Runtime) newLayerTreeFromData(ctx context.Context, images []*Image, layers []storage.Layer, generateManifestDigestList bool) (*layerTree, error) {
108+
func (r *Runtime) newLayerTreeFromData(images []*Image, layers []storage.Layer, generateManifestDigestList bool) (*layerTree, error) {
109109
tree := layerTree{
110110
nodes: make(map[string]*layerNode),
111111
ociCache: make(map[string]*ociv1.Image),
@@ -136,14 +136,23 @@ func (r *Runtime) newLayerTreeFromData(ctx context.Context, images []*Image, lay
136136
if !generateManifestDigestList {
137137
continue
138138
}
139-
if manifestList, _ := img.IsManifestList(ctx); manifestList {
140-
mlist, err := img.ToManifestList()
141-
if err != nil {
142-
return nil, err
143-
}
144-
for _, digest := range mlist.list.Instances() {
145-
tree.manifestListDigests[digest] = struct{}{}
139+
// ignore errors, common errors are
140+
// - image is not manifest
141+
// - image has been removed from the store in the meantime
142+
// In all cases we should ensure image listing still works and not error out.
143+
mlist, err := img.ToManifestList()
144+
if err != nil {
145+
// If it is not a manifest it likely is a regular image so just ignore it.
146+
// If the image is unknown that likely means there was a race where the image/manifest
147+
// was removed after out MultiList() call so we ignore that as well.
148+
if errors.Is(err, ErrNotAManifestList) || errors.Is(err, storageTypes.ErrImageUnknown) {
149+
continue
146150
}
151+
return nil, err
152+
}
153+
154+
for _, digest := range mlist.list.Instances() {
155+
tree.manifestListDigests[digest] = struct{}{}
147156
}
148157
continue
149158
}

libimage/runtime.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,7 @@ func (r *Runtime) ListImages(ctx context.Context, options *ListImagesOptions) ([
634634

635635
var tree *layerTree
636636
if needsLayerTree {
637-
tree, err = r.newLayerTreeFromData(ctx, images, snapshot.Layers, true)
637+
tree, err = r.newLayerTreeFromData(images, snapshot.Layers, true)
638638
if err != nil {
639639
return nil, err
640640
}

0 commit comments

Comments
 (0)