// syncWatchers periodically syncs unsynced watchers by: Iterate all unsynced // watchers to get the minimum revision within its range, skipping the // watcher if its current revision is behind the compact revision of the // store. And use this minimum revision to get all key-value pairs. Then send // those events to watchers. func (s *watchableStore) syncWatchers() { s.store.mu.Lock() defer s.store.mu.Unlock() if len(s.unsynced) == 0 { return } // in order to find key-value pairs from unsynced watchers, we need to // find min revision index, and these revisions can be used to // query the backend store of key-value pairs minRev := int64(math.MaxInt64) curRev := s.store.currentRev.main compactionRev := s.store.compactMainRev prefixes := make(map[string]struct{}) for _, set := range s.unsynced { for w := range set { k := string(w.key) if w.cur > curRev { panic("watcher current revision should not exceed current revision") } if w.cur < compactionRev { select { case w.ch <- WatchResponse{WatchID: w.id, CompactRevision: compactionRev}: s.unsynced.delete(w) default: // retry next time } continue } if minRev >= w.cur { minRev = w.cur } if w.prefix { prefixes[k] = struct{}{} } } } minBytes, maxBytes := newRevBytes(), newRevBytes() revToBytes(revision{main: minRev}, minBytes) revToBytes(revision{main: curRev + 1}, maxBytes) // UnsafeRange returns keys and values. And in boltdb, keys are revisions. // values are actual key-value pairs in backend. tx := s.store.b.BatchTx() tx.Lock() ks, vs := tx.UnsafeRange(keyBucketName, minBytes, maxBytes, 0) evs := []storagepb.Event{} // get the list of all events from all key-value pairs for i, v := range vs { var kv storagepb.KeyValue if err := kv.Unmarshal(v); err != nil { log.Panicf("storage: cannot unmarshal event: %v", err) } k := string(kv.Key) if _, ok := s.unsynced.getSetByKey(k); !ok && !matchPrefix(k, prefixes) { continue } var ev storagepb.Event switch { case isTombstone(ks[i]): ev.Type = storagepb.DELETE default: ev.Type = storagepb.PUT } ev.Kv = &kv evs = append(evs, ev) } tx.Unlock() for w, es := range newWatcherToEventMap(s.unsynced, evs) { select { // s.store.Rev also uses Lock, so just return directly case w.ch <- WatchResponse{WatchID: w.id, Events: es, Revision: s.store.currentRev.main}: pendingEventsGauge.Add(float64(len(es))) default: // TODO: handle the full unsynced watchers. // continue to process other watchers for now, the full ones // will be processed next time and hopefully it will not be full. continue } w.cur = curRev s.synced.add(w) s.unsynced.delete(w) } slowWatcherGauge.Set(float64(len(s.unsynced))) }
// syncWatchers periodically syncs unsynced watchers by: Iterate all unsynced // watchers to get the minimum revision within its range, skipping the // watcher if its current revision is behind the compact revision of the // store. And use this minimum revision to get all key-value pairs. Then send // those events to watchers. func (s *watchableStore) syncWatchers() { s.store.mu.Lock() defer s.store.mu.Unlock() if len(s.unsynced) == 0 { return } // in order to find key-value pairs from unsynced watchers, we need to // find min revision index, and these revisions can be used to // query the backend store of key-value pairs minRev := int64(math.MaxInt64) curRev := s.store.currentRev.main compactionRev := s.store.compactMainRev // TODO: change unsynced struct type same to this keyToUnsynced := make(map[string]map[*watcher]struct{}) for w := range s.unsynced { k := string(w.key) if w.cur > curRev { panic("watcher current revision should not exceed current revision") } if w.cur < compactionRev { // TODO: return error compacted to that watcher instead of // just removing it sliently from unsynced. delete(s.unsynced, w) continue } if minRev >= w.cur { minRev = w.cur } if _, ok := keyToUnsynced[k]; !ok { keyToUnsynced[k] = make(map[*watcher]struct{}) } keyToUnsynced[k][w] = struct{}{} } minBytes, maxBytes := newRevBytes(), newRevBytes() revToBytes(revision{main: minRev}, minBytes) revToBytes(revision{main: curRev + 1}, maxBytes) // UnsafeRange returns keys and values. And in boltdb, keys are revisions. // values are actual key-value pairs in backend. tx := s.store.b.BatchTx() tx.Lock() ks, vs := tx.UnsafeRange(keyBucketName, minBytes, maxBytes, 0) tx.Unlock() evs := []storagepb.Event{} // get the list of all events from all key-value pairs for i, v := range vs { var kv storagepb.KeyValue if err := kv.Unmarshal(v); err != nil { log.Panicf("storage: cannot unmarshal event: %v", err) } k := string(kv.Key) if _, ok := keyToUnsynced[k]; !ok { continue } var ev storagepb.Event switch { case isTombstone(ks[i]): ev.Type = storagepb.DELETE default: ev.Type = storagepb.PUT } ev.Kv = &kv evs = append(evs, ev) } for w, es := range newWatcherToEventMap(keyToUnsynced, evs) { wr := WatchResponse{WatchID: w.id, Events: es} select { case w.ch <- wr: pendingEventsGauge.Add(float64(len(es))) default: // TODO: handle the full unsynced watchers. // continue to process other watchers for now, the full ones // will be processed next time and hopefully it will not be full. continue } k := string(w.key) if err := unsafeAddWatcher(&s.synced, k, w); err != nil { log.Panicf("error unsafeAddWatcher (%v) for key %s", err, k) } delete(s.unsynced, w) } slowWatcherGauge.Set(float64(len(s.unsynced))) }