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  }