// NameWatch watches for specific member names to join. It can be used // to discover when a known group of members is present. // // w := condition.NewNameWatch(client, "path", "to", "watch") // defer w.Stop() // // The path must be to an etcd directory, otherwise the watch will not // function. // // for { // select { // case <- w.WatchUntil("member1", "member2"): // ... 2 partners are present ... // case err := <- w.WatchError(): // ... error code ... // } // } // // Or // for { // select { // case names := <- w.WatchNames(): // ... names of partners currently present ... // case err := <- w.WatchError(): // ... error code ... // } // } // // It is not correct to select from both WatchUntil and WatchCount at // the same time. func NewNameWatch(e *etcd.Client, path ...string) NameWatch { key := strings.Join(path, "/") stopc := make(chan bool) namesc := make(chan []string, 1) errorc := make(chan error, 1) go func() { defer e.Close() var res *etcd.Response var err error var exists bool for !exists { res, err = e.Get(key, false, false) if err != nil { switch err := err.(type) { case *etcd.EtcdError: if err.ErrorCode == 100 { ticker := time.NewTicker(5 * time.Second) select { case <-stopc: ticker.Stop() return case <-ticker.C: ticker.Stop() } } default: errorc <- err return } } else { exists = true } } index := uint64(0) if res != nil && res.Node != nil && res.Node.Dir { names := make([]string, 0, len(res.Node.Nodes)) for _, n := range res.Node.Nodes { names = append(names, n.Key) } select { case namesc <- names: default: } index = res.EtcdIndex } else { select { case namesc <- nil: default: } } watch := make(chan *etcd.Response) go e.Watch(key, index, true, watch, stopc) for { select { case <-stopc: return case res, open := <-watch: if !open { select { case errorc <- fmt.Errorf("name watch closed unexpectedly for: %v", key): default: } return } res, err := e.Get(key, false, false) if err != nil { switch err := err.(type) { case *etcd.EtcdError: if err.ErrorCode == 100 { // Do nothing. } default: errorc <- err return } } if res != nil && res.Node != nil && res.Node.Dir { names := make([]string, 0, len(res.Node.Nodes)) for _, n := range res.Node.Nodes { names = append(names, n.Key) } select { case namesc <- names: default: } } else { select { case namesc <- nil: default: } } } } }() return &namewatch{ stopc: stopc, namesc: namesc, errorc: errorc, } }
// CountWatch watches for some number of joins. It can be used to discover // when a known number of group memebers are present. // // w := condition.NewCountWatch(client, "path", "to", "watch") // defer w.Stop() // // The path must be to an etcd directory, otherwise the watch will not // function. // // for { // select { // case <- w.WatchUntil(10): // ... 10 partners are present ... // case err := <- w.WatchError(): // ... error code ... // } // } // // Or // for { // select { // case n := <- w.WatchCount(): // ... n partners are present ... // case err := <- w.WatchError(): // ... error code ... // } // } // // It is not correct to select from both WatchUntil and WatchCount at // the same time. func NewCountWatch(e *etcd.Client, path ...string) CountWatch { key := strings.Join(path, "/") stopc := make(chan bool) countc := make(chan int, 1) errorc := make(chan error, 1) go func() { defer e.Close() var res *etcd.Response var err error var exists bool for !exists { res, err = e.Get(key, false, false) if err != nil { switch err := err.(type) { case *etcd.EtcdError: if err.ErrorCode == 100 { ticker := time.NewTicker(5 * time.Second) select { case <-stopc: ticker.Stop() return case <-ticker.C: ticker.Stop() } } default: errorc <- err return } } else { exists = true } } index := uint64(0) if res != nil && res.Node != nil && res.Node.Dir { select { case countc <- len(res.Node.Nodes): default: } index = res.EtcdIndex } else { select { case countc <- 0: default: } } watch := make(chan *etcd.Response) go e.Watch(key, index, true, watch, stopc) for { select { case <-stopc: return case res, open := <-watch: if !open { select { case errorc <- fmt.Errorf("count watch closed unexpectedly: %v", key): default: } return } res, err := e.Get(key, false, false) if err != nil { switch err := err.(type) { case *etcd.EtcdError: if err.ErrorCode == 100 { // Do nothing. } default: errorc <- err return } } if res != nil && res.Node != nil && res.Node.Dir { select { case countc <- len(res.Node.Nodes): default: } } else { select { case countc <- 0: default: } } } } }() return &countwatch{ stopc: stopc, countc: countc, errorc: errorc, } }