github.com/macb/etcd@v0.3.1-0.20140227003422-a60481c6b1a0/store/watcher_hub.go (about) 1 package store 2 3 import ( 4 "container/list" 5 "path" 6 "strings" 7 "sync" 8 "sync/atomic" 9 10 etcdErr "github.com/coreos/etcd/error" 11 ) 12 13 // A watcherHub contains all subscribed watchers 14 // watchers is a map with watched path as key and watcher as value 15 // EventHistory keeps the old events for watcherHub. It is used to help 16 // watcher to get a continuous event history. Or a watcher might miss the 17 // event happens between the end of the first watch command and the start 18 // of the second command. 19 type watcherHub struct { 20 mutex sync.Mutex // protect the hash map 21 watchers map[string]*list.List 22 count int64 // current number of watchers. 23 EventHistory *EventHistory 24 } 25 26 // newWatchHub creates a watchHub. The capacity determines how many events we will 27 // keep in the eventHistory. 28 // Typically, we only need to keep a small size of history[smaller than 20K]. 29 // Ideally, it should smaller than 20K/s[max throughput] * 2 * 50ms[RTT] = 2000 30 func newWatchHub(capacity int) *watcherHub { 31 return &watcherHub{ 32 watchers: make(map[string]*list.List), 33 EventHistory: newEventHistory(capacity), 34 } 35 } 36 37 // Watch function returns a watcher. 38 // If recursive is true, the first change after index under key will be sent to the event channel of the watcher. 39 // If recursive is false, the first change after index at key will be sent to the event channel of the watcher. 40 // If index is zero, watch will start from the current index + 1. 41 func (wh *watcherHub) watch(key string, recursive, stream bool, index uint64) (*Watcher, *etcdErr.Error) { 42 event, err := wh.EventHistory.scan(key, recursive, index) 43 44 if err != nil { 45 return nil, err 46 } 47 48 w := &Watcher{ 49 EventChan: make(chan *Event, 1), // use a buffered channel 50 recursive: recursive, 51 stream: stream, 52 sinceIndex: index, 53 hub: wh, 54 } 55 56 if event != nil { 57 w.EventChan <- event 58 return w, nil 59 } 60 61 wh.mutex.Lock() 62 defer wh.mutex.Unlock() 63 64 l, ok := wh.watchers[key] 65 66 var elem *list.Element 67 68 if ok { // add the new watcher to the back of the list 69 elem = l.PushBack(w) 70 71 } else { // create a new list and add the new watcher 72 l = list.New() 73 elem = l.PushBack(w) 74 wh.watchers[key] = l 75 } 76 77 w.remove = func() { 78 if w.removed { // avoid remove it twice 79 return 80 } 81 w.removed = true 82 l.Remove(elem) 83 atomic.AddInt64(&wh.count, -1) 84 if l.Len() == 0 { 85 delete(wh.watchers, key) 86 } 87 } 88 89 atomic.AddInt64(&wh.count, 1) 90 91 return w, nil 92 } 93 94 // notify function accepts an event and notify to the watchers. 95 func (wh *watcherHub) notify(e *Event) { 96 e = wh.EventHistory.addEvent(e) // add event into the eventHistory 97 98 segments := strings.Split(e.Node.Key, "/") 99 100 currPath := "/" 101 102 // walk through all the segments of the path and notify the watchers 103 // if the path is "/foo/bar", it will notify watchers with path "/", 104 // "/foo" and "/foo/bar" 105 106 for _, segment := range segments { 107 currPath = path.Join(currPath, segment) 108 // notify the watchers who interests in the changes of current path 109 wh.notifyWatchers(e, currPath, false) 110 } 111 } 112 113 func (wh *watcherHub) notifyWatchers(e *Event, nodePath string, deleted bool) { 114 wh.mutex.Lock() 115 defer wh.mutex.Unlock() 116 117 l, ok := wh.watchers[nodePath] 118 if ok { 119 curr := l.Front() 120 121 for curr != nil { 122 next := curr.Next() // save reference to the next one in the list 123 124 w, _ := curr.Value.(*Watcher) 125 126 originalPath := (e.Node.Key == nodePath) 127 if (originalPath || !isHidden(nodePath, e.Node.Key)) && w.notify(e, originalPath, deleted) { 128 if !w.stream { // do not remove the stream watcher 129 // if we successfully notify a watcher 130 // we need to remove the watcher from the list 131 // and decrease the counter 132 l.Remove(curr) 133 atomic.AddInt64(&wh.count, -1) 134 } 135 } 136 137 curr = next // update current to the next element in the list 138 } 139 140 if l.Len() == 0 { 141 // if we have notified all watcher in the list 142 // we can delete the list 143 delete(wh.watchers, nodePath) 144 } 145 } 146 } 147 148 // clone function clones the watcherHub and return the cloned one. 149 // only clone the static content. do not clone the current watchers. 150 func (wh *watcherHub) clone() *watcherHub { 151 clonedHistory := wh.EventHistory.clone() 152 153 return &watcherHub{ 154 EventHistory: clonedHistory, 155 } 156 } 157 158 // isHidden checks to see if key path is considered hidden to watch path i.e. the 159 // last element is hidden or it's within a hidden directory 160 func isHidden(watchPath, keyPath string) bool { 161 // When deleting a directory, watchPath might be deeper than the actual keyPath 162 // For example, when deleting /foo we also need to notify watchers on /foo/bar. 163 if len(watchPath) > len(keyPath) { 164 return false 165 } 166 // if watch path is just a "/", after path will start without "/" 167 // add a "/" to deal with the special case when watchPath is "/" 168 afterPath := path.Clean("/" + keyPath[len(watchPath):]) 169 return strings.Contains(afterPath, "/_") 170 }