Skip to content

Commit 8e425eb

Browse files
committed
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 <[email protected]>
1 parent eed4c7b commit 8e425eb

File tree

3 files changed

+68
-125
lines changed

3 files changed

+68
-125
lines changed

daemon/list.go

Lines changed: 66 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"github.com/Sirupsen/logrus"
1111
"github.com/docker/docker/api/types"
1212
"github.com/docker/docker/api/types/filters"
13-
networktypes "github.com/docker/docker/api/types/network"
1413
"github.com/docker/docker/container"
1514
"github.com/docker/docker/image"
1615
"github.com/docker/docker/volume"
@@ -47,7 +46,7 @@ type iterationAction int
4746

4847
// containerReducer represents a reducer for a container.
4948
// Returns the object to serialize by the api.
50-
type containerReducer func(*container.Container, *listContext) (*types.Container, error)
49+
type containerReducer func(*container.Snapshot, *listContext) (*types.Container, error)
5150

5251
const (
5352
// includeContainer is the action to include a container in the reducer.
@@ -83,9 +82,9 @@ type listContext struct {
8382
exitAllowed []int
8483

8584
// beforeFilter is a filter to ignore containers that appear before the one given
86-
beforeFilter *container.Container
85+
beforeFilter *container.Snapshot
8786
// sinceFilter is a filter to stop the filtering when the iterator arrive to the given container
88-
sinceFilter *container.Container
87+
sinceFilter *container.Snapshot
8988

9089
// taskFilter tells if we should filter based on wether a container is part of a task
9190
taskFilter bool
@@ -102,7 +101,7 @@ type listContext struct {
102101
}
103102

104103
// byContainerCreated is a temporary type used to sort a list of containers by creation time.
105-
type byContainerCreated []*container.Container
104+
type byContainerCreated []container.Snapshot
106105

107106
func (r byContainerCreated) Len() int { return len(r) }
108107
func (r byContainerCreated) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
@@ -115,15 +114,17 @@ func (daemon *Daemon) Containers(config *types.ContainerListOptions) ([]*types.C
115114
return daemon.reduceContainers(config, daemon.transformContainer)
116115
}
117116

118-
func (daemon *Daemon) filterByNameIDMatches(ctx *listContext) []*container.Container {
117+
func (daemon *Daemon) filterByNameIDMatches(view *container.View, ctx *listContext) ([]container.Snapshot, error) {
119118
idSearch := false
120119
names := ctx.filters.Get("name")
121120
ids := ctx.filters.Get("id")
122121
if len(names)+len(ids) == 0 {
123122
// if name or ID filters are not in use, return to
124123
// standard behavior of walking the entire container
125124
// list from the daemon's in-memory store
126-
return daemon.List()
125+
all, err := view.All()
126+
sort.Sort(sort.Reverse(byContainerCreated(all)))
127+
return all, err
127128
}
128129

129130
// idSearch will determine if we limit name matching to the IDs
@@ -158,38 +159,46 @@ func (daemon *Daemon) filterByNameIDMatches(ctx *listContext) []*container.Conta
158159
}
159160
}
160161

161-
cntrs := make([]*container.Container, 0, len(matches))
162+
cntrs := make([]container.Snapshot, 0, len(matches))
162163
for id := range matches {
163-
if c := daemon.containers.Get(id); c != nil {
164-
cntrs = append(cntrs, c)
164+
c, err := view.Get(id)
165+
if err != nil {
166+
return nil, err
167+
}
168+
if c != nil {
169+
cntrs = append(cntrs, *c)
165170
}
166171
}
167172

168173
// Restore sort-order after filtering
169174
// Created gives us nanosec resolution for sorting
170175
sort.Sort(sort.Reverse(byContainerCreated(cntrs)))
171176

172-
return cntrs
177+
return cntrs, nil
173178
}
174179

175180
// reduceContainers parses the user's filtering options and generates the list of containers to return based on a reducer.
176181
func (daemon *Daemon) reduceContainers(config *types.ContainerListOptions, reducer containerReducer) ([]*types.Container, error) {
177182
var (
183+
view = daemon.containersReplica.Snapshot()
178184
containers = []*types.Container{}
179185
)
180186

181-
ctx, err := daemon.foldFilter(config)
187+
ctx, err := daemon.foldFilter(view, config)
182188
if err != nil {
183189
return nil, err
184190
}
185191

186192
// fastpath to only look at a subset of containers if specific name
187193
// 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)
194+
// end up querying many more containers than intended
195+
containerList, err := daemon.filterByNameIDMatches(view, ctx)
196+
if err != nil {
197+
return nil, err
198+
}
190199

191-
for _, container := range containerList {
192-
t, err := daemon.reducePsContainer(container, ctx, reducer)
200+
for i := range containerList {
201+
t, err := daemon.reducePsContainer(&containerList[i], ctx, reducer)
193202
if err != nil {
194203
if err != errStopIteration {
195204
return nil, err
@@ -206,23 +215,17 @@ func (daemon *Daemon) reduceContainers(config *types.ContainerListOptions, reduc
206215
}
207216

208217
// 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-
218+
func (daemon *Daemon) reducePsContainer(container *container.Snapshot, ctx *listContext, reducer containerReducer) (*types.Container, error) {
212219
// filter containers to return
213-
action := includeContainerInList(container, ctx)
214-
switch action {
220+
switch includeContainerInList(container, ctx) {
215221
case excludeContainer:
216-
container.Unlock()
217222
return nil, nil
218223
case stopIteration:
219-
container.Unlock()
220224
return nil, errStopIteration
221225
}
222226

223227
// transform internal container struct into api structs
224228
newC, err := reducer(container, ctx)
225-
container.Unlock()
226229
if err != nil {
227230
return nil, err
228231
}
@@ -237,7 +240,7 @@ func (daemon *Daemon) reducePsContainer(container *container.Container, ctx *lis
237240
}
238241

239242
// foldFilter generates the container filter based on the user's filtering options.
240-
func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listContext, error) {
243+
func (daemon *Daemon) foldFilter(view *container.View, config *types.ContainerListOptions) (*listContext, error) {
241244
psFilters := config.Filters
242245

243246
if err := psFilters.Validate(acceptedPsFilterTags); err != nil {
@@ -294,18 +297,18 @@ func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listConte
294297
return nil, err
295298
}
296299

297-
var beforeContFilter, sinceContFilter *container.Container
300+
var beforeContFilter, sinceContFilter *container.Snapshot
298301

299302
err = psFilters.WalkValues("before", func(value string) error {
300-
beforeContFilter, err = daemon.GetContainer(value)
303+
beforeContFilter, err = view.Get(value)
301304
return err
302305
})
303306
if err != nil {
304307
return nil, err
305308
}
306309

307310
err = psFilters.WalkValues("since", func(value string) error {
308-
sinceContFilter, err = daemon.GetContainer(value)
311+
sinceContFilter, err = view.Get(value)
309312
return err
310313
})
311314
if err != nil {
@@ -383,7 +386,7 @@ func portOp(key string, filter map[nat.Port]bool) func(value string) error {
383386

384387
// includeContainerInList decides whether a container should be included in the output or not based in the filter.
385388
// It also decides if the iteration should be stopped or not.
386-
func includeContainerInList(container *container.Container, ctx *listContext) iterationAction {
389+
func includeContainerInList(container *container.Snapshot, ctx *listContext) iterationAction {
387390
// Do not include container if it's in the list before the filter container.
388391
// Set the filter container to nil to include the rest of containers after this one.
389392
if ctx.beforeFilter != nil {
@@ -422,7 +425,7 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
422425
}
423426

424427
// Do not include container if any of the labels don't match
425-
if !ctx.filters.MatchKVList("label", container.Config.Labels) {
428+
if !ctx.filters.MatchKVList("label", container.Labels) {
426429
return excludeContainer
427430
}
428431

@@ -440,7 +443,7 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
440443
if len(ctx.exitAllowed) > 0 {
441444
shouldSkip := true
442445
for _, code := range ctx.exitAllowed {
443-
if code == container.ExitCode() && !container.Running && !container.StartedAt.IsZero() {
446+
if code == container.ExitCode && !container.Running && !container.StartedAt.IsZero() {
444447
shouldSkip = false
445448
break
446449
}
@@ -451,28 +454,34 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
451454
}
452455

453456
// Do not include container if its status doesn't match the filter
454-
if !ctx.filters.Match("status", container.State.StateString()) {
457+
if !ctx.filters.Match("status", container.State) {
455458
return excludeContainer
456459
}
457460

458461
// Do not include container if its health doesn't match the filter
459-
if !ctx.filters.ExactMatch("health", container.State.HealthString()) {
462+
if !ctx.filters.ExactMatch("health", container.Health) {
460463
return excludeContainer
461464
}
462465

463466
if ctx.filters.Include("volume") {
464-
volumesByName := make(map[string]*volume.MountPoint)
465-
for _, m := range container.MountPoints {
467+
volumesByName := make(map[string]types.MountPoint)
468+
for _, m := range container.Mounts {
466469
if m.Name != "" {
467470
volumesByName[m.Name] = m
468471
} else {
469472
volumesByName[m.Source] = m
470473
}
471474
}
475+
volumesByDestination := make(map[string]types.MountPoint)
476+
for _, m := range container.Mounts {
477+
if m.Destination != "" {
478+
volumesByDestination[m.Destination] = m
479+
}
480+
}
472481

473482
volumeExist := fmt.Errorf("volume mounted in container")
474483
err := ctx.filters.WalkValues("volume", func(value string) error {
475-
if _, exist := container.MountPoints[value]; exist {
484+
if _, exist := volumesByDestination[value]; exist {
476485
return volumeExist
477486
}
478487
if _, exist := volumesByName[value]; exist {
@@ -489,7 +498,7 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
489498
if len(ctx.images) == 0 {
490499
return excludeContainer
491500
}
492-
if !ctx.images[container.ImageID] {
501+
if !ctx.images[image.ID(container.ImageID)] {
493502
return excludeContainer
494503
}
495504
}
@@ -501,7 +510,7 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
501510
return networkExist
502511
}
503512
for _, nw := range container.NetworkSettings.Networks {
504-
if nw.EndpointSettings == nil {
513+
if nw == nil {
505514
continue
506515
}
507516
if strings.HasPrefix(nw.NetworkID, value) {
@@ -518,7 +527,7 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
518527
if len(ctx.publish) > 0 {
519528
shouldSkip := true
520529
for port := range ctx.publish {
521-
if _, ok := container.HostConfig.PortBindings[port]; ok {
530+
if _, ok := container.PublishPorts[port]; ok {
522531
shouldSkip = false
523532
break
524533
}
@@ -531,7 +540,7 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
531540
if len(ctx.expose) > 0 {
532541
shouldSkip := true
533542
for port := range ctx.expose {
534-
if _, ok := container.Config.ExposedPorts[port]; ok {
543+
if _, ok := container.ExposedPorts[port]; ok {
535544
shouldSkip = false
536545
break
537546
}
@@ -545,104 +554,38 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
545554
}
546555

547556
// transformContainer generates the container type expected by the docker ps command.
548-
func (daemon *Daemon) transformContainer(container *container.Container, ctx *listContext) (*types.Container, error) {
557+
func (daemon *Daemon) transformContainer(container *container.Snapshot, ctx *listContext) (*types.Container, error) {
549558
newC := &types.Container{
550-
ID: container.ID,
551-
Names: ctx.names[container.ID],
552-
ImageID: container.ImageID.String(),
559+
ID: container.ID,
560+
Names: ctx.names[container.ID],
561+
ImageID: container.ImageID,
562+
Command: container.Command,
563+
Created: container.Created.Unix(),
564+
State: container.State,
565+
Status: container.Status,
566+
NetworkSettings: &container.NetworkSettings,
567+
Ports: container.Ports,
568+
Labels: container.Labels,
569+
Mounts: container.Mounts,
553570
}
554571
if newC.Names == nil {
555572
// Dead containers will often have no name, so make sure the response isn't null
556573
newC.Names = []string{}
557574
}
575+
newC.HostConfig.NetworkMode = container.HostConfig.NetworkMode
558576

559-
image := container.Config.Image // if possible keep the original ref
560-
if image != container.ImageID.String() {
577+
image := container.Image // if possible keep the original ref
578+
if image != container.ImageID {
561579
id, _, err := daemon.GetImageIDAndPlatform(image)
562580
if _, isDNE := err.(ErrImageDoesNotExist); err != nil && !isDNE {
563581
return nil, err
564582
}
565-
if err != nil || id != container.ImageID {
566-
image = container.ImageID.String()
583+
if err != nil || id.String() != container.ImageID {
584+
image = container.ImageID
567585
}
568586
}
569587
newC.Image = image
570588

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-
646589
return newC, nil
647590
}
648591

0 commit comments

Comments
 (0)