Browse code

stop grabbing container locks during ps

Container queries are now served from the consistent in-memory db, and
don't need to grab a lock on every container being listed.

Signed-off-by: Fabio Kung <fabio.kung@gmail.com>

Fabio Kung authored on 2017/02/23 08:01:46
Showing 3 changed files
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"github.com/Sirupsen/logrus"
11 11
 	"github.com/docker/docker/api/types"
12 12
 	"github.com/docker/docker/api/types/filters"
13
-	networktypes "github.com/docker/docker/api/types/network"
14 13
 	"github.com/docker/docker/container"
15 14
 	"github.com/docker/docker/image"
16 15
 	"github.com/docker/docker/volume"
... ...
@@ -47,7 +46,7 @@ type iterationAction int
47 47
 
48 48
 // containerReducer represents a reducer for a container.
49 49
 // Returns the object to serialize by the api.
50
-type containerReducer func(*container.Container, *listContext) (*types.Container, error)
50
+type containerReducer func(*container.Snapshot, *listContext) (*types.Container, error)
51 51
 
52 52
 const (
53 53
 	// includeContainer is the action to include a container in the reducer.
... ...
@@ -83,9 +82,9 @@ type listContext struct {
83 83
 	exitAllowed []int
84 84
 
85 85
 	// beforeFilter is a filter to ignore containers that appear before the one given
86
-	beforeFilter *container.Container
86
+	beforeFilter *container.Snapshot
87 87
 	// sinceFilter is a filter to stop the filtering when the iterator arrive to the given container
88
-	sinceFilter *container.Container
88
+	sinceFilter *container.Snapshot
89 89
 
90 90
 	// taskFilter tells if we should filter based on wether a container is part of a task
91 91
 	taskFilter bool
... ...
@@ -102,7 +101,7 @@ type listContext struct {
102 102
 }
103 103
 
104 104
 // byContainerCreated is a temporary type used to sort a list of containers by creation time.
105
-type byContainerCreated []*container.Container
105
+type byContainerCreated []container.Snapshot
106 106
 
107 107
 func (r byContainerCreated) Len() int      { return len(r) }
108 108
 func (r byContainerCreated) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
... ...
@@ -115,7 +114,7 @@ func (daemon *Daemon) Containers(config *types.ContainerListOptions) ([]*types.C
115 115
 	return daemon.reduceContainers(config, daemon.transformContainer)
116 116
 }
117 117
 
118
-func (daemon *Daemon) filterByNameIDMatches(ctx *listContext) []*container.Container {
118
+func (daemon *Daemon) filterByNameIDMatches(view *container.View, ctx *listContext) ([]container.Snapshot, error) {
119 119
 	idSearch := false
120 120
 	names := ctx.filters.Get("name")
121 121
 	ids := ctx.filters.Get("id")
... ...
@@ -123,7 +122,9 @@ func (daemon *Daemon) filterByNameIDMatches(ctx *listContext) []*container.Conta
123 123
 		// if name or ID filters are not in use, return to
124 124
 		// standard behavior of walking the entire container
125 125
 		// list from the daemon's in-memory store
126
-		return daemon.List()
126
+		all, err := view.All()
127
+		sort.Sort(sort.Reverse(byContainerCreated(all)))
128
+		return all, err
127 129
 	}
128 130
 
129 131
 	// idSearch will determine if we limit name matching to the IDs
... ...
@@ -158,10 +159,14 @@ func (daemon *Daemon) filterByNameIDMatches(ctx *listContext) []*container.Conta
158 158
 		}
159 159
 	}
160 160
 
161
-	cntrs := make([]*container.Container, 0, len(matches))
161
+	cntrs := make([]container.Snapshot, 0, len(matches))
162 162
 	for id := range matches {
163
-		if c := daemon.containers.Get(id); c != nil {
164
-			cntrs = append(cntrs, c)
163
+		c, err := view.Get(id)
164
+		if err != nil {
165
+			return nil, err
166
+		}
167
+		if c != nil {
168
+			cntrs = append(cntrs, *c)
165 169
 		}
166 170
 	}
167 171
 
... ...
@@ -169,27 +174,31 @@ func (daemon *Daemon) filterByNameIDMatches(ctx *listContext) []*container.Conta
169 169
 	// Created gives us nanosec resolution for sorting
170 170
 	sort.Sort(sort.Reverse(byContainerCreated(cntrs)))
171 171
 
172
-	return cntrs
172
+	return cntrs, nil
173 173
 }
174 174
 
175 175
 // reduceContainers parses the user's filtering options and generates the list of containers to return based on a reducer.
176 176
 func (daemon *Daemon) reduceContainers(config *types.ContainerListOptions, reducer containerReducer) ([]*types.Container, error) {
177 177
 	var (
178
+		view       = daemon.containersReplica.Snapshot()
178 179
 		containers = []*types.Container{}
179 180
 	)
180 181
 
181
-	ctx, err := daemon.foldFilter(config)
182
+	ctx, err := daemon.foldFilter(view, config)
182 183
 	if err != nil {
183 184
 		return nil, err
184 185
 	}
185 186
 
186 187
 	// fastpath to only look at a subset of containers if specific name
187 188
 	// or ID matches were provided by the user--otherwise we potentially
188
-	// end up locking and querying many more containers than intended
189
-	containerList := daemon.filterByNameIDMatches(ctx)
189
+	// end up querying many more containers than intended
190
+	containerList, err := daemon.filterByNameIDMatches(view, ctx)
191
+	if err != nil {
192
+		return nil, err
193
+	}
190 194
 
191
-	for _, container := range containerList {
192
-		t, err := daemon.reducePsContainer(container, ctx, reducer)
195
+	for i := range containerList {
196
+		t, err := daemon.reducePsContainer(&containerList[i], ctx, reducer)
193 197
 		if err != nil {
194 198
 			if err != errStopIteration {
195 199
 				return nil, err
... ...
@@ -206,23 +215,17 @@ func (daemon *Daemon) reduceContainers(config *types.ContainerListOptions, reduc
206 206
 }
207 207
 
208 208
 // reducePsContainer is the basic representation for a container as expected by the ps command.
209
-func (daemon *Daemon) reducePsContainer(container *container.Container, ctx *listContext, reducer containerReducer) (*types.Container, error) {
210
-	container.Lock()
211
-
209
+func (daemon *Daemon) reducePsContainer(container *container.Snapshot, ctx *listContext, reducer containerReducer) (*types.Container, error) {
212 210
 	// filter containers to return
213
-	action := includeContainerInList(container, ctx)
214
-	switch action {
211
+	switch includeContainerInList(container, ctx) {
215 212
 	case excludeContainer:
216
-		container.Unlock()
217 213
 		return nil, nil
218 214
 	case stopIteration:
219
-		container.Unlock()
220 215
 		return nil, errStopIteration
221 216
 	}
222 217
 
223 218
 	// transform internal container struct into api structs
224 219
 	newC, err := reducer(container, ctx)
225
-	container.Unlock()
226 220
 	if err != nil {
227 221
 		return nil, err
228 222
 	}
... ...
@@ -237,7 +240,7 @@ func (daemon *Daemon) reducePsContainer(container *container.Container, ctx *lis
237 237
 }
238 238
 
239 239
 // foldFilter generates the container filter based on the user's filtering options.
240
-func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listContext, error) {
240
+func (daemon *Daemon) foldFilter(view *container.View, config *types.ContainerListOptions) (*listContext, error) {
241 241
 	psFilters := config.Filters
242 242
 
243 243
 	if err := psFilters.Validate(acceptedPsFilterTags); err != nil {
... ...
@@ -294,10 +297,10 @@ func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listConte
294 294
 		return nil, err
295 295
 	}
296 296
 
297
-	var beforeContFilter, sinceContFilter *container.Container
297
+	var beforeContFilter, sinceContFilter *container.Snapshot
298 298
 
299 299
 	err = psFilters.WalkValues("before", func(value string) error {
300
-		beforeContFilter, err = daemon.GetContainer(value)
300
+		beforeContFilter, err = view.Get(value)
301 301
 		return err
302 302
 	})
303 303
 	if err != nil {
... ...
@@ -305,7 +308,7 @@ func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listConte
305 305
 	}
306 306
 
307 307
 	err = psFilters.WalkValues("since", func(value string) error {
308
-		sinceContFilter, err = daemon.GetContainer(value)
308
+		sinceContFilter, err = view.Get(value)
309 309
 		return err
310 310
 	})
311 311
 	if err != nil {
... ...
@@ -383,7 +386,7 @@ func portOp(key string, filter map[nat.Port]bool) func(value string) error {
383 383
 
384 384
 // includeContainerInList decides whether a container should be included in the output or not based in the filter.
385 385
 // It also decides if the iteration should be stopped or not.
386
-func includeContainerInList(container *container.Container, ctx *listContext) iterationAction {
386
+func includeContainerInList(container *container.Snapshot, ctx *listContext) iterationAction {
387 387
 	// Do not include container if it's in the list before the filter container.
388 388
 	// Set the filter container to nil to include the rest of containers after this one.
389 389
 	if ctx.beforeFilter != nil {
... ...
@@ -422,7 +425,7 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
422 422
 	}
423 423
 
424 424
 	// Do not include container if any of the labels don't match
425
-	if !ctx.filters.MatchKVList("label", container.Config.Labels) {
425
+	if !ctx.filters.MatchKVList("label", container.Labels) {
426 426
 		return excludeContainer
427 427
 	}
428 428
 
... ...
@@ -440,7 +443,7 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
440 440
 	if len(ctx.exitAllowed) > 0 {
441 441
 		shouldSkip := true
442 442
 		for _, code := range ctx.exitAllowed {
443
-			if code == container.ExitCode() && !container.Running && !container.StartedAt.IsZero() {
443
+			if code == container.ExitCode && !container.Running && !container.StartedAt.IsZero() {
444 444
 				shouldSkip = false
445 445
 				break
446 446
 			}
... ...
@@ -451,28 +454,34 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
451 451
 	}
452 452
 
453 453
 	// Do not include container if its status doesn't match the filter
454
-	if !ctx.filters.Match("status", container.State.StateString()) {
454
+	if !ctx.filters.Match("status", container.State) {
455 455
 		return excludeContainer
456 456
 	}
457 457
 
458 458
 	// Do not include container if its health doesn't match the filter
459
-	if !ctx.filters.ExactMatch("health", container.State.HealthString()) {
459
+	if !ctx.filters.ExactMatch("health", container.Health) {
460 460
 		return excludeContainer
461 461
 	}
462 462
 
463 463
 	if ctx.filters.Include("volume") {
464
-		volumesByName := make(map[string]*volume.MountPoint)
465
-		for _, m := range container.MountPoints {
464
+		volumesByName := make(map[string]types.MountPoint)
465
+		for _, m := range container.Mounts {
466 466
 			if m.Name != "" {
467 467
 				volumesByName[m.Name] = m
468 468
 			} else {
469 469
 				volumesByName[m.Source] = m
470 470
 			}
471 471
 		}
472
+		volumesByDestination := make(map[string]types.MountPoint)
473
+		for _, m := range container.Mounts {
474
+			if m.Destination != "" {
475
+				volumesByDestination[m.Destination] = m
476
+			}
477
+		}
472 478
 
473 479
 		volumeExist := fmt.Errorf("volume mounted in container")
474 480
 		err := ctx.filters.WalkValues("volume", func(value string) error {
475
-			if _, exist := container.MountPoints[value]; exist {
481
+			if _, exist := volumesByDestination[value]; exist {
476 482
 				return volumeExist
477 483
 			}
478 484
 			if _, exist := volumesByName[value]; exist {
... ...
@@ -489,7 +498,7 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
489 489
 		if len(ctx.images) == 0 {
490 490
 			return excludeContainer
491 491
 		}
492
-		if !ctx.images[container.ImageID] {
492
+		if !ctx.images[image.ID(container.ImageID)] {
493 493
 			return excludeContainer
494 494
 		}
495 495
 	}
... ...
@@ -501,7 +510,7 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
501 501
 				return networkExist
502 502
 			}
503 503
 			for _, nw := range container.NetworkSettings.Networks {
504
-				if nw.EndpointSettings == nil {
504
+				if nw == nil {
505 505
 					continue
506 506
 				}
507 507
 				if strings.HasPrefix(nw.NetworkID, value) {
... ...
@@ -518,7 +527,7 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
518 518
 	if len(ctx.publish) > 0 {
519 519
 		shouldSkip := true
520 520
 		for port := range ctx.publish {
521
-			if _, ok := container.HostConfig.PortBindings[port]; ok {
521
+			if _, ok := container.PublishPorts[port]; ok {
522 522
 				shouldSkip = false
523 523
 				break
524 524
 			}
... ...
@@ -531,7 +540,7 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
531 531
 	if len(ctx.expose) > 0 {
532 532
 		shouldSkip := true
533 533
 		for port := range ctx.expose {
534
-			if _, ok := container.Config.ExposedPorts[port]; ok {
534
+			if _, ok := container.ExposedPorts[port]; ok {
535 535
 				shouldSkip = false
536 536
 				break
537 537
 			}
... ...
@@ -545,104 +554,38 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
545 545
 }
546 546
 
547 547
 // transformContainer generates the container type expected by the docker ps command.
548
-func (daemon *Daemon) transformContainer(container *container.Container, ctx *listContext) (*types.Container, error) {
548
+func (daemon *Daemon) transformContainer(container *container.Snapshot, ctx *listContext) (*types.Container, error) {
549 549
 	newC := &types.Container{
550
-		ID:      container.ID,
551
-		Names:   ctx.names[container.ID],
552
-		ImageID: container.ImageID.String(),
550
+		ID:              container.ID,
551
+		Names:           ctx.names[container.ID],
552
+		ImageID:         container.ImageID,
553
+		Command:         container.Command,
554
+		Created:         container.Created.Unix(),
555
+		State:           container.State,
556
+		Status:          container.Status,
557
+		NetworkSettings: &container.NetworkSettings,
558
+		Ports:           container.Ports,
559
+		Labels:          container.Labels,
560
+		Mounts:          container.Mounts,
553 561
 	}
554 562
 	if newC.Names == nil {
555 563
 		// Dead containers will often have no name, so make sure the response isn't null
556 564
 		newC.Names = []string{}
557 565
 	}
566
+	newC.HostConfig.NetworkMode = container.HostConfig.NetworkMode
558 567
 
559
-	image := container.Config.Image // if possible keep the original ref
560
-	if image != container.ImageID.String() {
568
+	image := container.Image // if possible keep the original ref
569
+	if image != container.ImageID {
561 570
 		id, _, err := daemon.GetImageIDAndPlatform(image)
562 571
 		if _, isDNE := err.(ErrImageDoesNotExist); err != nil && !isDNE {
563 572
 			return nil, err
564 573
 		}
565
-		if err != nil || id != container.ImageID {
566
-			image = container.ImageID.String()
574
+		if err != nil || id.String() != container.ImageID {
575
+			image = container.ImageID
567 576
 		}
568 577
 	}
569 578
 	newC.Image = image
570 579
 
571
-	if len(container.Args) > 0 {
572
-		args := []string{}
573
-		for _, arg := range container.Args {
574
-			if strings.Contains(arg, " ") {
575
-				args = append(args, fmt.Sprintf("'%s'", arg))
576
-			} else {
577
-				args = append(args, arg)
578
-			}
579
-		}
580
-		argsAsString := strings.Join(args, " ")
581
-
582
-		newC.Command = fmt.Sprintf("%s %s", container.Path, argsAsString)
583
-	} else {
584
-		newC.Command = container.Path
585
-	}
586
-	newC.Created = container.Created.Unix()
587
-	newC.State = container.State.StateString()
588
-	newC.Status = container.State.String()
589
-	newC.HostConfig.NetworkMode = string(container.HostConfig.NetworkMode)
590
-	// copy networks to avoid races
591
-	networks := make(map[string]*networktypes.EndpointSettings)
592
-	for name, network := range container.NetworkSettings.Networks {
593
-		if network == nil || network.EndpointSettings == nil {
594
-			continue
595
-		}
596
-		networks[name] = &networktypes.EndpointSettings{
597
-			EndpointID:          network.EndpointID,
598
-			Gateway:             network.Gateway,
599
-			IPAddress:           network.IPAddress,
600
-			IPPrefixLen:         network.IPPrefixLen,
601
-			IPv6Gateway:         network.IPv6Gateway,
602
-			GlobalIPv6Address:   network.GlobalIPv6Address,
603
-			GlobalIPv6PrefixLen: network.GlobalIPv6PrefixLen,
604
-			MacAddress:          network.MacAddress,
605
-			NetworkID:           network.NetworkID,
606
-		}
607
-		if network.IPAMConfig != nil {
608
-			networks[name].IPAMConfig = &networktypes.EndpointIPAMConfig{
609
-				IPv4Address: network.IPAMConfig.IPv4Address,
610
-				IPv6Address: network.IPAMConfig.IPv6Address,
611
-			}
612
-		}
613
-	}
614
-	newC.NetworkSettings = &types.SummaryNetworkSettings{Networks: networks}
615
-
616
-	newC.Ports = []types.Port{}
617
-	for port, bindings := range container.NetworkSettings.Ports {
618
-		p, err := nat.ParsePort(port.Port())
619
-		if err != nil {
620
-			return nil, err
621
-		}
622
-		if len(bindings) == 0 {
623
-			newC.Ports = append(newC.Ports, types.Port{
624
-				PrivatePort: uint16(p),
625
-				Type:        port.Proto(),
626
-			})
627
-			continue
628
-		}
629
-		for _, binding := range bindings {
630
-			h, err := nat.ParsePort(binding.HostPort)
631
-			if err != nil {
632
-				return nil, err
633
-			}
634
-			newC.Ports = append(newC.Ports, types.Port{
635
-				PrivatePort: uint16(p),
636
-				PublicPort:  uint16(h),
637
-				Type:        port.Proto(),
638
-				IP:          binding.HostIP,
639
-			})
640
-		}
641
-	}
642
-
643
-	newC.Labels = container.Config.Labels
644
-	newC.Mounts = addMountPoints(container)
645
-
646 580
 	return newC, nil
647 581
 }
648 582
 
... ...
@@ -6,6 +6,6 @@ import "github.com/docker/docker/container"
6 6
 
7 7
 // excludeByIsolation is a platform specific helper function to support PS
8 8
 // filtering by Isolation. This is a Windows-only concept, so is a no-op on Unix.
9
-func excludeByIsolation(container *container.Container, ctx *listContext) iterationAction {
9
+func excludeByIsolation(container *container.Snapshot, ctx *listContext) iterationAction {
10 10
 	return includeContainer
11 11
 }
... ...
@@ -8,7 +8,7 @@ import (
8 8
 
9 9
 // excludeByIsolation is a platform specific helper function to support PS
10 10
 // filtering by Isolation. This is a Windows-only concept, so is a no-op on Unix.
11
-func excludeByIsolation(container *container.Container, ctx *listContext) iterationAction {
11
+func excludeByIsolation(container *container.Snapshot, ctx *listContext) iterationAction {
12 12
 	i := strings.ToLower(string(container.HostConfig.Isolation))
13 13
 	if i == "" {
14 14
 		i = "default"