Browse code

graph: add parent img refcount for faster rmi

also fix a typo in pkg/truncindex package comment

Signed-off-by: Antonio Murdaca <runcom@redhat.com>

Antonio Murdaca authored on 2015/10/10 02:13:45
Showing 5 changed files
... ...
@@ -193,7 +193,7 @@ func (d Docker) Copy(c *daemon.Container, destPath string, src builder.FileInfo,
193 193
 // GetCachedImage returns a reference to a cached image whose parent equals `parent`
194 194
 // and runconfig equals `cfg`. A cache miss is expected to return an empty ID and a nil error.
195 195
 func (d Docker) GetCachedImage(imgID string, cfg *runconfig.Config) (string, error) {
196
-	cache, err := d.Daemon.ImageGetCached(string(imgID), cfg)
196
+	cache, err := d.Daemon.ImageGetCached(imgID, cfg)
197 197
 	if cache == nil || err != nil {
198 198
 		return "", err
199 199
 	}
... ...
@@ -279,7 +279,7 @@ func (daemon *Daemon) checkImageDeleteHardConflict(img *image.Image) *imageDelet
279 279
 	}
280 280
 
281 281
 	// Check if the image has any descendent images.
282
-	if daemon.Graph().HasChildren(img) {
282
+	if daemon.Graph().HasChildren(img.ID) {
283 283
 		return &imageDeleteConflict{
284 284
 			hard:    true,
285 285
 			imgID:   img.ID,
... ...
@@ -337,5 +337,5 @@ func (daemon *Daemon) checkImageDeleteSoftConflict(img *image.Image) *imageDelet
337 337
 // that there are no repository references to the given image and it has no
338 338
 // child images.
339 339
 func (daemon *Daemon) imageIsDangling(img *image.Image) bool {
340
-	return !(daemon.repositories.HasReferences(img) || daemon.Graph().HasChildren(img))
340
+	return !(daemon.Repositories().HasReferences(img) || daemon.Graph().HasChildren(img.ID))
341 341
 }
... ...
@@ -104,6 +104,7 @@ type Graph struct {
104 104
 	tarSplitDisabled bool
105 105
 	uidMaps          []idtools.IDMap
106 106
 	gidMaps          []idtools.IDMap
107
+	parentRefs       map[string]int
107 108
 }
108 109
 
109 110
 // file names for ./graph/<ID>/
... ...
@@ -141,12 +142,13 @@ func NewGraph(root string, driver graphdriver.Driver, uidMaps, gidMaps []idtools
141 141
 	}
142 142
 
143 143
 	graph := &Graph{
144
-		root:     abspath,
145
-		idIndex:  truncindex.NewTruncIndex([]string{}),
146
-		driver:   driver,
147
-		retained: &retainedLayers{layerHolders: make(map[string]map[string]struct{})},
148
-		uidMaps:  uidMaps,
149
-		gidMaps:  gidMaps,
144
+		root:       abspath,
145
+		idIndex:    truncindex.NewTruncIndex([]string{}),
146
+		driver:     driver,
147
+		retained:   &retainedLayers{layerHolders: make(map[string]map[string]struct{})},
148
+		uidMaps:    uidMaps,
149
+		gidMaps:    gidMaps,
150
+		parentRefs: make(map[string]int),
150 151
 	}
151 152
 
152 153
 	// Windows does not currently support tarsplit functionality.
... ...
@@ -174,6 +176,20 @@ func (graph *Graph) restore() error {
174 174
 	for _, v := range dir {
175 175
 		id := v.Name()
176 176
 		if graph.driver.Exists(id) {
177
+			pth := filepath.Join(graph.root, id, "json")
178
+			jsonSource, err := os.Open(pth)
179
+			if err != nil {
180
+				return err
181
+			}
182
+			defer jsonSource.Close()
183
+			decoder := json.NewDecoder(jsonSource)
184
+			var img *image.Image
185
+			if err := decoder.Decode(img); err != nil {
186
+				return err
187
+			}
188
+			graph.imageMutex.Lock(img.Parent)
189
+			graph.parentRefs[img.Parent]++
190
+			graph.imageMutex.Unlock(img.Parent)
177 191
 			ids = append(ids, id)
178 192
 		}
179 193
 	}
... ...
@@ -326,7 +342,13 @@ func (graph *Graph) register(im image.Descriptor, layerData io.Reader) (err erro
326 326
 	if err := os.Rename(tmp, graph.imageRoot(imgID)); err != nil {
327 327
 		return err
328 328
 	}
329
-	graph.idIndex.Add(imgID)
329
+
330
+	graph.idIndex.Add(img.ID)
331
+
332
+	graph.imageMutex.Lock(img.Parent)
333
+	graph.parentRefs[img.Parent]++
334
+	graph.imageMutex.Unlock(img.Parent)
335
+
330 336
 	return nil
331 337
 }
332 338
 
... ...
@@ -393,6 +415,10 @@ func (graph *Graph) Delete(name string) error {
393 393
 	if err != nil {
394 394
 		return err
395 395
 	}
396
+	img, err := graph.Get(id)
397
+	if err != nil {
398
+		return err
399
+	}
396 400
 	tmp, err := graph.mktemp()
397 401
 	graph.idIndex.Delete(id)
398 402
 	if err == nil {
... ...
@@ -407,6 +433,14 @@ func (graph *Graph) Delete(name string) error {
407 407
 	}
408 408
 	// Remove rootfs data from the driver
409 409
 	graph.driver.Remove(id)
410
+
411
+	graph.imageMutex.Lock(img.Parent)
412
+	graph.parentRefs[img.Parent]--
413
+	if graph.parentRefs[img.Parent] == 0 {
414
+		delete(graph.parentRefs, img.Parent)
415
+	}
416
+	graph.imageMutex.Unlock(img.Parent)
417
+
410 418
 	// Remove the trashed image directory
411 419
 	return os.RemoveAll(tmp)
412 420
 }
... ...
@@ -424,9 +458,11 @@ func (graph *Graph) Map() map[string]*image.Image {
424 424
 // The walking order is undetermined.
425 425
 func (graph *Graph) walkAll(handler func(*image.Image)) {
426 426
 	graph.idIndex.Iterate(func(id string) {
427
-		if img, err := graph.Get(id); err != nil {
427
+		img, err := graph.Get(id)
428
+		if err != nil {
428 429
 			return
429
-		} else if handler != nil {
430
+		}
431
+		if handler != nil {
430 432
 			handler(img)
431 433
 		}
432 434
 	})
... ...
@@ -453,8 +489,11 @@ func (graph *Graph) ByParent() map[string][]*image.Image {
453 453
 }
454 454
 
455 455
 // HasChildren returns whether the given image has any child images.
456
-func (graph *Graph) HasChildren(img *image.Image) bool {
457
-	return len(graph.ByParent()[img.ID]) > 0
456
+func (graph *Graph) HasChildren(imgID string) bool {
457
+	graph.imageMutex.Lock(imgID)
458
+	count := graph.parentRefs[imgID]
459
+	graph.imageMutex.Unlock(imgID)
460
+	return count > 0
458 461
 }
459 462
 
460 463
 // Retain keeps the images and layers that are in the pulling chain so that
... ...
@@ -472,11 +511,10 @@ func (graph *Graph) Release(sessionID string, layerIDs ...string) {
472 472
 // A head is an image which is not the parent of another image in the graph.
473 473
 func (graph *Graph) Heads() map[string]*image.Image {
474 474
 	heads := make(map[string]*image.Image)
475
-	byParent := graph.ByParent()
476 475
 	graph.walkAll(func(image *image.Image) {
477 476
 		// If it's not in the byParent lookup table, then
478 477
 		// it's not a parent -> so it's a head!
479
-		if _, exists := byParent[image.ID]; !exists {
478
+		if !graph.HasChildren(image.ID) {
480 479
 			heads[image.ID] = image
481 480
 		}
482 481
 	})
... ...
@@ -51,7 +51,6 @@ func (s *DockerSuite) TestImagesEnsureImageWithBadTagIsNotListed(c *check.C) {
51 51
 	if strings.Contains(out, "busybox") {
52 52
 		c.Fatal("images should not have listed busybox")
53 53
 	}
54
-
55 54
 }
56 55
 
57 56
 func (s *DockerSuite) TestImagesOrderedByCreationDate(c *check.C) {
... ...
@@ -204,3 +203,42 @@ func (s *DockerSuite) TestImagesWithIncorrectFilter(c *check.C) {
204 204
 	c.Assert(err, check.NotNil)
205 205
 	c.Assert(out, checker.Contains, "Invalid filter")
206 206
 }
207
+
208
+func (s *DockerSuite) TestImagesEnsureOnlyHeadsImagesShown(c *check.C) {
209
+	testRequires(c, DaemonIsLinux)
210
+
211
+	dockerfile := `
212
+        FROM scratch
213
+        MAINTAINER docker
214
+        ENV foo bar`
215
+
216
+	head, out, err := buildImageWithOut("scratch-image", dockerfile, false)
217
+	c.Assert(err, check.IsNil)
218
+
219
+	split := strings.Split(out, "\n")
220
+	intermediate := strings.TrimSpace(split[5][7:])
221
+
222
+	out, _ = dockerCmd(c, "images")
223
+	if strings.Contains(out, intermediate) {
224
+		c.Fatalf("images shouldn't show non-heads images, got %s in %s", intermediate, out)
225
+	}
226
+	if !strings.Contains(out, head[:12]) {
227
+		c.Fatalf("images should contain final built images, want %s in out, got %s", head[:12], out)
228
+	}
229
+}
230
+
231
+func (s *DockerSuite) TestImagesEnsureImagesFromScratchShown(c *check.C) {
232
+	testRequires(c, DaemonIsLinux)
233
+
234
+	dockerfile := `
235
+        FROM scratch
236
+        MAINTAINER docker`
237
+
238
+	id, _, err := buildImageWithOut("scratch-image", dockerfile, false)
239
+	c.Assert(err, check.IsNil)
240
+
241
+	out, _ := dockerCmd(c, "images")
242
+	if !strings.Contains(out, id[:12]) {
243
+		c.Fatalf("images should contain images built from scratch (e.g. %s), got %s", id[:12], out)
244
+	}
245
+}
... ...
@@ -284,3 +284,15 @@ RUN echo 2 #layer2
284 284
 	// should be allowed to untag with the -f flag
285 285
 	c.Assert(out, checker.Contains, fmt.Sprintf("Untagged: %s:latest", newTag))
286 286
 }
287
+
288
+func (*DockerSuite) TestRmiParentImageFail(c *check.C) {
289
+	testRequires(c, DaemonIsLinux)
290
+
291
+	parent, err := inspectField("busybox", "Parent")
292
+	c.Assert(err, check.IsNil)
293
+	out, _, err := dockerCmdWithError("rmi", parent)
294
+	c.Assert(err, check.NotNil)
295
+	if !strings.Contains(out, "image has dependent child images") {
296
+		c.Fatalf("rmi should have failed because it's a parent image, got %s", out)
297
+	}
298
+}