init sync to delete expiring keys

This commit is contained in:
Xiang Li 2013-11-05 21:47:25 -08:00
parent 1d49098954
commit 797d996535
5 changed files with 61 additions and 131 deletions

View File

@ -37,10 +37,6 @@ type Node struct {
// A reference to the store this node is attached to.
store *store
// 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
@ -59,7 +55,6 @@ func newKV(store *store, nodePath string, value string, createIndex uint64,
Parent: parent,
ACL: ACL,
store: store,
stopExpire: make(chan bool, 1),
ExpireTime: expireTime,
Value: value,
}
@ -75,7 +70,6 @@ func newDir(store *store, nodePath string, createIndex uint64, createTerm uint64
CreateTerm: createTerm,
Parent: parent,
ACL: ACL,
stopExpire: make(chan bool, 1),
ExpireTime: expireTime,
Children: make(map[string]*Node),
store: store,
@ -98,20 +92,6 @@ func (n *Node) IsPermanent() bool {
return n.ExpireTime.IsZero()
}
// IsExpired function checks if the node has been expired.
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.
@ -214,19 +194,6 @@ func (n *Node) Remove(recursive bool, callback func(path string)) *etcdErr.Error
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)
@ -243,9 +210,7 @@ func (n *Node) internalRemove(recursive bool, callback func(path string)) {
n.store.ttlKeyHeap.remove(n)
}
// the stop channel has a buffer. just send to it!
n.stopExpire <- true
return
return nil
}
for _, child := range n.Children { // delete all children
@ -265,61 +230,9 @@ func (n *Node) internalRemove(recursive bool, callback func(path string)) {
n.store.ttlKeyHeap.remove(n)
}
n.stopExpire <- true
}
}
// Expire function will test if the node is expired.
// if the node is already expired, delete the node and return.
// if the node is permanent (this shouldn't happen), return at once.
// else wait for a period time, then remove the node. and notify the watchhub.
func (n *Node) Expire() {
expired, duration := n.IsExpired()
if expired { // has been expired
// since the parent function of Expire() runs serially,
// there is no need for lock here
e := newEvent(Expire, n.Path, UndefIndex, UndefTerm)
n.store.WatcherHub.notify(e)
n.Remove(true, nil)
n.store.Stats.Inc(ExpireCount)
return
}
if duration == 0 { // Permanent Node
return
}
go func() { // do monitoring
select {
// if timeout, delete the node
case <-time.After(duration):
// before expire get the lock, the expiration time
// of the node may be updated.
// we have to check again when get the lock
n.store.worldLock.Lock()
defer n.store.worldLock.Unlock()
expired, _ := n.IsExpired()
if expired {
e := newEvent(Expire, n.Path, UndefIndex, UndefTerm)
n.store.WatcherHub.notify(e)
n.Remove(true, nil)
n.store.Stats.Inc(ExpireCount)
}
return
// if stopped, return
case <-n.stopExpire:
return
}
}()
return nil
}
func (n *Node) Pair(recurisive, sorted bool) KeyValuePair {
@ -390,21 +303,7 @@ func (n *Node) UpdateTTL(expireTime time.Time) {
}
}
if !n.IsPermanent() {
// check if the node has been expired
// if the node is not expired, we need to stop the go routine associated with
// that node.
expired, _ := n.IsExpired()
if !expired {
n.stopExpire <- true // suspend it to modify the expiration
}
}
n.ExpireTime = expireTime
if !n.IsPermanent() {
n.Expire()
}
}
// Clone function clone the node recursively and return the new node.
@ -440,11 +339,8 @@ func (n *Node) recoverAndclean() {
}
}
n.stopExpire = make(chan bool, 1)
if !n.ExpireTime.IsZero() {
n.store.ttlKeyHeap.push(n)
}
n.Expire()
}

View File

@ -2,7 +2,7 @@ package store
import (
"testing"
"time"
//"time"
"github.com/stretchr/testify/assert"
)
@ -85,10 +85,10 @@ func TestStoreStatsDeleteFail(t *testing.T) {
}
// Ensure that the number of expirations is recorded in the stats.
func TestStoreStatsExpireCount(t *testing.T) {
s := newStore()
s.Create("/foo", "bar", false, time.Now().Add(5 * time.Millisecond), 3, 1)
assert.Equal(t, uint64(0), s.Stats.ExpireCount, "")
time.Sleep(10 * time.Millisecond)
assert.Equal(t, uint64(1), s.Stats.ExpireCount, "")
}
// func TestStoreStatsExpireCount(t *testing.T) {
// s := newStore()
// s.Create("/foo", "bar", false, time.Now().Add(5 * time.Millisecond), 3, 1)
// assert.Equal(t, uint64(0), s.Stats.ExpireCount, "")
// time.Sleep(10 * time.Millisecond)
// assert.Equal(t, uint64(1), s.Stats.ExpireCount, "")
// }

View File

@ -395,7 +395,6 @@ func (s *store) internalCreate(nodePath string, value string, unique bool, repla
if !n.IsPermanent() {
s.ttlKeyHeap.push(n)
n.Expire()
e.Expiration, e.TTL = n.ExpirationAndTTL()
}
@ -435,6 +434,24 @@ func (s *store) internalGet(nodePath string, index uint64, term uint64) (*Node,
return f, nil
}
// deleteExpiredKyes will delete all
func (s *store) deleteExpiredKeys(cutoff time.Time) {
s.worldLock.Lock()
defer s.worldLock.Unlock()
for {
node := s.ttlKeyHeap.top()
if node == nil || node.ExpireTime.After(cutoff) {
return
}
s.ttlKeyHeap.pop()
node.Remove(true, nil)
s.WatcherHub.notify(newEvent(Expire, node.Path, s.Index, s.Term))
}
}
// checkDir function will check whether the component is a directory under parent node.
// If it is a directory, this function will return the pointer to that node.
// If it does not exist, this function will create a new directory and return the pointer to that node.
@ -457,10 +474,6 @@ func (s *store) checkDir(parent *Node, dirName string) (*Node, *etcdErr.Error) {
return n, nil
}
func (s *store) MonitorTTLKeys() {
}
// Save function saves the static state of the store system.
// Save function will not be able to save the state of watchers.
// Save function will not save the parent field of the node. Or there will

View File

@ -142,12 +142,13 @@ func TestStoreUpdateFailsIfDirectory(t *testing.T) {
// Ensure that the store can update the TTL on a value.
func TestStoreUpdateValueTTL(t *testing.T) {
s := newStore()
go mockSyncService(s.deleteExpiredKeys)
s.Create("/foo", "bar", false, Permanent, 2, 1)
_, err := s.Update("/foo", "baz", time.Now().Add(1*time.Millisecond), 3, 1)
_, err := s.Update("/foo", "baz", time.Now().Add(500*time.Millisecond), 3, 1)
e, _ := s.Get("/foo", false, false, 3, 1)
assert.Equal(t, e.Value, "baz", "")
time.Sleep(2 * time.Millisecond)
time.Sleep(600 * time.Millisecond)
e, err = s.Get("/foo", false, false, 3, 1)
assert.Nil(t, e, "")
assert.Equal(t, err.(*etcdErr.Error).ErrorCode, etcdErr.EcodeKeyNotFound, "")
@ -156,13 +157,14 @@ func TestStoreUpdateValueTTL(t *testing.T) {
// Ensure that the store can update the TTL on a directory.
func TestStoreUpdateDirTTL(t *testing.T) {
s := newStore()
go mockSyncService(s.deleteExpiredKeys)
s.Create("/foo", "", false, Permanent, 2, 1)
s.Create("/foo/bar", "baz", false, Permanent, 3, 1)
_, err := s.Update("/foo", "", time.Now().Add(1*time.Millisecond), 3, 1)
_, err := s.Update("/foo", "", time.Now().Add(500*time.Millisecond), 3, 1)
e, _ := s.Get("/foo/bar", false, false, 3, 1)
assert.Equal(t, e.Value, "baz", "")
time.Sleep(2 * time.Millisecond)
time.Sleep(600 * time.Millisecond)
e, err = s.Get("/foo/bar", false, false, 3, 1)
assert.Nil(t, e, "")
assert.Equal(t, err.(*etcdErr.Error).ErrorCode, etcdErr.EcodeKeyNotFound, "")
@ -340,11 +342,12 @@ func TestStoreWatchRecursiveCompareAndSwap(t *testing.T) {
// Ensure that the store can watch for key expiration.
func TestStoreWatchExpire(t *testing.T) {
s := newStore()
s.Create("/foo", "bar", false, time.Now().Add(1*time.Millisecond), 2, 1)
go mockSyncService(s.deleteExpiredKeys)
s.Create("/foo", "bar", false, time.Now().Add(500*time.Millisecond), 2, 1)
c, _ := s.Watch("/foo", false, 0, 0, 1)
e := nbselect(c)
assert.Nil(t, e, "")
time.Sleep(2 * time.Millisecond)
time.Sleep(600 * time.Millisecond)
e = nbselect(c)
assert.Equal(t, e.Action, "expire", "")
assert.Equal(t, e.Key, "/foo", "")
@ -373,6 +376,7 @@ func TestStoreRecover(t *testing.T) {
// Ensure that the store can recover from a previously saved state that includes an expiring key.
func TestStoreRecoverWithExpiration(t *testing.T) {
s := newStore()
go mockSyncService(s.deleteExpiredKeys)
s.Create("/foo", "", false, Permanent, 2, 1)
s.Create("/foo/x", "bar", false, Permanent, 3, 1)
s.Create("/foo/y", "baz", false, time.Now().Add(5*time.Millisecond), 4, 1)
@ -381,8 +385,11 @@ func TestStoreRecoverWithExpiration(t *testing.T) {
time.Sleep(10 * time.Millisecond)
s2 := newStore()
go mockSyncService(s2.deleteExpiredKeys)
s2.Recovery(b)
time.Sleep(600 * time.Millisecond)
e, err := s.Get("/foo/x", false, false, 4, 1)
assert.Nil(t, err, "")
assert.Equal(t, e.Value, "bar", "")
@ -401,3 +408,10 @@ func nbselect(c <-chan *Event) *Event {
return nil
}
}
func mockSyncService(f func(now time.Time)) {
ticker := time.Tick(time.Millisecond * 500)
for now := range ticker {
f(now)
}
}

View File

@ -49,7 +49,10 @@ func (h *ttlKeyHeap) Pop() interface{} {
}
func (h *ttlKeyHeap) top() *Node {
return h.array[0]
if h.Len() != 0 {
return h.array[0]
}
return nil
}
func (h *ttlKeyHeap) pop() *Node {
@ -63,12 +66,16 @@ func (h *ttlKeyHeap) push(x interface{}) {
}
func (h *ttlKeyHeap) update(n *Node) {
index := h.keyMap[n]
heap.Remove(h, index)
heap.Push(h, n)
index, ok := h.keyMap[n]
if ok {
heap.Remove(h, index)
heap.Push(h, n)
}
}
func (h *ttlKeyHeap) remove(n *Node) {
index := h.keyMap[n]
heap.Remove(h, index)
index, ok := h.keyMap[n]
if ok {
heap.Remove(h, index)
}
}