From a07802a34756ed218a5009240e520fdfd3b884b5 Mon Sep 17 00:00:00 2001 From: Xiang Li Date: Mon, 7 Oct 2013 23:12:13 -0700 Subject: [PATCH] refactor node.go; use once --- store/node.go | 222 +++++++++++++++++++++++++++---------------------- store/store.go | 2 +- 2 files changed, 124 insertions(+), 100 deletions(-) diff --git a/store/node.go b/store/node.go index 4117225cb..547e1aed8 100644 --- a/store/node.go +++ b/store/node.go @@ -3,6 +3,7 @@ package store import ( "path" "sort" + "sync" "time" etcdErr "github.com/coreos/etcd/error" @@ -17,22 +18,37 @@ const ( removed ) +// Node is the basic element in the store system. +// A key-value pair will have a string value +// A directory will have a children map type Node struct { - Path string + Path string + CreateIndex uint64 CreateTerm uint64 ModifiedIndex uint64 ModifiedTerm uint64 - Parent *Node `json:"-"` - ExpireTime time.Time - ACL string - Value string // for key-value pair - Children map[string]*Node // for directory - status int - stopExpire chan bool // stop expire routine channel + + Parent *Node `json:"-"` // should not encode this field! avoid cyclical dependency. + + ExpireTime time.Time + ACL string + Value string // for key-value pair + Children map[string]*Node // for directory + + // a ttl node will have an expire routine associated with it. + // we need a channel to stop that routine when the expiration changes. + stopExpire chan bool + + // ensure we only delete the node once + // expire and remove may try to delete a node twice + once sync.Once } -func newFile(nodePath string, value string, createIndex uint64, createTerm uint64, parent *Node, ACL string, expireTime time.Time) *Node { +// newKV creates a Key-Value pair +func newKV(nodePath string, value string, createIndex uint64, + createTerm uint64, parent *Node, ACL string, expireTime time.Time) *Node { + return &Node{ Path: nodePath, CreateIndex: createIndex, @@ -47,7 +63,10 @@ func newFile(nodePath string, value string, createIndex uint64, createTerm uint6 } } -func newDir(nodePath string, createIndex uint64, createTerm uint64, parent *Node, ACL string, expireTime time.Time) *Node { +// newDir creates a directory +func newDir(nodePath string, createIndex uint64, createTerm uint64, + parent *Node, ACL string, expireTime time.Time) *Node { + return &Node{ Path: nodePath, CreateIndex: createIndex, @@ -60,36 +79,79 @@ func newDir(nodePath string, createIndex uint64, createTerm uint64, parent *Node } } -// Remove function remove the node. -// If the node is a directory and recursive is true, the function will recursively remove -// add nodes under the receiver node. -func (n *Node) Remove(recursive bool, callback func(path string)) *etcdErr.Error { - if n.status == removed { // check race between remove and expire - return nil +// IsHidden function checks if the node is a hidden node. A hidden node +// will begin with '_' +// A hidden node will not be shown via get command under a directory +// For example if we have /foo/_hidden and /foo/notHidden, get "/foo" +// will only return /foo/notHidden +func (n *Node) IsHidden() bool { + _, name := path.Split(n.Path) + + return name[0] == '_' +} + +// IsPermanent function checks if the node is a permanent one. +func (n *Node) IsPermanent() bool { + return n.ExpireTime.Sub(Permanent) == 0 +} + +// IsExpired function checks if the node has been expired. +func (n *Node) IsExpired() (bool, time.Duration) { + if n.IsPermanent() { + return false, 0 } - if !n.IsDir() { // file node: key-value pair + duration := n.ExpireTime.Sub(time.Now()) + if duration <= 0 { + return true, 0 + } + + return false, duration +} + +// IsDir function checks whether the node is a directory. +// If the node is a directory, the function will return true. +// Otherwise the function will return false. +func (n *Node) IsDir() bool { + return !(n.Children == nil) +} + +// Remove function remove the node. +func (n *Node) Remove(recursive bool, callback func(path string)) *etcdErr.Error { + + if n.IsDir() && !recursive { + // cannot delete a directory without set recursive to true + return etcdErr.NewError(etcdErr.EcodeNotFile, "", UndefIndex, UndefTerm) + } + + onceBody := func() { + n.internalRemove(recursive, callback) + } + + // this function might be entered multiple times by expire and delete + // every node will only be deleted once. + n.once.Do(onceBody) + + return nil +} + +// internalRemove function will be called by remove() +func (n *Node) internalRemove(recursive bool, callback func(path string)) { + if !n.IsDir() { // key-value pair _, name := path.Split(n.Path) + // find its parent and remove the node from the map if n.Parent != nil && n.Parent.Children[name] == n { - // This is the only pointer to Node object - // Handled by garbage collector delete(n.Parent.Children, name) - - if callback != nil { - callback(n.Path) - } - - n.stopExpire <- true - n.status = removed - } - return nil - } + if callback != nil { + callback(n.Path) + } - if !recursive { - return etcdErr.NewError(etcdErr.EcodeNotFile, "", UndefIndex, UndefTerm) + // the stop channel has a buffer. just send to it! + n.stopExpire <- true + return } for _, child := range n.Children { // delete all children @@ -106,10 +168,7 @@ func (n *Node) Remove(recursive bool, callback func(path string)) *etcdErr.Error } n.stopExpire <- true - n.status = removed } - - return nil } // Read function gets the value of the node. @@ -192,36 +251,6 @@ func (n *Node) Add(child *Node) *etcdErr.Error { return nil } -// Clone function clone the node recursively and return the new node. -// If the node is a directory, it will clone all the content under this directory. -// If the node is a key-value pair, it will clone the pair. -func (n *Node) Clone() *Node { - if !n.IsDir() { - return newFile(n.Path, n.Value, n.CreateIndex, n.CreateTerm, n.Parent, n.ACL, n.ExpireTime) - } - - clone := newDir(n.Path, n.CreateIndex, n.CreateTerm, n.Parent, n.ACL, n.ExpireTime) - - for key, child := range n.Children { - clone.Children[key] = child.Clone() - } - - return clone -} - -func (n *Node) recoverAndclean(s *Store) { - if n.IsDir() { - for _, child := range n.Children { - child.Parent = n - child.recoverAndclean(s) - } - } - - n.stopExpire = make(chan bool, 1) - - n.Expire(s) -} - // Expire function will test if the node is expired. // if the node is already expired, delete the node and return. // if the node is permemant (this shouldn't happen), return at once. @@ -268,41 +297,6 @@ func (n *Node) Expire(s *Store) { }() } -// IsHidden function checks if the node is a hidden node. A hidden node -// will begin with '_' -// A hidden node will not be shown via get command under a directory -// For example if we have /foo/_hidden and /foo/notHidden, get "/foo" -// will only return /foo/notHidden -func (n *Node) IsHidden() bool { - _, name := path.Split(n.Path) - - return name[0] == '_' -} - -func (n *Node) IsPermanent() bool { - return n.ExpireTime.Sub(Permanent) == 0 -} - -func (n *Node) IsExpired() (bool, time.Duration) { - if n.IsPermanent() { - return false, 0 - } - - duration := n.ExpireTime.Sub(time.Now()) - if duration <= 0 { - return true, 0 - } - - return false, duration -} - -// IsDir function checks whether the node is a directory. -// If the node is a directory, the function will return true. -// Otherwise the function will return false. -func (n *Node) IsDir() bool { - return !(n.Children == nil) -} - func (n *Node) Pair(recurisive, sorted bool) KeyValuePair { if n.IsDir() { pair := KeyValuePair{ @@ -364,3 +358,33 @@ func (n *Node) UpdateTTL(expireTime time.Time, s *Store) { n.Expire(s) } } + +// Clone function clone the node recursively and return the new node. +// If the node is a directory, it will clone all the content under this directory. +// If the node is a key-value pair, it will clone the pair. +func (n *Node) Clone() *Node { + if !n.IsDir() { + return newKV(n.Path, n.Value, n.CreateIndex, n.CreateTerm, n.Parent, n.ACL, n.ExpireTime) + } + + clone := newDir(n.Path, n.CreateIndex, n.CreateTerm, n.Parent, n.ACL, n.ExpireTime) + + for key, child := range n.Children { + clone.Children[key] = child.Clone() + } + + return clone +} + +func (n *Node) recoverAndclean(s *Store) { + if n.IsDir() { + for _, child := range n.Children { + child.Parent = n + child.recoverAndclean(s) + } + } + + n.stopExpire = make(chan bool, 1) + + n.Expire(s) +} diff --git a/store/store.go b/store/store.go index b8830fe5e..5d41e45ed 100644 --- a/store/store.go +++ b/store/store.go @@ -344,7 +344,7 @@ func (s *Store) internalCreate(nodePath string, value string, incrementalSuffix if len(value) != 0 { // create file e.Value = value - n = newFile(nodePath, value, s.Index, s.Term, d, "", expireTime) + n = newKV(nodePath, value, s.Index, s.Term, d, "", expireTime) } else { // create directory e.Dir = true