diff --git a/pkg/server/container_attach.go b/pkg/server/container_attach.go index a7621480c..2e3825624 100644 --- a/pkg/server/container_attach.go +++ b/pkg/server/container_attach.go @@ -52,7 +52,7 @@ func (c *criContainerdService) attachContainer(ctx context.Context, id string, s // Get container from our container store. cntr, err := c.containerStore.Get(id) if err != nil { - return fmt.Errorf("failed to find container in store: %v", err) + return fmt.Errorf("failed to find container %q in store: %v", id, err) } id = cntr.ID diff --git a/pkg/server/container_exec.go b/pkg/server/container_exec.go index 182e9895c..bcaf2a1c5 100644 --- a/pkg/server/container_exec.go +++ b/pkg/server/container_exec.go @@ -27,7 +27,7 @@ import ( func (c *criContainerdService) Exec(ctx context.Context, r *runtime.ExecRequest) (*runtime.ExecResponse, error) { cntr, err := c.containerStore.Get(r.GetContainerId()) if err != nil { - return nil, fmt.Errorf("failed to find container in store: %v", err) + return nil, fmt.Errorf("failed to find container %q in store: %v", r.GetContainerId(), err) } state := cntr.Status.Get().State() if state != runtime.ContainerState_CONTAINER_RUNNING { diff --git a/pkg/server/container_execsync.go b/pkg/server/container_execsync.go index 8136909e2..732889ca2 100644 --- a/pkg/server/container_execsync.go +++ b/pkg/server/container_execsync.go @@ -78,7 +78,7 @@ func (c *criContainerdService) execInContainer(ctx context.Context, id string, o // Get container from our container store. cntr, err := c.containerStore.Get(id) if err != nil { - return nil, fmt.Errorf("failed to find container in store: %v", err) + return nil, fmt.Errorf("failed to find container %q in store: %v", id, err) } id = cntr.ID diff --git a/pkg/server/image_pull.go b/pkg/server/image_pull.go index 5f002eff9..528e0483a 100644 --- a/pkg/server/image_pull.go +++ b/pkg/server/image_pull.go @@ -142,7 +142,9 @@ func (c *criContainerdService) PullImage(ctx context.Context, r *runtime.PullIma img.RepoTags = []string{repoTag} } - c.imageStore.Add(img) + if err := c.imageStore.Add(img); err != nil { + return nil, fmt.Errorf("failed to add image %q into store: %v", img.ID, err) + } // NOTE(random-liu): the actual state in containerd is the source of truth, even we maintain // in-memory image store, it's only for in-memory indexing. The image could be removed diff --git a/pkg/server/restart.go b/pkg/server/restart.go index 22bd2e029..2d625ea1c 100644 --- a/pkg/server/restart.go +++ b/pkg/server/restart.go @@ -101,8 +101,10 @@ func (c *criContainerdService) recover(ctx context.Context) error { return fmt.Errorf("failed to load images: %v", err) } for _, image := range images { - c.imageStore.Add(image) glog.V(4).Infof("Loaded image %+v", image) + if err := c.imageStore.Add(image); err != nil { + return fmt.Errorf("failed to add image %q to store: %v", image.ID, err) + } } // It's possible that containerd containers are deleted unexpectedly. In that case, diff --git a/pkg/server/sandbox_portforward.go b/pkg/server/sandbox_portforward.go index 0a3e5b975..def0e6a6a 100644 --- a/pkg/server/sandbox_portforward.go +++ b/pkg/server/sandbox_portforward.go @@ -35,7 +35,7 @@ func (c *criContainerdService) PortForward(ctx context.Context, r *runtime.PortF // TODO(random-liu): Run a socat container inside the sandbox to do portforward. sandbox, err := c.sandboxStore.Get(r.GetPodSandboxId()) if err != nil { - return nil, fmt.Errorf("failed to find sandbox: %v", err) + return nil, fmt.Errorf("failed to find sandbox %q: %v", r.GetPodSandboxId(), err) } t, err := sandbox.Container.Task(ctx, nil) @@ -59,7 +59,7 @@ func (c *criContainerdService) PortForward(ctx context.Context, r *runtime.PortF func (c *criContainerdService) portForward(id string, port int32, stream io.ReadWriteCloser) error { s, err := c.sandboxStore.Get(id) if err != nil { - return fmt.Errorf("failed to find sandbox in store: %v", err) + return fmt.Errorf("failed to find sandbox %q in store: %v", id, err) } t, err := s.Container.Task(context.Background(), nil) if err != nil { diff --git a/pkg/store/container/container.go b/pkg/store/container/container.go index d2bbb6ca8..bfe754c18 100644 --- a/pkg/store/container/container.go +++ b/pkg/store/container/container.go @@ -20,6 +20,7 @@ import ( "sync" "github.com/containerd/containerd" + "github.com/docker/docker/pkg/truncindex" cio "github.com/kubernetes-incubator/cri-containerd/pkg/server/io" "github.com/kubernetes-incubator/cri-containerd/pkg/store" @@ -92,12 +93,15 @@ func (c *Container) Delete() error { type Store struct { lock sync.RWMutex containers map[string]Container - // TODO(random-liu): Add trunc index. + idIndex *truncindex.TruncIndex } // NewStore creates a container store. func NewStore() *Store { - return &Store{containers: make(map[string]Container)} + return &Store{ + containers: make(map[string]Container), + idIndex: truncindex.NewTruncIndex([]string{}), + } } // Add a container into the store. Returns store.ErrAlreadyExist if the @@ -108,6 +112,9 @@ func (s *Store) Add(c Container) error { if _, ok := s.containers[c.ID]; ok { return store.ErrAlreadyExist } + if err := s.idIndex.Add(c.ID); err != nil { + return err + } s.containers[c.ID] = c return nil } @@ -117,6 +124,13 @@ func (s *Store) Add(c Container) error { func (s *Store) Get(id string) (Container, error) { s.lock.RLock() defer s.lock.RUnlock() + id, err := s.idIndex.Get(id) + if err != nil { + if err == truncindex.ErrNotExist { + err = store.ErrNotExist + } + return Container{}, err + } if c, ok := s.containers[id]; ok { return c, nil } @@ -138,5 +152,12 @@ func (s *Store) List() []Container { func (s *Store) Delete(id string) { s.lock.Lock() defer s.lock.Unlock() + id, err := s.idIndex.Get(id) + if err != nil { + // Note: The idIndex.Delete and delete doesn't handle truncated index. + // So we need to return if there are error. + return + } + s.idIndex.Delete(id) // nolint: errcheck delete(s.containers, id) } diff --git a/pkg/store/container/container_test.go b/pkg/store/container/container_test.go index 3f25887a4..0907b157a 100644 --- a/pkg/store/container/container_test.go +++ b/pkg/store/container/container_test.go @@ -28,7 +28,6 @@ import ( ) func TestContainerStore(t *testing.T) { - ids := []string{"1", "2", "3"} metadatas := map[string]Metadata{ "1": { ID: "1", @@ -43,32 +42,44 @@ func TestContainerStore(t *testing.T) { ImageRef: "TestImage-1", LogPath: "/test/log/path/1", }, - "2": { - ID: "2", - Name: "Container-2", - SandboxID: "Sandbox-2", + "2abcd": { + ID: "2abcd", + Name: "Container-2abcd", + SandboxID: "Sandbox-2abcd", Config: &runtime.ContainerConfig{ Metadata: &runtime.ContainerMetadata{ - Name: "TestPod-2", + Name: "TestPod-2abcd", Attempt: 2, }, }, ImageRef: "TestImage-2", LogPath: "/test/log/path/2", }, - "3": { - ID: "3", - Name: "Container-3", - SandboxID: "Sandbox-3", + "4a333": { + ID: "4a333", + Name: "Container-4a333", + SandboxID: "Sandbox-4a333", Config: &runtime.ContainerConfig{ Metadata: &runtime.ContainerMetadata{ - Name: "TestPod-3", + Name: "TestPod-4a333", Attempt: 3, }, }, ImageRef: "TestImage-3", LogPath: "/test/log/path/3", }, + "4abcd": { + ID: "4abcd", + Name: "Container-4abcd", + SandboxID: "Sandbox-4abcd", + Config: &runtime.ContainerConfig{ + Metadata: &runtime.ContainerMetadata{ + Name: "TestPod-4abcd", + Attempt: 1, + }, + }, + ImageRef: "TestImage-4abcd", + }, } statuses := map[string]Status{ "1": { @@ -80,29 +91,39 @@ func TestContainerStore(t *testing.T) { Reason: "TestReason-1", Message: "TestMessage-1", }, - "2": { + "2abcd": { Pid: 2, CreatedAt: time.Now().UnixNano(), StartedAt: time.Now().UnixNano(), FinishedAt: time.Now().UnixNano(), ExitCode: 2, - Reason: "TestReason-2", - Message: "TestMessage-2", + Reason: "TestReason-2abcd", + Message: "TestMessage-2abcd", }, - "3": { + "4a333": { Pid: 3, CreatedAt: time.Now().UnixNano(), StartedAt: time.Now().UnixNano(), FinishedAt: time.Now().UnixNano(), ExitCode: 3, - Reason: "TestReason-3", - Message: "TestMessage-3", + Reason: "TestReason-4a333", + Message: "TestMessage-4a333", + Removing: true, + }, + "4abcd": { + Pid: 4, + CreatedAt: time.Now().UnixNano(), + StartedAt: time.Now().UnixNano(), + FinishedAt: time.Now().UnixNano(), + ExitCode: 4, + Reason: "TestReason-4abcd", + Message: "TestMessage-4abcd", Removing: true, }, } assert := assertlib.New(t) containers := map[string]Container{} - for _, id := range ids { + for id := range metadatas { container, err := NewContainer( metadatas[id], WithFakeStatus(statuses[id]), @@ -119,29 +140,35 @@ func TestContainerStore(t *testing.T) { } t.Logf("should be able to get container") + genTruncIndex := func(normalName string) string { return normalName[:(len(normalName)+1)/2] } for id, c := range containers { - got, err := s.Get(id) + got, err := s.Get(genTruncIndex(id)) assert.NoError(err) assert.Equal(c, got) } t.Logf("should be able to list containers") cs := s.List() - assert.Len(cs, 3) + assert.Len(cs, len(containers)) - testID := "2" - t.Logf("add should return already exists error for duplicated container") - assert.Equal(store.ErrAlreadyExist, s.Add(containers[testID])) + cntrNum := len(containers) + for testID, v := range containers { + truncID := genTruncIndex(testID) - t.Logf("should be able to delete container") - s.Delete(testID) - cs = s.List() - assert.Len(cs, 2) + t.Logf("add should return already exists error for duplicated container") + assert.Equal(store.ErrAlreadyExist, s.Add(v)) - t.Logf("get should return not exist error after deletion") - c, err := s.Get(testID) - assert.Equal(Container{}, c) - assert.Equal(store.ErrNotExist, err) + t.Logf("should be able to delete container") + s.Delete(truncID) + cntrNum-- + cs = s.List() + assert.Len(cs, cntrNum) + + t.Logf("get should return not exist error after deletion") + c, err := s.Get(truncID) + assert.Equal(Container{}, c) + assert.Equal(store.ErrNotExist, err) + } } func TestWithContainerIO(t *testing.T) { diff --git a/pkg/store/image/image.go b/pkg/store/image/image.go index abd81ea50..e6942e096 100644 --- a/pkg/store/image/image.go +++ b/pkg/store/image/image.go @@ -20,6 +20,7 @@ import ( "sync" "github.com/containerd/containerd" + "github.com/docker/docker/pkg/truncindex" imagespec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/kubernetes-incubator/cri-containerd/pkg/store" @@ -46,30 +47,43 @@ type Image struct { // Store stores all images. type Store struct { - lock sync.RWMutex - images map[string]Image - // TODO(random-liu): Add trunc index. + lock sync.RWMutex + images map[string]Image + idIndex *truncindex.TruncIndex } // NewStore creates an image store. func NewStore() *Store { - return &Store{images: make(map[string]Image)} + return &Store{ + images: make(map[string]Image), + idIndex: truncindex.NewTruncIndex([]string{}), + } } // Add an image into the store. -func (s *Store) Add(img Image) { +func (s *Store) Add(img Image) error { s.lock.Lock() defer s.lock.Unlock() + if _, err := s.idIndex.Get(img.ID); err != nil { + if err != truncindex.ErrNotExist { + return err + } + if err := s.idIndex.Add(img.ID); err != nil { + return err + } + } + i, ok := s.images[img.ID] if !ok { // If the image doesn't exist, add it. s.images[img.ID] = img - return + return nil } // Or else, merge the repo tags/digests. i.RepoTags = mergeStringSlices(i.RepoTags, img.RepoTags) i.RepoDigests = mergeStringSlices(i.RepoDigests, img.RepoDigests) s.images[img.ID] = i + return nil } // Get returns the image with specified id. Returns store.ErrNotExist if the @@ -77,6 +91,13 @@ func (s *Store) Add(img Image) { func (s *Store) Get(id string) (Image, error) { s.lock.RLock() defer s.lock.RUnlock() + id, err := s.idIndex.Get(id) + if err != nil { + if err == truncindex.ErrNotExist { + err = store.ErrNotExist + } + return Image{}, err + } if i, ok := s.images[id]; ok { return i, nil } @@ -98,6 +119,13 @@ func (s *Store) List() []Image { func (s *Store) Delete(id string) { s.lock.Lock() defer s.lock.Unlock() + id, err := s.idIndex.Get(id) + if err != nil { + // Note: The idIndex.Delete and delete doesn't handle truncated index. + // So we need to return if there are error. + return + } + s.idIndex.Delete(id) // nolint: errcheck delete(s.images, id) } diff --git a/pkg/store/image/image_test.go b/pkg/store/image/image_test.go index 89e52c00a..04241ac51 100644 --- a/pkg/store/image/image_test.go +++ b/pkg/store/image/image_test.go @@ -35,22 +35,30 @@ func TestImageStore(t *testing.T) { Size: 10, Config: &imagespec.ImageConfig{}, }, - "2": { - ID: "2", - ChainID: "test-chain-id-2", - RepoTags: []string{"tag-2"}, - RepoDigests: []string{"digest-2"}, + "2abcd": { + ID: "2abcd", + ChainID: "test-chain-id-2abcd", + RepoTags: []string{"tag-2abcd"}, + RepoDigests: []string{"digest-2abcd"}, Size: 20, Config: &imagespec.ImageConfig{}, }, - "3": { - ID: "3", - RepoTags: []string{"tag-3"}, - RepoDigests: []string{"digest-3"}, - ChainID: "test-chain-id-3", + "4a333": { + ID: "4a333", + RepoTags: []string{"tag-4a333"}, + RepoDigests: []string{"digest-4a333"}, + ChainID: "test-chain-id-4a333", Size: 30, Config: &imagespec.ImageConfig{}, }, + "4abcd": { + ID: "4abcd", + RepoTags: []string{"tag-4abcd"}, + RepoDigests: []string{"digest-4abcd"}, + ChainID: "test-chain-id-4abcd", + Size: 40, + Config: &imagespec.ImageConfig{}, + }, } assert := assertlib.New(t) @@ -58,49 +66,62 @@ func TestImageStore(t *testing.T) { t.Logf("should be able to add image") for _, img := range images { - s.Add(img) + err := s.Add(img) + assert.NoError(err) } t.Logf("should be able to get image") + genTruncIndex := func(normalName string) string { return normalName[:(len(normalName)+1)/2] } for id, img := range images { - got, err := s.Get(id) + got, err := s.Get(genTruncIndex(id)) assert.NoError(err) assert.Equal(img, got) } t.Logf("should be able to list images") imgs := s.List() - assert.Len(imgs, 3) + assert.Len(imgs, len(images)) - testID := "2" - t.Logf("should be able to add new repo tags/digests") - newImg := images[testID] - newImg.RepoTags = []string{"tag-new"} - newImg.RepoDigests = []string{"digest-new"} - s.Add(newImg) - got, err := s.Get(testID) - assert.NoError(err) - assert.Len(got.RepoTags, 2) - assert.Contains(got.RepoTags, "tag-2", "tag-new") - assert.Len(got.RepoDigests, 2) - assert.Contains(got.RepoDigests, "digest-2", "digest-new") + imageNum := len(images) + for testID, v := range images { + truncID := genTruncIndex(testID) + oldRepoTag := v.RepoTags[0] + oldRepoDigest := v.RepoDigests[0] + newRepoTag := oldRepoTag + "new" + newRepoDigest := oldRepoDigest + "new" - t.Logf("should not be able to add duplicated repo tags/digests") - s.Add(newImg) - got, err = s.Get(testID) - assert.NoError(err) - assert.Len(got.RepoTags, 2) - assert.Contains(got.RepoTags, "tag-2", "tag-new") - assert.Len(got.RepoDigests, 2) - assert.Contains(got.RepoDigests, "digest-2", "digest-new") + t.Logf("should be able to add new repo tags/digests") + newImg := v + newImg.RepoTags = []string{newRepoTag} + newImg.RepoDigests = []string{newRepoDigest} + err := s.Add(newImg) + assert.NoError(err) + got, err := s.Get(truncID) + assert.NoError(err) + assert.Len(got.RepoTags, 2) + assert.Contains(got.RepoTags, oldRepoTag, newRepoTag) + assert.Len(got.RepoDigests, 2) + assert.Contains(got.RepoDigests, oldRepoDigest, newRepoDigest) - t.Logf("should be able to delete image") - s.Delete(testID) - imgs = s.List() - assert.Len(imgs, 2) + t.Logf("should not be able to add duplicated repo tags/digests") + err = s.Add(newImg) + assert.NoError(err) + got, err = s.Get(truncID) + assert.NoError(err) + assert.Len(got.RepoTags, 2) + assert.Contains(got.RepoTags, oldRepoTag, newRepoTag) + assert.Len(got.RepoDigests, 2) + assert.Contains(got.RepoDigests, oldRepoDigest, newRepoDigest) - t.Logf("get should return empty struct and ErrNotExist after deletion") - img, err := s.Get(testID) - assert.Equal(Image{}, img) - assert.Equal(store.ErrNotExist, err) + t.Logf("should be able to delete image") + s.Delete(truncID) + imageNum-- + imgs = s.List() + assert.Len(imgs, imageNum) + + t.Logf("get should return empty struct and ErrNotExist after deletion") + img, err := s.Get(truncID) + assert.Equal(Image{}, img) + assert.Equal(store.ErrNotExist, err) + } } diff --git a/pkg/store/sandbox/sandbox.go b/pkg/store/sandbox/sandbox.go index 74501bf58..bf3bae63f 100644 --- a/pkg/store/sandbox/sandbox.go +++ b/pkg/store/sandbox/sandbox.go @@ -20,6 +20,7 @@ import ( "sync" "github.com/containerd/containerd" + "github.com/docker/docker/pkg/truncindex" "github.com/kubernetes-incubator/cri-containerd/pkg/store" ) @@ -39,12 +40,15 @@ type Sandbox struct { type Store struct { lock sync.RWMutex sandboxes map[string]Sandbox - // TODO(random-liu): Add trunc index. + idIndex *truncindex.TruncIndex } // NewStore creates a sandbox store. func NewStore() *Store { - return &Store{sandboxes: make(map[string]Sandbox)} + return &Store{ + sandboxes: make(map[string]Sandbox), + idIndex: truncindex.NewTruncIndex([]string{}), + } } // Add a sandbox into the store. @@ -54,6 +58,9 @@ func (s *Store) Add(sb Sandbox) error { if _, ok := s.sandboxes[sb.ID]; ok { return store.ErrAlreadyExist } + if err := s.idIndex.Add(sb.ID); err != nil { + return err + } s.sandboxes[sb.ID] = sb return nil } @@ -63,6 +70,13 @@ func (s *Store) Add(sb Sandbox) error { func (s *Store) Get(id string) (Sandbox, error) { s.lock.RLock() defer s.lock.RUnlock() + id, err := s.idIndex.Get(id) + if err != nil { + if err == truncindex.ErrNotExist { + err = store.ErrNotExist + } + return Sandbox{}, err + } if sb, ok := s.sandboxes[id]; ok { return sb, nil } @@ -84,5 +98,12 @@ func (s *Store) List() []Sandbox { func (s *Store) Delete(id string) { s.lock.Lock() defer s.lock.Unlock() + id, err := s.idIndex.Get(id) + if err != nil { + // Note: The idIndex.Delete and delete doesn't handle truncated index. + // So we need to return if there are error. + return + } + s.idIndex.Delete(id) // nolint: errcheck delete(s.sandboxes, id) } diff --git a/pkg/store/sandbox/sandbox_test.go b/pkg/store/sandbox/sandbox_test.go index 4d1e25d82..ee1e91ba9 100644 --- a/pkg/store/sandbox/sandbox_test.go +++ b/pkg/store/sandbox/sandbox_test.go @@ -26,7 +26,6 @@ import ( ) func TestSandboxStore(t *testing.T) { - ids := []string{"1", "2", "3"} metadatas := map[string]Metadata{ "1": { ID: "1", @@ -41,36 +40,49 @@ func TestSandboxStore(t *testing.T) { }, NetNSPath: "TestNetNS-1", }, - "2": { - ID: "2", - Name: "Sandbox-2", + "2abcd": { + ID: "2abcd", + Name: "Sandbox-2abcd", Config: &runtime.PodSandboxConfig{ Metadata: &runtime.PodSandboxMetadata{ - Name: "TestPod-2", - Uid: "TestUid-2", - Namespace: "TestNamespace-2", + Name: "TestPod-2abcd", + Uid: "TestUid-2abcd", + Namespace: "TestNamespace-2abcd", Attempt: 2, }, }, NetNSPath: "TestNetNS-2", }, - "3": { - ID: "3", - Name: "Sandbox-3", + "4a333": { + ID: "4a333", + Name: "Sandbox-4a333", Config: &runtime.PodSandboxConfig{ Metadata: &runtime.PodSandboxMetadata{ - Name: "TestPod-3", - Uid: "TestUid-3", - Namespace: "TestNamespace-3", + Name: "TestPod-4a333", + Uid: "TestUid-4a333", + Namespace: "TestNamespace-4a333", Attempt: 3, }, }, NetNSPath: "TestNetNS-3", }, + "4abcd": { + ID: "4abcd", + Name: "Sandbox-4abcd", + Config: &runtime.PodSandboxConfig{ + Metadata: &runtime.PodSandboxMetadata{ + Name: "TestPod-4abcd", + Uid: "TestUid-4abcd", + Namespace: "TestNamespace-4abcd", + Attempt: 1, + }, + }, + NetNSPath: "TestNetNS-4abcd", + }, } assert := assertlib.New(t) sandboxes := map[string]Sandbox{} - for _, id := range ids { + for id := range metadatas { sandboxes[id] = Sandbox{Metadata: metadatas[id]} } @@ -82,27 +94,33 @@ func TestSandboxStore(t *testing.T) { } t.Logf("should be able to get sandbox") + genTruncIndex := func(normalName string) string { return normalName[:(len(normalName)+1)/2] } for id, sb := range sandboxes { - got, err := s.Get(id) + got, err := s.Get(genTruncIndex(id)) assert.NoError(err) assert.Equal(sb, got) } t.Logf("should be able to list sandboxes") sbs := s.List() - assert.Len(sbs, 3) + assert.Len(sbs, len(sandboxes)) - testID := "2" - t.Logf("add should return already exists error for duplicated sandbox") - assert.Equal(store.ErrAlreadyExist, s.Add(sandboxes[testID])) + sbNum := len(sandboxes) + for testID, v := range sandboxes { + truncID := genTruncIndex(testID) - t.Logf("should be able to delete sandbox") - s.Delete(testID) - sbs = s.List() - assert.Len(sbs, 2) + t.Logf("add should return already exists error for duplicated sandbox") + assert.Equal(store.ErrAlreadyExist, s.Add(v)) - t.Logf("get should return not exist error after deletion") - sb, err := s.Get(testID) - assert.Equal(Sandbox{}, sb) - assert.Equal(store.ErrNotExist, err) + t.Logf("should be able to delete sandbox") + s.Delete(truncID) + sbNum-- + sbs = s.List() + assert.Len(sbs, sbNum) + + t.Logf("get should return not exist error after deletion") + sb, err := s.Get(truncID) + assert.Equal(Sandbox{}, sb) + assert.Equal(store.ErrNotExist, err) + } } diff --git a/vendor.conf b/vendor.conf index d0934cbfa..5ad4f0913 100644 --- a/vendor.conf +++ b/vendor.conf @@ -66,3 +66,4 @@ k8s.io/client-go 82aa063804cf055e16e8911250f888bc216e8b61 k8s.io/kube-openapi abfc5fbe1cf87ee697db107fdfd24c32fe4397a8 k8s.io/kubernetes d9bc7f0896091ba9879743fe4c9b27f352fe8289 k8s.io/utils 4fe312863be2155a7b68acd2aff1c9221b24e68c +github.com/tchap/go-patricia 5ad6cdb7538b0097d5598c7e57f0a24072adf7dc diff --git a/vendor/github.com/docker/docker/pkg/truncindex/truncindex.go b/vendor/github.com/docker/docker/pkg/truncindex/truncindex.go new file mode 100644 index 000000000..74776e65e --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/truncindex/truncindex.go @@ -0,0 +1,139 @@ +// Package truncindex provides a general 'index tree', used by Docker +// in order to be able to reference containers by only a few unambiguous +// characters of their id. +package truncindex + +import ( + "errors" + "fmt" + "strings" + "sync" + + "github.com/tchap/go-patricia/patricia" +) + +var ( + // ErrEmptyPrefix is an error returned if the prefix was empty. + ErrEmptyPrefix = errors.New("Prefix can't be empty") + + // ErrIllegalChar is returned when a space is in the ID + ErrIllegalChar = errors.New("illegal character: ' '") + + // ErrNotExist is returned when ID or its prefix not found in index. + ErrNotExist = errors.New("ID does not exist") +) + +// ErrAmbiguousPrefix is returned if the prefix was ambiguous +// (multiple ids for the prefix). +type ErrAmbiguousPrefix struct { + prefix string +} + +func (e ErrAmbiguousPrefix) Error() string { + return fmt.Sprintf("Multiple IDs found with provided prefix: %s", e.prefix) +} + +// TruncIndex allows the retrieval of string identifiers by any of their unique prefixes. +// This is used to retrieve image and container IDs by more convenient shorthand prefixes. +type TruncIndex struct { + sync.RWMutex + trie *patricia.Trie + ids map[string]struct{} +} + +// NewTruncIndex creates a new TruncIndex and initializes with a list of IDs. +func NewTruncIndex(ids []string) (idx *TruncIndex) { + idx = &TruncIndex{ + ids: make(map[string]struct{}), + + // Change patricia max prefix per node length, + // because our len(ID) always 64 + trie: patricia.NewTrie(patricia.MaxPrefixPerNode(64)), + } + for _, id := range ids { + idx.addID(id) + } + return +} + +func (idx *TruncIndex) addID(id string) error { + if strings.Contains(id, " ") { + return ErrIllegalChar + } + if id == "" { + return ErrEmptyPrefix + } + if _, exists := idx.ids[id]; exists { + return fmt.Errorf("id already exists: '%s'", id) + } + idx.ids[id] = struct{}{} + if inserted := idx.trie.Insert(patricia.Prefix(id), struct{}{}); !inserted { + return fmt.Errorf("failed to insert id: %s", id) + } + return nil +} + +// Add adds a new ID to the TruncIndex. +func (idx *TruncIndex) Add(id string) error { + idx.Lock() + defer idx.Unlock() + return idx.addID(id) +} + +// Delete removes an ID from the TruncIndex. If there are multiple IDs +// with the given prefix, an error is thrown. +func (idx *TruncIndex) Delete(id string) error { + idx.Lock() + defer idx.Unlock() + if _, exists := idx.ids[id]; !exists || id == "" { + return fmt.Errorf("no such id: '%s'", id) + } + delete(idx.ids, id) + if deleted := idx.trie.Delete(patricia.Prefix(id)); !deleted { + return fmt.Errorf("no such id: '%s'", id) + } + return nil +} + +// Get retrieves an ID from the TruncIndex. If there are multiple IDs +// with the given prefix, an error is thrown. +func (idx *TruncIndex) Get(s string) (string, error) { + if s == "" { + return "", ErrEmptyPrefix + } + var ( + id string + ) + subTreeVisitFunc := func(prefix patricia.Prefix, item patricia.Item) error { + if id != "" { + // we haven't found the ID if there are two or more IDs + id = "" + return ErrAmbiguousPrefix{prefix: string(prefix)} + } + id = string(prefix) + return nil + } + + idx.RLock() + defer idx.RUnlock() + if err := idx.trie.VisitSubtree(patricia.Prefix(s), subTreeVisitFunc); err != nil { + return "", err + } + if id != "" { + return id, nil + } + return "", ErrNotExist +} + +// Iterate iterates over all stored IDs and passes each of them to the given +// handler. Take care that the handler method does not call any public +// method on truncindex as the internal locking is not reentrant/recursive +// and will result in deadlock. +func (idx *TruncIndex) Iterate(handler func(id string)) { + idx.Lock() + defer idx.Unlock() + idx.trie.Visit(func(prefix patricia.Prefix, item patricia.Item) error { + handler(string(prefix)) + return nil + }) +} diff --git a/vendor/github.com/tchap/go-patricia/LICENSE b/vendor/github.com/tchap/go-patricia/LICENSE new file mode 100644 index 000000000..e50d398e9 --- /dev/null +++ b/vendor/github.com/tchap/go-patricia/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 The AUTHORS + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/tchap/go-patricia/README.md b/vendor/github.com/tchap/go-patricia/README.md new file mode 100644 index 000000000..e936d1839 --- /dev/null +++ b/vendor/github.com/tchap/go-patricia/README.md @@ -0,0 +1,117 @@ +# go-patricia # + +**Documentation**: [GoDoc](http://godoc.org/github.com/tchap/go-patricia/patricia)
+**Test Coverage**: [![Coverage +Status](https://coveralls.io/repos/tchap/go-patricia/badge.png)](https://coveralls.io/r/tchap/go-patricia) + +## About ## + +A generic patricia trie (also called radix tree) implemented in Go (Golang). + +The patricia trie as implemented in this library enables fast visiting of items +in some particular ways: + +1. visit all items saved in the tree, +2. visit all items matching particular prefix (visit subtree), or +3. given a string, visit all items matching some prefix of that string. + +`[]byte` type is used for keys, `interface{}` for values. + +`Trie` is not thread safe. Synchronize the access yourself. + +### State of the Project ### + +Apparently some people are using this, so the API should not change often. +Any ideas on how to make the library better are still welcome. + +More (unit) testing would be cool as well... + +## Usage ## + +Import the package from GitHub first. + +```go +import "github.com/tchap/go-patricia/patricia" +``` + +You can as well use gopkg.in thingie: + +```go +import "gopkg.in/tchap/go-patricia.v2/patricia" +``` + +Then you can start having fun. + +```go +printItem := func(prefix patricia.Prefix, item patricia.Item) error { + fmt.Printf("%q: %v\n", prefix, item) + return nil +} + +// Create a new default trie (using the default parameter values). +trie := NewTrie() + +// Create a new custom trie. +trie := NewTrie(MaxPrefixPerNode(16), MaxChildrenPerSparseNode(10)) + +// Insert some items. +trie.Insert(Prefix("Pepa Novak"), 1) +trie.Insert(Prefix("Pepa Sindelar"), 2) +trie.Insert(Prefix("Karel Macha"), 3) +trie.Insert(Prefix("Karel Hynek Macha"), 4) + +// Just check if some things are present in the tree. +key := Prefix("Pepa Novak") +fmt.Printf("%q present? %v\n", key, trie.Match(key)) +// "Pepa Novak" present? true +key = Prefix("Karel") +fmt.Printf("Anybody called %q here? %v\n", key, trie.MatchSubtree(key)) +// Anybody called "Karel" here? true + +// Walk the tree in alphabetical order. +trie.Visit(printItem) +// "Karel Hynek Macha": 4 +// "Karel Macha": 3 +// "Pepa Novak": 1 +// "Pepa Sindelar": 2 + +// Walk a subtree. +trie.VisitSubtree(Prefix("Pepa"), printItem) +// "Pepa Novak": 1 +// "Pepa Sindelar": 2 + +// Modify an item, then fetch it from the tree. +trie.Set(Prefix("Karel Hynek Macha"), 10) +key = Prefix("Karel Hynek Macha") +fmt.Printf("%q: %v\n", key, trie.Get(key)) +// "Karel Hynek Macha": 10 + +// Walk prefixes. +prefix := Prefix("Karel Hynek Macha je kouzelnik") +trie.VisitPrefixes(prefix, printItem) +// "Karel Hynek Macha": 10 + +// Delete some items. +trie.Delete(Prefix("Pepa Novak")) +trie.Delete(Prefix("Karel Macha")) + +// Walk again. +trie.Visit(printItem) +// "Karel Hynek Macha": 10 +// "Pepa Sindelar": 2 + +// Delete a subtree. +trie.DeleteSubtree(Prefix("Pepa")) + +// Print what is left. +trie.Visit(printItem) +// "Karel Hynek Macha": 10 +``` + +## License ## + +MIT, check the `LICENSE` file. + +[![Gittip +Badge](http://img.shields.io/gittip/alanhamlett.png)](https://www.gittip.com/tchap/ +"Gittip Badge") diff --git a/vendor/github.com/tchap/go-patricia/patricia/children.go b/vendor/github.com/tchap/go-patricia/patricia/children.go new file mode 100644 index 000000000..a5677c335 --- /dev/null +++ b/vendor/github.com/tchap/go-patricia/patricia/children.go @@ -0,0 +1,325 @@ +// Copyright (c) 2014 The go-patricia AUTHORS +// +// Use of this source code is governed by The MIT License +// that can be found in the LICENSE file. + +package patricia + +import ( + "fmt" + "io" + "sort" +) + +type childList interface { + length() int + head() *Trie + add(child *Trie) childList + remove(b byte) + replace(b byte, child *Trie) + next(b byte) *Trie + walk(prefix *Prefix, visitor VisitorFunc) error + print(w io.Writer, indent int) + total() int +} + +type tries []*Trie + +func (t tries) Len() int { + return len(t) +} + +func (t tries) Less(i, j int) bool { + strings := sort.StringSlice{string(t[i].prefix), string(t[j].prefix)} + return strings.Less(0, 1) +} + +func (t tries) Swap(i, j int) { + t[i], t[j] = t[j], t[i] +} + +type sparseChildList struct { + children tries +} + +func newSparseChildList(maxChildrenPerSparseNode int) childList { + return &sparseChildList{ + children: make(tries, 0, maxChildrenPerSparseNode), + } +} + +func (list *sparseChildList) length() int { + return len(list.children) +} + +func (list *sparseChildList) head() *Trie { + return list.children[0] +} + +func (list *sparseChildList) add(child *Trie) childList { + // Search for an empty spot and insert the child if possible. + if len(list.children) != cap(list.children) { + list.children = append(list.children, child) + return list + } + + // Otherwise we have to transform to the dense list type. + return newDenseChildList(list, child) +} + +func (list *sparseChildList) remove(b byte) { + for i, node := range list.children { + if node.prefix[0] == b { + list.children[i] = list.children[len(list.children)-1] + list.children[len(list.children)-1] = nil + list.children = list.children[:len(list.children)-1] + return + } + } + + // This is not supposed to be reached. + panic("removing non-existent child") +} + +func (list *sparseChildList) replace(b byte, child *Trie) { + // Make a consistency check. + if p0 := child.prefix[0]; p0 != b { + panic(fmt.Errorf("child prefix mismatch: %v != %v", p0, b)) + } + + // Seek the child and replace it. + for i, node := range list.children { + if node.prefix[0] == b { + list.children[i] = child + return + } + } +} + +func (list *sparseChildList) next(b byte) *Trie { + for _, child := range list.children { + if child.prefix[0] == b { + return child + } + } + return nil +} + +func (list *sparseChildList) walk(prefix *Prefix, visitor VisitorFunc) error { + + sort.Sort(list.children) + + for _, child := range list.children { + *prefix = append(*prefix, child.prefix...) + if child.item != nil { + err := visitor(*prefix, child.item) + if err != nil { + if err == SkipSubtree { + *prefix = (*prefix)[:len(*prefix)-len(child.prefix)] + continue + } + *prefix = (*prefix)[:len(*prefix)-len(child.prefix)] + return err + } + } + + err := child.children.walk(prefix, visitor) + *prefix = (*prefix)[:len(*prefix)-len(child.prefix)] + if err != nil { + return err + } + } + + return nil +} + +func (list *sparseChildList) total() int { + tot := 0 + for _, child := range list.children { + if child != nil { + tot = tot + child.total() + } + } + return tot +} + +func (list *sparseChildList) print(w io.Writer, indent int) { + for _, child := range list.children { + if child != nil { + child.print(w, indent) + } + } +} + +type denseChildList struct { + min int + max int + numChildren int + headIndex int + children []*Trie +} + +func newDenseChildList(list *sparseChildList, child *Trie) childList { + var ( + min int = 255 + max int = 0 + ) + for _, child := range list.children { + b := int(child.prefix[0]) + if b < min { + min = b + } + if b > max { + max = b + } + } + + b := int(child.prefix[0]) + if b < min { + min = b + } + if b > max { + max = b + } + + children := make([]*Trie, max-min+1) + for _, child := range list.children { + children[int(child.prefix[0])-min] = child + } + children[int(child.prefix[0])-min] = child + + return &denseChildList{ + min: min, + max: max, + numChildren: list.length() + 1, + headIndex: 0, + children: children, + } +} + +func (list *denseChildList) length() int { + return list.numChildren +} + +func (list *denseChildList) head() *Trie { + return list.children[list.headIndex] +} + +func (list *denseChildList) add(child *Trie) childList { + b := int(child.prefix[0]) + var i int + + switch { + case list.min <= b && b <= list.max: + if list.children[b-list.min] != nil { + panic("dense child list collision detected") + } + i = b - list.min + list.children[i] = child + + case b < list.min: + children := make([]*Trie, list.max-b+1) + i = 0 + children[i] = child + copy(children[list.min-b:], list.children) + list.children = children + list.min = b + + default: // b > list.max + children := make([]*Trie, b-list.min+1) + i = b - list.min + children[i] = child + copy(children, list.children) + list.children = children + list.max = b + } + + list.numChildren++ + if i < list.headIndex { + list.headIndex = i + } + return list +} + +func (list *denseChildList) remove(b byte) { + i := int(b) - list.min + if list.children[i] == nil { + // This is not supposed to be reached. + panic("removing non-existent child") + } + list.numChildren-- + list.children[i] = nil + + // Update head index. + if i == list.headIndex { + for ; i < len(list.children); i++ { + if list.children[i] != nil { + list.headIndex = i + return + } + } + } +} + +func (list *denseChildList) replace(b byte, child *Trie) { + // Make a consistency check. + if p0 := child.prefix[0]; p0 != b { + panic(fmt.Errorf("child prefix mismatch: %v != %v", p0, b)) + } + + // Replace the child. + list.children[int(b)-list.min] = child +} + +func (list *denseChildList) next(b byte) *Trie { + i := int(b) + if i < list.min || list.max < i { + return nil + } + return list.children[i-list.min] +} + +func (list *denseChildList) walk(prefix *Prefix, visitor VisitorFunc) error { + for _, child := range list.children { + if child == nil { + continue + } + *prefix = append(*prefix, child.prefix...) + if child.item != nil { + if err := visitor(*prefix, child.item); err != nil { + if err == SkipSubtree { + *prefix = (*prefix)[:len(*prefix)-len(child.prefix)] + continue + } + *prefix = (*prefix)[:len(*prefix)-len(child.prefix)] + return err + } + } + + err := child.children.walk(prefix, visitor) + *prefix = (*prefix)[:len(*prefix)-len(child.prefix)] + if err != nil { + return err + } + } + + return nil +} + +func (list *denseChildList) print(w io.Writer, indent int) { + for _, child := range list.children { + if child != nil { + child.print(w, indent) + } + } +} + +func (list *denseChildList) total() int { + tot := 0 + for _, child := range list.children { + if child != nil { + tot = tot + child.total() + } + } + return tot +} diff --git a/vendor/github.com/tchap/go-patricia/patricia/patricia.go b/vendor/github.com/tchap/go-patricia/patricia/patricia.go new file mode 100644 index 000000000..a1fc53d5d --- /dev/null +++ b/vendor/github.com/tchap/go-patricia/patricia/patricia.go @@ -0,0 +1,594 @@ +// Copyright (c) 2014 The go-patricia AUTHORS +// +// Use of this source code is governed by The MIT License +// that can be found in the LICENSE file. + +package patricia + +import ( + "bytes" + "errors" + "fmt" + "io" + "strings" +) + +//------------------------------------------------------------------------------ +// Trie +//------------------------------------------------------------------------------ + +const ( + DefaultMaxPrefixPerNode = 10 + DefaultMaxChildrenPerSparseNode = 8 +) + +type ( + Prefix []byte + Item interface{} + VisitorFunc func(prefix Prefix, item Item) error +) + +// Trie is a generic patricia trie that allows fast retrieval of items by prefix. +// and other funky stuff. +// +// Trie is not thread-safe. +type Trie struct { + prefix Prefix + item Item + + maxPrefixPerNode int + maxChildrenPerSparseNode int + + children childList +} + +// Public API ------------------------------------------------------------------ + +type Option func(*Trie) + +// Trie constructor. +func NewTrie(options ...Option) *Trie { + trie := &Trie{} + + for _, opt := range options { + opt(trie) + } + + if trie.maxPrefixPerNode <= 0 { + trie.maxPrefixPerNode = DefaultMaxPrefixPerNode + } + if trie.maxChildrenPerSparseNode <= 0 { + trie.maxChildrenPerSparseNode = DefaultMaxChildrenPerSparseNode + } + + trie.children = newSparseChildList(trie.maxChildrenPerSparseNode) + return trie +} + +func MaxPrefixPerNode(value int) Option { + return func(trie *Trie) { + trie.maxPrefixPerNode = value + } +} + +func MaxChildrenPerSparseNode(value int) Option { + return func(trie *Trie) { + trie.maxChildrenPerSparseNode = value + } +} + +// Item returns the item stored in the root of this trie. +func (trie *Trie) Item() Item { + return trie.item +} + +// Insert inserts a new item into the trie using the given prefix. Insert does +// not replace existing items. It returns false if an item was already in place. +func (trie *Trie) Insert(key Prefix, item Item) (inserted bool) { + return trie.put(key, item, false) +} + +// Set works much like Insert, but it always sets the item, possibly replacing +// the item previously inserted. +func (trie *Trie) Set(key Prefix, item Item) { + trie.put(key, item, true) +} + +// Get returns the item located at key. +// +// This method is a bit dangerous, because Get can as well end up in an internal +// node that is not really representing any user-defined value. So when nil is +// a valid value being used, it is not possible to tell if the value was inserted +// into the tree by the user or not. A possible workaround for this is not to use +// nil interface as a valid value, even using zero value of any type is enough +// to prevent this bad behaviour. +func (trie *Trie) Get(key Prefix) (item Item) { + _, node, found, leftover := trie.findSubtree(key) + if !found || len(leftover) != 0 { + return nil + } + return node.item +} + +// Match returns what Get(prefix) != nil would return. The same warning as for +// Get applies here as well. +func (trie *Trie) Match(prefix Prefix) (matchedExactly bool) { + return trie.Get(prefix) != nil +} + +// MatchSubtree returns true when there is a subtree representing extensions +// to key, that is if there are any keys in the tree which have key as prefix. +func (trie *Trie) MatchSubtree(key Prefix) (matched bool) { + _, _, matched, _ = trie.findSubtree(key) + return +} + +// Visit calls visitor on every node containing a non-nil item +// in alphabetical order. +// +// If an error is returned from visitor, the function stops visiting the tree +// and returns that error, unless it is a special error - SkipSubtree. In that +// case Visit skips the subtree represented by the current node and continues +// elsewhere. +func (trie *Trie) Visit(visitor VisitorFunc) error { + return trie.walk(nil, visitor) +} + +func (trie *Trie) size() int { + n := 0 + + trie.walk(nil, func(prefix Prefix, item Item) error { + n++ + return nil + }) + + return n +} + +func (trie *Trie) total() int { + return 1 + trie.children.total() +} + +// VisitSubtree works much like Visit, but it only visits nodes matching prefix. +func (trie *Trie) VisitSubtree(prefix Prefix, visitor VisitorFunc) error { + // Nil prefix not allowed. + if prefix == nil { + panic(ErrNilPrefix) + } + + // Empty trie must be handled explicitly. + if trie.prefix == nil { + return nil + } + + // Locate the relevant subtree. + _, root, found, leftover := trie.findSubtree(prefix) + if !found { + return nil + } + prefix = append(prefix, leftover...) + + // Visit it. + return root.walk(prefix, visitor) +} + +// VisitPrefixes visits only nodes that represent prefixes of key. +// To say the obvious, returning SkipSubtree from visitor makes no sense here. +func (trie *Trie) VisitPrefixes(key Prefix, visitor VisitorFunc) error { + // Nil key not allowed. + if key == nil { + panic(ErrNilPrefix) + } + + // Empty trie must be handled explicitly. + if trie.prefix == nil { + return nil + } + + // Walk the path matching key prefixes. + node := trie + prefix := key + offset := 0 + for { + // Compute what part of prefix matches. + common := node.longestCommonPrefixLength(key) + key = key[common:] + offset += common + + // Partial match means that there is no subtree matching prefix. + if common < len(node.prefix) { + return nil + } + + // Call the visitor. + if item := node.item; item != nil { + if err := visitor(prefix[:offset], item); err != nil { + return err + } + } + + if len(key) == 0 { + // This node represents key, we are finished. + return nil + } + + // There is some key suffix left, move to the children. + child := node.children.next(key[0]) + if child == nil { + // There is nowhere to continue, return. + return nil + } + + node = child + } +} + +// Delete deletes the item represented by the given prefix. +// +// True is returned if the matching node was found and deleted. +func (trie *Trie) Delete(key Prefix) (deleted bool) { + // Nil prefix not allowed. + if key == nil { + panic(ErrNilPrefix) + } + + // Empty trie must be handled explicitly. + if trie.prefix == nil { + return false + } + + // Find the relevant node. + path, found, _ := trie.findSubtreePath(key) + if !found { + return false + } + + node := path[len(path)-1] + var parent *Trie + if len(path) != 1 { + parent = path[len(path)-2] + } + + // If the item is already set to nil, there is nothing to do. + if node.item == nil { + return false + } + + // Delete the item. + node.item = nil + + // Initialise i before goto. + // Will be used later in a loop. + i := len(path) - 1 + + // In case there are some child nodes, we cannot drop the whole subtree. + // We can try to compact nodes, though. + if node.children.length() != 0 { + goto Compact + } + + // In case we are at the root, just reset it and we are done. + if parent == nil { + node.reset() + return true + } + + // We can drop a subtree. + // Find the first ancestor that has its value set or it has 2 or more child nodes. + // That will be the node where to drop the subtree at. + for ; i >= 0; i-- { + if current := path[i]; current.item != nil || current.children.length() >= 2 { + break + } + } + + // Handle the case when there is no such node. + // In other words, we can reset the whole tree. + if i == -1 { + path[0].reset() + return true + } + + // We can just remove the subtree here. + node = path[i] + if i == 0 { + parent = nil + } else { + parent = path[i-1] + } + // i+1 is always a valid index since i is never pointing to the last node. + // The loop above skips at least the last node since we are sure that the item + // is set to nil and it has no children, othewise we would be compacting instead. + node.children.remove(path[i+1].prefix[0]) + +Compact: + // The node is set to the first non-empty ancestor, + // so try to compact since that might be possible now. + if compacted := node.compact(); compacted != node { + if parent == nil { + *node = *compacted + } else { + parent.children.replace(node.prefix[0], compacted) + *parent = *parent.compact() + } + } + + return true +} + +// DeleteSubtree finds the subtree exactly matching prefix and deletes it. +// +// True is returned if the subtree was found and deleted. +func (trie *Trie) DeleteSubtree(prefix Prefix) (deleted bool) { + // Nil prefix not allowed. + if prefix == nil { + panic(ErrNilPrefix) + } + + // Empty trie must be handled explicitly. + if trie.prefix == nil { + return false + } + + // Locate the relevant subtree. + parent, root, found, _ := trie.findSubtree(prefix) + if !found { + return false + } + + // If we are in the root of the trie, reset the trie. + if parent == nil { + root.reset() + return true + } + + // Otherwise remove the root node from its parent. + parent.children.remove(root.prefix[0]) + return true +} + +// Internal helper methods ----------------------------------------------------- + +func (trie *Trie) empty() bool { + return trie.item == nil && trie.children.length() == 0 +} + +func (trie *Trie) reset() { + trie.prefix = nil + trie.children = newSparseChildList(trie.maxPrefixPerNode) +} + +func (trie *Trie) put(key Prefix, item Item, replace bool) (inserted bool) { + // Nil prefix not allowed. + if key == nil { + panic(ErrNilPrefix) + } + + var ( + common int + node *Trie = trie + child *Trie + ) + + if node.prefix == nil { + if len(key) <= trie.maxPrefixPerNode { + node.prefix = key + goto InsertItem + } + node.prefix = key[:trie.maxPrefixPerNode] + key = key[trie.maxPrefixPerNode:] + goto AppendChild + } + + for { + // Compute the longest common prefix length. + common = node.longestCommonPrefixLength(key) + key = key[common:] + + // Only a part matches, split. + if common < len(node.prefix) { + goto SplitPrefix + } + + // common == len(node.prefix) since never (common > len(node.prefix)) + // common == len(former key) <-> 0 == len(key) + // -> former key == node.prefix + if len(key) == 0 { + goto InsertItem + } + + // Check children for matching prefix. + child = node.children.next(key[0]) + if child == nil { + goto AppendChild + } + node = child + } + +SplitPrefix: + // Split the prefix if necessary. + child = new(Trie) + *child = *node + *node = *NewTrie() + node.prefix = child.prefix[:common] + child.prefix = child.prefix[common:] + child = child.compact() + node.children = node.children.add(child) + +AppendChild: + // Keep appending children until whole prefix is inserted. + // This loop starts with empty node.prefix that needs to be filled. + for len(key) != 0 { + child := NewTrie() + if len(key) <= trie.maxPrefixPerNode { + child.prefix = key + node.children = node.children.add(child) + node = child + goto InsertItem + } else { + child.prefix = key[:trie.maxPrefixPerNode] + key = key[trie.maxPrefixPerNode:] + node.children = node.children.add(child) + node = child + } + } + +InsertItem: + // Try to insert the item if possible. + if replace || node.item == nil { + node.item = item + return true + } + return false +} + +func (trie *Trie) compact() *Trie { + // Only a node with a single child can be compacted. + if trie.children.length() != 1 { + return trie + } + + child := trie.children.head() + + // If any item is set, we cannot compact since we want to retain + // the ability to do searching by key. This makes compaction less usable, + // but that simply cannot be avoided. + if trie.item != nil || child.item != nil { + return trie + } + + // Make sure the combined prefixes fit into a single node. + if len(trie.prefix)+len(child.prefix) > trie.maxPrefixPerNode { + return trie + } + + // Concatenate the prefixes, move the items. + child.prefix = append(trie.prefix, child.prefix...) + if trie.item != nil { + child.item = trie.item + } + + return child +} + +func (trie *Trie) findSubtree(prefix Prefix) (parent *Trie, root *Trie, found bool, leftover Prefix) { + // Find the subtree matching prefix. + root = trie + for { + // Compute what part of prefix matches. + common := root.longestCommonPrefixLength(prefix) + prefix = prefix[common:] + + // We used up the whole prefix, subtree found. + if len(prefix) == 0 { + found = true + leftover = root.prefix[common:] + return + } + + // Partial match means that there is no subtree matching prefix. + if common < len(root.prefix) { + leftover = root.prefix[common:] + return + } + + // There is some prefix left, move to the children. + child := root.children.next(prefix[0]) + if child == nil { + // There is nowhere to continue, there is no subtree matching prefix. + return + } + + parent = root + root = child + } +} + +func (trie *Trie) findSubtreePath(prefix Prefix) (path []*Trie, found bool, leftover Prefix) { + // Find the subtree matching prefix. + root := trie + var subtreePath []*Trie + for { + // Append the current root to the path. + subtreePath = append(subtreePath, root) + + // Compute what part of prefix matches. + common := root.longestCommonPrefixLength(prefix) + prefix = prefix[common:] + + // We used up the whole prefix, subtree found. + if len(prefix) == 0 { + path = subtreePath + found = true + leftover = root.prefix[common:] + return + } + + // Partial match means that there is no subtree matching prefix. + if common < len(root.prefix) { + leftover = root.prefix[common:] + return + } + + // There is some prefix left, move to the children. + child := root.children.next(prefix[0]) + if child == nil { + // There is nowhere to continue, there is no subtree matching prefix. + return + } + + root = child + } +} + +func (trie *Trie) walk(actualRootPrefix Prefix, visitor VisitorFunc) error { + var prefix Prefix + // Allocate a bit more space for prefix at the beginning. + if actualRootPrefix == nil { + prefix = make(Prefix, 32+len(trie.prefix)) + copy(prefix, trie.prefix) + prefix = prefix[:len(trie.prefix)] + } else { + prefix = make(Prefix, 32+len(actualRootPrefix)) + copy(prefix, actualRootPrefix) + prefix = prefix[:len(actualRootPrefix)] + } + + // Visit the root first. Not that this works for empty trie as well since + // in that case item == nil && len(children) == 0. + if trie.item != nil { + if err := visitor(prefix, trie.item); err != nil { + if err == SkipSubtree { + return nil + } + return err + } + } + + // Then continue to the children. + return trie.children.walk(&prefix, visitor) +} + +func (trie *Trie) longestCommonPrefixLength(prefix Prefix) (i int) { + for ; i < len(prefix) && i < len(trie.prefix) && prefix[i] == trie.prefix[i]; i++ { + } + return +} + +func (trie *Trie) dump() string { + writer := &bytes.Buffer{} + trie.print(writer, 0) + return writer.String() +} + +func (trie *Trie) print(writer io.Writer, indent int) { + fmt.Fprintf(writer, "%s%s %v\n", strings.Repeat(" ", indent), string(trie.prefix), trie.item) + trie.children.print(writer, indent+2) +} + +// Errors ---------------------------------------------------------------------- + +var ( + SkipSubtree = errors.New("Skip this subtree") + ErrNilPrefix = errors.New("Nil prefix passed into a method call") +)