Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

race, storage: add independent AddNames and RemoveNames for images,layers and containers #1153

Merged
merged 1 commit into from
Mar 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 40 additions & 13 deletions containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,17 @@ type ContainerStore interface {

// SetNames updates the list of names associated with the container
// with the specified ID.
// Deprecated: Prone to race conditions, suggested alternatives are `AddNames` and `RemoveNames`.
SetNames(id string, names []string) error

// AddNames adds the supplied values to the list of names associated with the container with
// the specified id.
AddNames(id string, names []string) error

// RemoveNames removes the supplied values from the list of names associated with the container with
// the specified id.
RemoveNames(id string, names []string) error
Comment on lines +90 to +96
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nalind do we even need to make these methods public? I can’t see any external users of the ContainerStore interface.

So maybe we just need a single updateNames here, with SetNames a compatibility forwarder. Having each of the three stores provide an updateNames would allow store.UpdateNames to avoid the switch statements.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not attached to the new methods being public. The early guess was that we'd find ourselves needing to call into LayerStore/ImageStore/ContainerStore methods directly far more often than we do now, but I don't think that's turned out to be the case in practice.


// Get retrieves information about a container given an ID or name.
Get(id string) (*Container, error)

Expand Down Expand Up @@ -377,22 +386,40 @@ func (r *containerStore) removeName(container *Container, name string) {
container.Names = stringSliceWithoutValue(container.Names, name)
}

// Deprecated: Prone to race conditions, suggested alternatives are `AddNames` and `RemoveNames`.
func (r *containerStore) SetNames(id string, names []string) error {
flouthoc marked this conversation as resolved.
Show resolved Hide resolved
names = dedupeNames(names)
if container, ok := r.lookup(id); ok {
for _, name := range container.Names {
delete(r.byname, name)
}
for _, name := range names {
if otherContainer, ok := r.byname[name]; ok {
r.removeName(otherContainer, name)
}
r.byname[name] = container
return r.updateNames(id, names, setNames)
}

func (r *containerStore) AddNames(id string, names []string) error {
flouthoc marked this conversation as resolved.
Show resolved Hide resolved
return r.updateNames(id, names, addNames)
}

func (r *containerStore) RemoveNames(id string, names []string) error {
return r.updateNames(id, names, removeNames)
}

func (r *containerStore) updateNames(id string, names []string, op updateNameOperation) error {
container, ok := r.lookup(id)
if !ok {
return ErrContainerUnknown
}
oldNames := container.Names
names, err := applyNameOperation(oldNames, names, op)
if err != nil {
return err
}
for _, name := range oldNames {
delete(r.byname, name)
}
for _, name := range names {
if otherContainer, ok := r.byname[name]; ok {
r.removeName(otherContainer, name)
}
container.Names = names
return r.Save()
r.byname[name] = container
}
return ErrContainerUnknown
container.Names = names
return r.Save()
}

func (r *containerStore) Delete(id string) error {
Expand Down
5 changes: 5 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package storage

import (
"errors"

"github.com/containers/storage/types"
)

Expand Down Expand Up @@ -57,4 +59,7 @@ var (
ErrNotSupported = types.ErrNotSupported
// ErrInvalidMappings is returned when the specified mappings are invalid.
ErrInvalidMappings = types.ErrInvalidMappings
// ErrInvalidNameOperation is returned when updateName is called with invalid operation.
// Internal error
errInvalidUpdateNameOperation = errors.New("invalid update name operation")
)
57 changes: 43 additions & 14 deletions images.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,19 @@ type ImageStore interface {
// SetNames replaces the list of names associated with an image with the
// supplied values. The values are expected to be valid normalized
// named image references.
// Deprecated: Prone to race conditions, suggested alternatives are `AddNames` and `RemoveNames`.
SetNames(id string, names []string) error

// AddNames adds the supplied values to the list of names associated with the image with
// the specified id. The values are expected to be valid normalized
// named image references.
AddNames(id string, names []string) error

// RemoveNames removes the supplied values from the list of names associated with the image with
// the specified id. The values are expected to be valid normalized
// named image references.
RemoveNames(id string, names []string) error

// Delete removes the record of the image.
Delete(id string) error

Expand Down Expand Up @@ -505,26 +516,44 @@ func (i *Image) addNameToHistory(name string) {
i.NamesHistory = dedupeNames(append([]string{name}, i.NamesHistory...))
}

// Deprecated: Prone to race conditions, suggested alternatives are `AddNames` and `RemoveNames`.
func (r *imageStore) SetNames(id string, names []string) error {
return r.updateNames(id, names, setNames)
}

func (r *imageStore) AddNames(id string, names []string) error {
return r.updateNames(id, names, addNames)
}

func (r *imageStore) RemoveNames(id string, names []string) error {
return r.updateNames(id, names, removeNames)
}

func (r *imageStore) updateNames(id string, names []string, op updateNameOperation) error {
if !r.IsReadWrite() {
return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to change image name assignments at %q", r.imagespath())
}
names = dedupeNames(names)
if image, ok := r.lookup(id); ok {
for _, name := range image.Names {
delete(r.byname, name)
}
for _, name := range names {
if otherImage, ok := r.byname[name]; ok {
r.removeName(otherImage, name)
}
r.byname[name] = image
image.addNameToHistory(name)
image, ok := r.lookup(id)
if !ok {
return errors.Wrapf(ErrImageUnknown, "error locating image with ID %q", id)
}
oldNames := image.Names
names, err := applyNameOperation(oldNames, names, op)
if err != nil {
return err
}
for _, name := range oldNames {
delete(r.byname, name)
}
for _, name := range names {
if otherImage, ok := r.byname[name]; ok {
r.removeName(otherImage, name)
}
image.Names = names
return r.Save()
r.byname[name] = image
image.addNameToHistory(name)
}
return errors.Wrapf(ErrImageUnknown, "error locating image with ID %q", id)
image.Names = names
return r.Save()
}

func (r *imageStore) Delete(id string) error {
Expand Down
12 changes: 12 additions & 0 deletions images_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,16 @@ func TestHistoryNames(t *testing.T) {
require.Len(t, secondImage.NamesHistory, 2)
require.Equal(t, secondImage.NamesHistory[0], "3")
require.Equal(t, secondImage.NamesHistory[1], "2")

// test independent add and remove operations
require.Nil(t, store.AddNames(firstImageID, []string{"5"}))
firstImage, err = store.Get(firstImageID)
require.Nil(t, err)
require.Equal(t, firstImage.NamesHistory, []string{"4", "3", "2", "1", "5"})

// history should still contain old values
require.Nil(t, store.RemoveNames(firstImageID, []string{"5"}))
firstImage, err = store.Get(firstImageID)
require.Nil(t, err)
require.Equal(t, firstImage.NamesHistory, []string{"4", "3", "2", "1", "5"})
}
53 changes: 40 additions & 13 deletions layers.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,17 @@ type LayerStore interface {

// SetNames replaces the list of names associated with a layer with the
// supplied values.
// Deprecated: Prone to race conditions, suggested alternatives are `AddNames` and `RemoveNames`.
SetNames(id string, names []string) error

// AddNames adds the supplied values to the list of names associated with the layer with the
// specified id.
AddNames(id string, names []string) error

// RemoveNames remove the supplied values from the list of names associated with the layer with the
// specified id.
RemoveNames(id string, names []string) error

// Delete deletes a layer with the specified name or ID.
Delete(id string) error

Expand Down Expand Up @@ -1040,25 +1049,43 @@ func (r *layerStore) removeName(layer *Layer, name string) {
layer.Names = stringSliceWithoutValue(layer.Names, name)
}

// Deprecated: Prone to race conditions, suggested alternatives are `AddNames` and `RemoveNames`.
func (r *layerStore) SetNames(id string, names []string) error {
return r.updateNames(id, names, setNames)
}

func (r *layerStore) AddNames(id string, names []string) error {
return r.updateNames(id, names, addNames)
}

func (r *layerStore) RemoveNames(id string, names []string) error {
return r.updateNames(id, names, removeNames)
}

func (r *layerStore) updateNames(id string, names []string, op updateNameOperation) error {
if !r.IsReadWrite() {
return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to change layer name assignments at %q", r.layerspath())
}
names = dedupeNames(names)
if layer, ok := r.lookup(id); ok {
for _, name := range layer.Names {
delete(r.byname, name)
}
for _, name := range names {
if otherLayer, ok := r.byname[name]; ok {
r.removeName(otherLayer, name)
}
r.byname[name] = layer
layer, ok := r.lookup(id)
if !ok {
return ErrLayerUnknown
}
oldNames := layer.Names
names, err := applyNameOperation(oldNames, names, op)
if err != nil {
return err
}
for _, name := range oldNames {
delete(r.byname, name)
}
for _, name := range names {
if otherLayer, ok := r.byname[name]; ok {
r.removeName(otherLayer, name)
}
layer.Names = names
return r.Save()
r.byname[name] = layer
}
return ErrLayerUnknown
layer.Names = names
return r.Save()
}

func (r *layerStore) datadir(id string) string {
Expand Down
63 changes: 60 additions & 3 deletions store.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ import (
"github.com/pkg/errors"
)

type updateNameOperation int

const (
setNames updateNameOperation = iota
addNames
removeNames
)

var (
stores []*store
storesLock sync.Mutex
Expand Down Expand Up @@ -368,8 +376,17 @@ type Store interface {

// SetNames changes the list of names for a layer, image, or container.
// Duplicate names are removed from the list automatically.
// Deprecated: Prone to race conditions, suggested alternatives are `AddNames` and `RemoveNames`.
SetNames(id string, names []string) error

// AddNames adds the list of names for a layer, image, or container.
// Duplicate names are removed from the list automatically.
AddNames(id string, names []string) error

// RemoveNames removes the list of names for a layer, image, or container.
// Duplicate names are removed from the list automatically.
RemoveNames(id string, names []string) error

// ListImageBigData retrieves a list of the (possibly large) chunks of
// named data associated with an image.
ListImageBigData(id string) ([]string, error)
Expand Down Expand Up @@ -2050,7 +2067,20 @@ func dedupeNames(names []string) []string {
return deduped
}

// Deprecated: Prone to race conditions, suggested alternatives are `AddNames` and `RemoveNames`.
func (s *store) SetNames(id string, names []string) error {
flouthoc marked this conversation as resolved.
Show resolved Hide resolved
return s.updateNames(id, names, setNames)
}

func (s *store) AddNames(id string, names []string) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not sure whether to go there — others, feel free to say this is not the time.

If we are adding a new top-level API, do we have to follow the generic SetNames model, where the code needs to lock all three stores and guess which one the user meant? I’d expect the user to always know whether the goal is to set a name of a container/layer/image. So we could have Add{Container,Image,Layer}Names, that would actually be more efficient than the API provided now, and the tedious switch statements in store.updateNames would not be necessary.

return s.updateNames(id, names, addNames)
}

func (s *store) RemoveNames(id string, names []string) error {
return s.updateNames(id, names, removeNames)
}

func (s *store) updateNames(id string, names []string, op updateNameOperation) error {
deduped := dedupeNames(names)

rlstore, err := s.LayerStore()
Expand All @@ -2063,7 +2093,16 @@ func (s *store) SetNames(id string, names []string) error {
return err
}
if rlstore.Exists(id) {
return rlstore.SetNames(id, deduped)
switch op {
flouthoc marked this conversation as resolved.
Show resolved Hide resolved
case setNames:
return rlstore.SetNames(id, deduped)
case removeNames:
return rlstore.RemoveNames(id, deduped)
case addNames:
return rlstore.AddNames(id, deduped)
default:
return errInvalidUpdateNameOperation
}
}

ristore, err := s.ImageStore()
Expand All @@ -2076,7 +2115,16 @@ func (s *store) SetNames(id string, names []string) error {
return err
}
if ristore.Exists(id) {
return ristore.SetNames(id, deduped)
switch op {
case setNames:
return ristore.SetNames(id, deduped)
case removeNames:
return ristore.RemoveNames(id, deduped)
case addNames:
return ristore.AddNames(id, deduped)
default:
return errInvalidUpdateNameOperation
}
}

// Check is id refers to a RO Store
Expand Down Expand Up @@ -2114,7 +2162,16 @@ func (s *store) SetNames(id string, names []string) error {
return err
}
if rcstore.Exists(id) {
return rcstore.SetNames(id, deduped)
switch op {
case setNames:
return rcstore.SetNames(id, deduped)
case removeNames:
return rcstore.RemoveNames(id, deduped)
case addNames:
return rcstore.AddNames(id, deduped)
default:
return errInvalidUpdateNameOperation
}
}
return ErrLayerUnknown
}
Expand Down
Loading