// Until reads items from the watch until each provided condition succeeds, and then returns the last watch // encountered. The first condition that returns an error terminates the watch (and the event is also returned). // If no event has been received, the returned event will be nil. // TODO: move to pkg/watch upstream func Until(timeout time.Duration, watcher watch.Interface, conditions ...WatchConditionFunc) (*watch.Event, error) { ch := watcher.ResultChan() defer watcher.Stop() var after <-chan time.Time if timeout > 0 { after = time.After(timeout) } else { ch := make(chan time.Time) close(ch) after = ch } var lastEvent *watch.Event for _, condition := range conditions { for { select { case event, ok := <-ch: if !ok { return lastEvent, wait.ErrWaitTimeout } lastEvent = &event // TODO: check for watch expired error and retry watch from latest point? done, err := condition(event) if err != nil { return lastEvent, err } if done { return lastEvent, nil } case <-after: return lastEvent, wait.ErrWaitTimeout } } } return lastEvent, wait.ErrWaitTimeout }
// watchHandler watches w and keeps *resourceVersion up to date. func (r *Reflector) watchHandler(w watch.Interface, resourceVersion *string, resyncCh <-chan time.Time, stopCh <-chan struct{}) error { start := time.Now() eventCount := 0 // Stopping the watcher should be idempotent and if we return from this function there's no way // we're coming back in with the same watch interface. defer w.Stop() loop: for { select { case <-stopCh: return errorStopRequested case <-resyncCh: return errorResyncRequested case event, ok := <-w.ResultChan(): if !ok { break loop } if event.Type == watch.Error { return apierrs.FromObject(event.Object) } if e, a := r.expectedType, reflect.TypeOf(event.Object); e != nil && e != a { utilruntime.HandleError(fmt.Errorf("%s: expected type %v, but watch event object had type %v", r.name, e, a)) continue } meta, err := meta.Accessor(event.Object) if err != nil { utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event)) continue } newResourceVersion := meta.GetResourceVersion() switch event.Type { case watch.Added: r.store.Add(event.Object) case watch.Modified: r.store.Update(event.Object) case watch.Deleted: // TODO: Will any consumers need access to the "last known // state", which is passed in event.Object? If so, may need // to change this. r.store.Delete(event.Object) default: utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event)) } *resourceVersion = newResourceVersion r.setLastSyncResourceVersion(newResourceVersion) eventCount++ } } watchDuration := time.Now().Sub(start) if watchDuration < 1*time.Second && eventCount == 0 { glog.V(4).Infof("%s: Unexpected watch close - watch lasted less than a second and no items received", r.name) return errors.New("very short watch") } glog.V(4).Infof("%s: Watch close - %v total %v items received", r.name, r.expectedType, eventCount) return nil }
// WatchLoop loops, passing events in w to fn. // If user sends interrupt signal, shut down cleanly. Otherwise, never return. func WatchLoop(w watch.Interface, fn func(watch.Event) error) { signals := make(chan os.Signal, 1) signal.Notify(signals, os.Interrupt) defer signal.Stop(signals) for { select { case event, ok := <-w.ResultChan(): if !ok { return } if err := fn(event); err != nil { w.Stop() } case <-signals: w.Stop() } } }
func watcher(ctx context.Context) { debugf("starting watcher\n") var closed = watch.Event{} var w watch.Interface var wchan = make(chan watch.Interface) defer close(wchan) for { go acquireWatch(ctx, wchan) select { case w = <-wchan: debugf("watch acquired\n") case <-ctx.Done(): debugf("watcher cancelled\n") return } loop: for { select { case e := <-w.ResultChan(): if e == closed { debugf("watcher closed channel with event: %+v\n", e) break loop } handleWatchEvent(e) case <-ctx.Done(): w.Stop() debugf("watcher cancelled\n") return } } } }
// watchRollbackEvent watches for rollback events and returns rollback result func watchRollbackEvent(w watch.Interface) string { signals := make(chan os.Signal, 1) signal.Notify(signals, os.Interrupt, os.Kill, syscall.SIGTERM) for { select { case event, ok := <-w.ResultChan(): if !ok { return "" } obj, ok := event.Object.(*api.Event) if !ok { w.Stop() return "" } isRollback, result := isRollbackEvent(obj) if isRollback { w.Stop() return result } case <-signals: w.Stop() } } }