func newWatchBroadcast(wp *watchProxy, w *watcher, update func(*watchBroadcast)) *watchBroadcast { cctx, cancel := context.WithCancel(wp.ctx) wb := &watchBroadcast{ cancel: cancel, nextrev: w.nextrev, receivers: make(map[*watcher]struct{}), donec: make(chan struct{}), } wb.add(w) go func() { defer close(wb.donec) // loop because leader loss will close channel for cctx.Err() == nil { wch := wp.cw.Watch(cctx, w.wr.key, clientv3.WithRange(w.wr.end), clientv3.WithProgressNotify(), clientv3.WithCreatedNotify(), clientv3.WithRev(wb.nextrev), clientv3.WithPrevKV(), ) for wr := range wch { wb.bcast(wr) update(wb) } wp.retryLimiter.Wait(cctx) } }() return wb }
// startWatching does: // - get current objects if initialRev=0; set initialRev to current rev // - watch on given key and send events to process. func (wc *watchChan) startWatching(watchClosedCh chan struct{}) { if wc.initialRev == 0 { if err := wc.sync(); err != nil { glog.Errorf("failed to sync with latest state: %v", err) wc.sendError(err) return } } opts := []clientv3.OpOption{clientv3.WithRev(wc.initialRev + 1), clientv3.WithPrevKV()} if wc.recursive { opts = append(opts, clientv3.WithPrefix()) } wch := wc.watcher.client.Watch(wc.ctx, wc.key, opts...) for wres := range wch { if wres.Err() != nil { err := wres.Err() // If there is an error on server (e.g. compaction), the channel will return it before closed. glog.Errorf("watch chan error: %v", err) wc.sendError(err) return } for _, e := range wres.Events { wc.sendEvent(parseEvent(e)) } } // When we come to this point, it's only possible that client side ends the watch. // e.g. cancel the context, close the client. // If this watch chan is broken and context isn't cancelled, other goroutines will still hang. // We should notify the main thread that this goroutine has exited. close(watchClosedCh) }
func getDelOp(cmd *cobra.Command, args []string) (string, []clientv3.OpOption) { if len(args) == 0 || len(args) > 2 { ExitWithError(ExitBadArgs, fmt.Errorf("del command needs one argument as key and an optional argument as range_end.")) } if delPrefix && delFromKey { ExitWithError(ExitBadArgs, fmt.Errorf("`--prefix` and `--from-key` cannot be set at the same time, choose one.")) } opts := []clientv3.OpOption{} key := args[0] if len(args) > 1 { if delPrefix || delFromKey { ExitWithError(ExitBadArgs, fmt.Errorf("too many arguments, only accept one argument when `--prefix` or `--from-key` is set.")) } opts = append(opts, clientv3.WithRange(args[1])) } if delPrefix { opts = append(opts, clientv3.WithPrefix()) } if delPrevKV { opts = append(opts, clientv3.WithPrevKV()) } if delFromKey { if len(key) == 0 { key = "\x00" } opts = append(opts, clientv3.WithFromKey()) } return key, opts }
func getPutOp(cmd *cobra.Command, args []string) (string, string, []clientv3.OpOption) { if len(args) == 0 { ExitWithError(ExitBadArgs, fmt.Errorf("put command needs 1 argument and input from stdin or 2 arguments.")) } key := args[0] value, err := argOrStdin(args, os.Stdin, 1) if err != nil { ExitWithError(ExitBadArgs, fmt.Errorf("put command needs 1 argument and input from stdin or 2 arguments.")) } id, err := strconv.ParseInt(leaseStr, 16, 64) if err != nil { ExitWithError(ExitBadArgs, fmt.Errorf("bad lease ID (%v), expecting ID in Hex", err)) } opts := []clientv3.OpOption{} if id != 0 { opts = append(opts, clientv3.WithLease(clientv3.LeaseID(id))) } if putPrevKV { opts = append(opts, clientv3.WithPrevKV()) } return key, value, opts }
// watchCommandFunc executes the "watch" command. func watchCommandFunc(cmd *cobra.Command, args []string) { if watchInteractive { watchInteractiveFunc(cmd, args) return } if len(args) < 1 || len(args) > 2 { ExitWithError(ExitBadArgs, fmt.Errorf("watch in non-interactive mode requires one or two arguments as key or prefix, with range end")) } opts := []clientv3.OpOption{clientv3.WithRev(watchRev)} key := args[0] if len(args) == 2 { if watchPrefix { ExitWithError(ExitBadArgs, fmt.Errorf("`range_end` and `--prefix` cannot be set at the same time, choose one")) } opts = append(opts, clientv3.WithRange(args[1])) } if watchPrefix { opts = append(opts, clientv3.WithPrefix()) } if watchPrevKey { opts = append(opts, clientv3.WithPrevKV()) } c := mustClientFromCmd(cmd) wc := c.Watch(context.TODO(), key, opts...) printWatchCh(wc) err := c.Close() if err == nil { ExitWithError(ExitInterrupted, fmt.Errorf("watch is canceled by the server")) } ExitWithError(ExitBadConnection, err) }
func DelRequestToOp(r *pb.DeleteRangeRequest) clientv3.Op { opts := []clientv3.OpOption{} if len(r.RangeEnd) != 0 { opts = append(opts, clientv3.WithRange(string(r.RangeEnd))) } if r.PrevKv { opts = append(opts, clientv3.WithPrevKV()) } return clientv3.OpDelete(string(r.Key), opts...) }
func getWatchChan(c *clientv3.Client, args []string) (clientv3.WatchChan, error) { if len(args) < 1 || len(args) > 2 { return nil, fmt.Errorf("bad number of arguments") } key := args[0] opts := []clientv3.OpOption{clientv3.WithRev(watchRev)} if len(args) == 2 { if watchPrefix { return nil, fmt.Errorf("`range_end` and `--prefix` are mutually exclusive") } opts = append(opts, clientv3.WithRange(args[1])) } if watchPrefix { opts = append(opts, clientv3.WithPrefix()) } if watchPrevKey { opts = append(opts, clientv3.WithPrevKV()) } return c.Watch(context.TODO(), key, opts...), nil }
func (gw *gRPCWatcher) firstNext() ([]*naming.Update, error) { // Use serialized request so resolution still works if the target etcd // server is partitioned away from the quorum. resp, err := gw.c.Get(gw.ctx, gw.target, etcd.WithPrefix(), etcd.WithSerializable()) if gw.err = err; err != nil { return nil, err } updates := make([]*naming.Update, 0, len(resp.Kvs)) for _, kv := range resp.Kvs { var jupdate naming.Update if err := json.Unmarshal(kv.Value, &jupdate); err != nil { continue } updates = append(updates, &jupdate) } opts := []etcd.OpOption{etcd.WithRev(resp.Header.Revision + 1), etcd.WithPrefix(), etcd.WithPrevKV()} gw.wch = gw.c.Watch(gw.ctx, gw.target, opts...) return updates, nil }
func getDelOp(cmd *cobra.Command, args []string) (string, []clientv3.OpOption) { if len(args) == 0 || len(args) > 2 { ExitWithError(ExitBadArgs, fmt.Errorf("del command needs one argument as key and an optional argument as range_end.")) } opts := []clientv3.OpOption{} key := args[0] if len(args) > 1 { if delPrefix { ExitWithError(ExitBadArgs, fmt.Errorf("too many arguments, only accept one argument when `--prefix` is set.")) } opts = append(opts, clientv3.WithRange(args[1])) } if delPrefix { opts = append(opts, clientv3.WithPrefix()) } if delPrevKV { opts = append(opts, clientv3.WithPrevKV()) } return key, opts }
func newWatchBroadcast(wp *watchProxy, w *watcher, update func(*watchBroadcast)) *watchBroadcast { cctx, cancel := context.WithCancel(wp.ctx) wb := &watchBroadcast{ cancel: cancel, nextrev: w.nextrev, receivers: make(map[*watcher]struct{}), donec: make(chan struct{}), } wb.add(w) go func() { defer close(wb.donec) // loop because leader loss will close channel for cctx.Err() == nil { opts := []clientv3.OpOption{ clientv3.WithRange(w.wr.end), clientv3.WithProgressNotify(), clientv3.WithRev(wb.nextrev), clientv3.WithPrevKV(), } // The create notification should be the first response; // if the watch is recreated following leader loss, it // shouldn't post a second create response to the client. if wb.responses == 0 { opts = append(opts, clientv3.WithCreatedNotify()) } wch := wp.cw.Watch(cctx, w.wr.key, opts...) for wr := range wch { wb.bcast(wr) update(wb) } wp.retryLimiter.Wait(cctx) } }() return wb }