|
@@ -7,6 +7,9 @@ import (
|
|
|
"fmt"
|
|
"fmt"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+// global store
|
|
|
|
|
+var s *Store
|
|
|
|
|
+
|
|
|
// CONSTANTS
|
|
// CONSTANTS
|
|
|
const (
|
|
const (
|
|
|
ERROR = -1 + iota
|
|
ERROR = -1 + iota
|
|
@@ -15,14 +18,27 @@ const (
|
|
|
GET
|
|
GET
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+
|
|
|
|
|
+var PERMANENT = time.Unix(0,0)
|
|
|
|
|
+
|
|
|
type Store struct {
|
|
type Store struct {
|
|
|
|
|
+ // use the build-in hash map as the key-value store structure
|
|
|
Nodes map[string]Node `json:"nodes"`
|
|
Nodes map[string]Node `json:"nodes"`
|
|
|
|
|
+
|
|
|
|
|
+ // the string channel to send messages to the outside world
|
|
|
|
|
+ // now we use it to send changes to the hub of the web service
|
|
|
messager *chan string
|
|
messager *chan string
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+
|
|
|
type Node struct {
|
|
type Node struct {
|
|
|
Value string `json:"value"`
|
|
Value string `json:"value"`
|
|
|
|
|
+
|
|
|
|
|
+ // if the node is a permanent one the ExprieTime will be Unix(0,0)
|
|
|
|
|
+ // Otherwise after the expireTime, the node will be deleted
|
|
|
ExpireTime time.Time `json:"expireTime"`
|
|
ExpireTime time.Time `json:"expireTime"`
|
|
|
|
|
+
|
|
|
|
|
+ // a channel to update the expireTime of the node
|
|
|
update chan time.Time `json:"-"`
|
|
update chan time.Time `json:"-"`
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -31,14 +47,14 @@ type Response struct {
|
|
|
Key string `json:"key"`
|
|
Key string `json:"key"`
|
|
|
OldValue string `json:"oldValue"`
|
|
OldValue string `json:"oldValue"`
|
|
|
NewValue string `json:"newValue"`
|
|
NewValue string `json:"newValue"`
|
|
|
|
|
+
|
|
|
|
|
+ // if the key existed before the action, this field should be true
|
|
|
|
|
+ // if the key did not exist before the action, this field should be false
|
|
|
Exist bool `json:"exist"`
|
|
Exist bool `json:"exist"`
|
|
|
|
|
+
|
|
|
Expiration time.Time `json:"expiration"`
|
|
Expiration time.Time `json:"expiration"`
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-
|
|
|
|
|
-// global store
|
|
|
|
|
-var s *Store
|
|
|
|
|
-
|
|
|
|
|
func init() {
|
|
func init() {
|
|
|
s = createStore()
|
|
s = createStore()
|
|
|
s.messager = nil
|
|
s.messager = nil
|
|
@@ -51,10 +67,12 @@ func createStore() *Store{
|
|
|
return s
|
|
return s
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// return a pointer to the store
|
|
|
func GetStore() *Store {
|
|
func GetStore() *Store {
|
|
|
return s
|
|
return s
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// set the messager of the store
|
|
|
func (s *Store)SetMessager(messager *chan string) {
|
|
func (s *Store)SetMessager(messager *chan string) {
|
|
|
s.messager = messager
|
|
s.messager = messager
|
|
|
}
|
|
}
|
|
@@ -66,44 +84,45 @@ func Set(key string, value string, expireTime time.Time) ([]byte, error) {
|
|
|
|
|
|
|
|
var isExpire bool = false
|
|
var isExpire bool = false
|
|
|
|
|
|
|
|
- isExpire = !expireTime.Equal(time.Unix(0,0))
|
|
|
|
|
|
|
+ isExpire = !expireTime.Equal(PERMANENT)
|
|
|
|
|
|
|
|
// when the slow follower receive the set command
|
|
// when the slow follower receive the set command
|
|
|
- // the key may be expired, we need also to delete
|
|
|
|
|
- // the previous value of key
|
|
|
|
|
|
|
+ // the key may be expired, we should not add the node
|
|
|
|
|
+ // also if the node exist, we need to delete the node
|
|
|
if isExpire && expireTime.Sub(time.Now()) < 0 {
|
|
if isExpire && expireTime.Sub(time.Now()) < 0 {
|
|
|
return Delete(key)
|
|
return Delete(key)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // get the node
|
|
|
node, ok := s.Nodes[key]
|
|
node, ok := s.Nodes[key]
|
|
|
|
|
|
|
|
if ok {
|
|
if ok {
|
|
|
-
|
|
|
|
|
// if node is not permanent before
|
|
// if node is not permanent before
|
|
|
// update its expireTime
|
|
// update its expireTime
|
|
|
- if !node.ExpireTime.Equal(time.Unix(0,0)) {
|
|
|
|
|
|
|
+ if !node.ExpireTime.Equal(PERMANENT) {
|
|
|
|
|
|
|
|
node.update <- expireTime
|
|
node.update <- expireTime
|
|
|
|
|
|
|
|
} else {
|
|
} else {
|
|
|
-
|
|
|
|
|
- // if we want the permanent to have expire time
|
|
|
|
|
- // we need to create a chan and create a func
|
|
|
|
|
|
|
+ // if we want the permanent node to have expire time
|
|
|
|
|
+ // we need to create a chan and create a go routine
|
|
|
if isExpire {
|
|
if isExpire {
|
|
|
node.update = make(chan time.Time)
|
|
node.update = make(chan time.Time)
|
|
|
-
|
|
|
|
|
go expire(key, node.update, expireTime)
|
|
go expire(key, node.update, expireTime)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // update the information of the node
|
|
|
node.ExpireTime = expireTime
|
|
node.ExpireTime = expireTime
|
|
|
-
|
|
|
|
|
node.Value = value
|
|
node.Value = value
|
|
|
- notify(SET, key, node.Value, value, true)
|
|
|
|
|
|
|
|
|
|
- msg, err := json.Marshal(Response{SET, key, node.Value, value, true, expireTime})
|
|
|
|
|
|
|
+ resp := Response{SET, key, node.Value, value, true, expireTime}
|
|
|
|
|
|
|
|
- // notify the web interface
|
|
|
|
|
|
|
+ msg, err := json.Marshal(resp)
|
|
|
|
|
+
|
|
|
|
|
+ notify(resp)
|
|
|
|
|
+
|
|
|
|
|
+ // send to the messager
|
|
|
if (s.messager != nil && err == nil) {
|
|
if (s.messager != nil && err == nil) {
|
|
|
|
|
|
|
|
*s.messager <- string(msg)
|
|
*s.messager <- string(msg)
|
|
@@ -111,21 +130,23 @@ func Set(key string, value string, expireTime time.Time) ([]byte, error) {
|
|
|
|
|
|
|
|
return msg, err
|
|
return msg, err
|
|
|
|
|
|
|
|
|
|
+ // add new node
|
|
|
} else {
|
|
} else {
|
|
|
|
|
|
|
|
- // add new node
|
|
|
|
|
update := make(chan time.Time)
|
|
update := make(chan time.Time)
|
|
|
|
|
|
|
|
s.Nodes[key] = Node{value, expireTime, update}
|
|
s.Nodes[key] = Node{value, expireTime, update}
|
|
|
|
|
|
|
|
- // nofity the watcher
|
|
|
|
|
- notify(SET, key, "", value, false)
|
|
|
|
|
-
|
|
|
|
|
if isExpire {
|
|
if isExpire {
|
|
|
go expire(key, update, expireTime)
|
|
go expire(key, update, expireTime)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- msg, err := json.Marshal(Response{SET, key, "", value, false, expireTime})
|
|
|
|
|
|
|
+ resp := Response{SET, key, "", value, false, expireTime}
|
|
|
|
|
+
|
|
|
|
|
+ msg, err := json.Marshal(resp)
|
|
|
|
|
+
|
|
|
|
|
+ // nofity the watcher
|
|
|
|
|
+ notify(resp)
|
|
|
|
|
|
|
|
// notify the web interface
|
|
// notify the web interface
|
|
|
if (s.messager != nil && err == nil) {
|
|
if (s.messager != nil && err == nil) {
|
|
@@ -137,23 +158,45 @@ func Set(key string, value string, expireTime time.Time) ([]byte, error) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// delete the key when it expires
|
|
|
|
|
|
|
+// should be used as a go routine to delete the key when it expires
|
|
|
func expire(key string, update chan time.Time, expireTime time.Time) {
|
|
func expire(key string, update chan time.Time, expireTime time.Time) {
|
|
|
duration := expireTime.Sub(time.Now())
|
|
duration := expireTime.Sub(time.Now())
|
|
|
|
|
|
|
|
for {
|
|
for {
|
|
|
select {
|
|
select {
|
|
|
- // timeout delte key
|
|
|
|
|
|
|
+ // timeout delete the node
|
|
|
case <-time.After(duration):
|
|
case <-time.After(duration):
|
|
|
- fmt.Println("expired at ", time.Now())
|
|
|
|
|
- Delete(key)
|
|
|
|
|
- return
|
|
|
|
|
|
|
+ node, ok := s.Nodes[key]
|
|
|
|
|
+ if !ok {
|
|
|
|
|
+ return
|
|
|
|
|
+ } else {
|
|
|
|
|
+
|
|
|
|
|
+ delete(s.Nodes, key)
|
|
|
|
|
+
|
|
|
|
|
+ resp := Response{DELETE, key, node.Value, "", true, node.ExpireTime}
|
|
|
|
|
+
|
|
|
|
|
+ msg, err := json.Marshal(resp)
|
|
|
|
|
+
|
|
|
|
|
+ notify(resp)
|
|
|
|
|
+
|
|
|
|
|
+ // notify the messager
|
|
|
|
|
+ if (s.messager != nil && err == nil) {
|
|
|
|
|
+
|
|
|
|
|
+ *s.messager <- string(msg)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
case updateTime := <-update:
|
|
case updateTime := <-update:
|
|
|
//update duration
|
|
//update duration
|
|
|
- if updateTime.Equal(time.Unix(0,0)) {
|
|
|
|
|
- fmt.Println("node became stable")
|
|
|
|
|
|
|
+ // if the node become a permanent one, the go routine is
|
|
|
|
|
+ // not needed
|
|
|
|
|
+ if updateTime.Equal(PERMANENT) {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
+ // update duration
|
|
|
duration = updateTime.Sub(time.Now())
|
|
duration = updateTime.Sub(time.Now())
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -172,20 +215,33 @@ func Get(key string) Response {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// delete the key, return the old value if the key exists
|
|
|
|
|
|
|
+// delete the key
|
|
|
func Delete(key string) ([]byte, error) {
|
|
func Delete(key string) ([]byte, error) {
|
|
|
key = path.Clean(key)
|
|
key = path.Clean(key)
|
|
|
|
|
|
|
|
node, ok := s.Nodes[key]
|
|
node, ok := s.Nodes[key]
|
|
|
|
|
|
|
|
if ok {
|
|
if ok {
|
|
|
- delete(s.Nodes, key)
|
|
|
|
|
|
|
|
|
|
- notify(DELETE, key, node.Value, "", true)
|
|
|
|
|
|
|
+ if node.ExpireTime.Equal(PERMANENT) {
|
|
|
|
|
|
|
|
- msg, err := json.Marshal(Response{DELETE, key, node.Value, "", true, node.ExpireTime})
|
|
|
|
|
|
|
+ delete(s.Nodes, key)
|
|
|
|
|
|
|
|
- // notify the web interface
|
|
|
|
|
|
|
+ } else {
|
|
|
|
|
+
|
|
|
|
|
+ // kill the expire go routine
|
|
|
|
|
+ node.update <- PERMANENT
|
|
|
|
|
+ delete(s.Nodes, key)
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ resp := Response{DELETE, key, node.Value, "", true, node.ExpireTime}
|
|
|
|
|
+
|
|
|
|
|
+ msg, err := json.Marshal(resp)
|
|
|
|
|
+
|
|
|
|
|
+ notify(resp)
|
|
|
|
|
+
|
|
|
|
|
+ // notify the messager
|
|
|
if (s.messager != nil && err == nil) {
|
|
if (s.messager != nil && err == nil) {
|
|
|
|
|
|
|
|
*s.messager <- string(msg)
|
|
*s.messager <- string(msg)
|
|
@@ -194,7 +250,6 @@ func Delete(key string) ([]byte, error) {
|
|
|
return msg, err
|
|
return msg, err
|
|
|
|
|
|
|
|
} else {
|
|
} else {
|
|
|
- // no notify to the watcher and web interface
|
|
|
|
|
|
|
|
|
|
return json.Marshal(Response{DELETE, key, "", "", false, time.Unix(0, 0)})
|
|
return json.Marshal(Response{DELETE, key, "", "", false, time.Unix(0, 0)})
|
|
|
}
|
|
}
|
|
@@ -213,20 +268,25 @@ func (s *Store)Save() ([]byte, error) {
|
|
|
// recovery the state of the stroage system from a previous state
|
|
// recovery the state of the stroage system from a previous state
|
|
|
func (s *Store)Recovery(state []byte) error {
|
|
func (s *Store)Recovery(state []byte) error {
|
|
|
err := json.Unmarshal(state, s)
|
|
err := json.Unmarshal(state, s)
|
|
|
|
|
+
|
|
|
|
|
+ // clean the expired nodes
|
|
|
clean()
|
|
clean()
|
|
|
|
|
+
|
|
|
return err
|
|
return err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// clean all expired keys
|
|
// clean all expired keys
|
|
|
func clean() {
|
|
func clean() {
|
|
|
for key, node := range s.Nodes{
|
|
for key, node := range s.Nodes{
|
|
|
- // stable node
|
|
|
|
|
- if node.ExpireTime.Equal(time.Unix(0,0)) {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if node.ExpireTime.Equal(PERMANENT) {
|
|
|
continue
|
|
continue
|
|
|
} else {
|
|
} else {
|
|
|
|
|
+
|
|
|
if node.ExpireTime.Sub(time.Now()) >= time.Second {
|
|
if node.ExpireTime.Sub(time.Now()) >= time.Second {
|
|
|
node.update = make(chan time.Time)
|
|
node.update = make(chan time.Time)
|
|
|
go expire(key, node.update, node.ExpireTime)
|
|
go expire(key, node.update, node.ExpireTime)
|
|
|
|
|
+
|
|
|
} else {
|
|
} else {
|
|
|
// we should delete this node
|
|
// we should delete this node
|
|
|
delete(s.Nodes, key)
|
|
delete(s.Nodes, key)
|