зеркало из https://github.com/Azure/metaetcd.git
Reject watches that start too early
This commit is contained in:
Родитель
9a218be335
Коммит
2d8ca7222b
|
@ -22,10 +22,17 @@ var (
|
||||||
Name: "metaetcd_active_watch_count",
|
Name: "metaetcd_active_watch_count",
|
||||||
Help: "Number of active watch connections.",
|
Help: "Number of active watch connections.",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watchLateStartCount = prometheus.NewCounter(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "metaetcd_watch_late_start_count",
|
||||||
|
Help: "Number of total watch requests with revisions that are too late.",
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
prometheus.MustRegister(requestCount)
|
prometheus.MustRegister(requestCount)
|
||||||
prometheus.MustRegister(getMemberRevDepth)
|
prometheus.MustRegister(getMemberRevDepth)
|
||||||
prometheus.MustRegister(activeWatchCount)
|
prometheus.MustRegister(activeWatchCount)
|
||||||
|
prometheus.MustRegister(watchLateStartCount)
|
||||||
}
|
}
|
||||||
|
|
|
@ -186,9 +186,18 @@ func (s *server) Watch(srv etcdserverpb.Watch_WatchServer) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch := s.members.WatchMux.Watch(r.Key, r.RangeEnd, r.StartRevision)
|
||||||
|
if watch == nil {
|
||||||
|
zap.L().Warn("watch start revision is too early", zap.Int64("rev", r.StartRevision))
|
||||||
|
watchLateStartCount.Inc()
|
||||||
|
ch <- &etcdserverpb.WatchResponse{WatchId: r.WatchId, Created: false, Header: &etcdserverpb.ResponseHeader{}}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
wg.Go(func() error {
|
wg.Go(func() error {
|
||||||
zap.L().Info("adding keyspace to watch connection", zap.String("watchID", id), zap.String("start", string(r.Key)), zap.String("end", string(r.RangeEnd)), zap.Int64("metaRev", r.StartRevision))
|
zap.L().Info("starting watch connection", zap.String("watchID", id), zap.String("start", string(r.Key)), zap.String("end", string(r.RangeEnd)), zap.Int64("metaRev", r.StartRevision))
|
||||||
s.members.WatchMux.Watch(srv.Context(), r.Key, r.RangeEnd, r.StartRevision, ch)
|
watch.Run(srv.Context(), ch)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
ch <- &etcdserverpb.WatchResponse{WatchId: r.WatchId, Created: true, Header: &etcdserverpb.ResponseHeader{}}
|
ch <- &etcdserverpb.WatchResponse{WatchId: r.WatchId, Created: true, Header: &etcdserverpb.ResponseHeader{}}
|
||||||
|
|
|
@ -12,22 +12,20 @@ import (
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Return error for attempts to start watch connection before the oldest message in the buffer
|
|
||||||
|
|
||||||
// TODO: Consider watching only the metakey instead of maintaining event buffer for entire keyspace
|
// TODO: Consider watching only the metakey instead of maintaining event buffer for entire keyspace
|
||||||
|
|
||||||
type buffer struct {
|
type buffer struct {
|
||||||
mut sync.Mutex
|
mut sync.Mutex
|
||||||
list *list.List
|
list *list.List
|
||||||
gapTimeout time.Duration
|
gapTimeout time.Duration
|
||||||
maxLen int
|
maxLen int
|
||||||
upperBound int64
|
lowerBound, upperBound int64
|
||||||
bcast *broadcast
|
bcast *broadcast
|
||||||
upperVal *list.Element
|
upperVal *list.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBuffer(gapTimeout time.Duration, maxLen int, bcast *broadcast) *buffer {
|
func newBuffer(gapTimeout time.Duration, maxLen int, bcast *broadcast) *buffer {
|
||||||
return &buffer{list: list.New(), gapTimeout: gapTimeout, maxLen: maxLen, bcast: bcast}
|
return &buffer{list: list.New(), gapTimeout: gapTimeout, maxLen: maxLen, bcast: bcast, lowerBound: -1}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *buffer) Run(ctx context.Context) {
|
func (b *buffer) Run(ctx context.Context) {
|
||||||
|
@ -113,13 +111,24 @@ func (b *buffer) trimUnlocked() {
|
||||||
if front == b.upperVal {
|
if front == b.upperVal {
|
||||||
return // don't trim events until the gap has been filled
|
return // don't trim events until the gap has been filled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
next := front.Next()
|
||||||
|
if next != nil {
|
||||||
|
newFront := next.Value.(*eventWrapper)
|
||||||
|
b.lowerBound = newFront.Kv.ModRevision
|
||||||
|
}
|
||||||
|
|
||||||
b.list.Remove(front)
|
b.list.Remove(front)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *buffer) StartRange(start int64) *list.Element {
|
func (b *buffer) StartRange(start int64) (*list.Element, bool) {
|
||||||
b.mut.Lock()
|
b.mut.Lock()
|
||||||
defer b.mut.Unlock()
|
defer b.mut.Unlock()
|
||||||
|
|
||||||
|
if start < b.lowerBound {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
val := b.list.Front()
|
val := b.list.Front()
|
||||||
for i := 0; true; i++ {
|
for i := 0; true; i++ {
|
||||||
if val == nil {
|
if val == nil {
|
||||||
|
@ -130,11 +139,11 @@ func (b *buffer) StartRange(start int64) *list.Element {
|
||||||
break // buffer starts after the requested start rev
|
break // buffer starts after the requested start rev
|
||||||
}
|
}
|
||||||
if e.Kv.ModRevision == start {
|
if e.Kv.ModRevision == start {
|
||||||
return val
|
return val, true
|
||||||
}
|
}
|
||||||
val = val.Next()
|
val = val.Next()
|
||||||
}
|
}
|
||||||
return nil
|
return nil, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *buffer) Range(start *list.Element, ivl adt.IntervalTree) (slice []*mvccpb.Event, n int, pos *list.Element) {
|
func (b *buffer) Range(start *list.Element, ivl adt.IntervalTree) (slice []*mvccpb.Event, n int, pos *list.Element) {
|
||||||
|
@ -193,6 +202,9 @@ func (b *buffer) bridgeGapUnlocked() (ok, changed bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
b.upperBound = valE.Kv.ModRevision
|
b.upperBound = valE.Kv.ModRevision
|
||||||
|
if b.lowerBound == -1 {
|
||||||
|
b.lowerBound = valE.Kv.ModRevision
|
||||||
|
}
|
||||||
currentWatchRev.Set(float64(b.upperBound))
|
currentWatchRev.Set(float64(b.upperBound))
|
||||||
watchLatency.Observe(age.Seconds())
|
watchLatency.Observe(age.Seconds())
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package watch
|
package watch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"container/list"
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -32,7 +33,7 @@ func TestBufferOrdering(t *testing.T) {
|
||||||
|
|
||||||
// The first event starts at rev 2, wait for the initial gap
|
// The first event starts at rev 2, wait for the initial gap
|
||||||
b.Push(eventWithModRev(2))
|
b.Push(eventWithModRev(2))
|
||||||
_, n, _ := b.Range(b.StartRange(0), defaultKeyRange)
|
_, n, _ := b.Range(startRange(b, 0), defaultKeyRange)
|
||||||
assert.Equal(t, 0, n)
|
assert.Equal(t, 0, n)
|
||||||
<-ch
|
<-ch
|
||||||
|
|
||||||
|
@ -40,7 +41,7 @@ func TestBufferOrdering(t *testing.T) {
|
||||||
b.Push(eventWithModRev(4))
|
b.Push(eventWithModRev(4))
|
||||||
|
|
||||||
// Full range - but only the first should be returned since there is a gap
|
// Full range - but only the first should be returned since there is a gap
|
||||||
buf, n, _ := b.Range(b.StartRange(0), defaultKeyRange)
|
buf, n, _ := b.Range(startRange(b, 0), defaultKeyRange)
|
||||||
assert.Equal(t, 1, n)
|
assert.Equal(t, 1, n)
|
||||||
assert.Equal(t, []int64{2}, testutil.EventModRevs(buf))
|
assert.Equal(t, []int64{2}, testutil.EventModRevs(buf))
|
||||||
|
|
||||||
|
@ -48,12 +49,12 @@ func TestBufferOrdering(t *testing.T) {
|
||||||
b.Push(eventWithModRev(3))
|
b.Push(eventWithModRev(3))
|
||||||
|
|
||||||
// Full range
|
// Full range
|
||||||
buf, n, _ = b.Range(b.StartRange(0), defaultKeyRange)
|
buf, n, _ = b.Range(startRange(b, 0), defaultKeyRange)
|
||||||
assert.Equal(t, 3, n)
|
assert.Equal(t, 3, n)
|
||||||
assert.Equal(t, []int64{2, 3, 4}, testutil.EventModRevs(buf))
|
assert.Equal(t, []int64{2, 3, 4}, testutil.EventModRevs(buf))
|
||||||
|
|
||||||
// Partial range
|
// Partial range
|
||||||
buf, n, _ = b.Range(b.StartRange(2), defaultKeyRange)
|
buf, n, _ = b.Range(startRange(b, 2), defaultKeyRange)
|
||||||
assert.Equal(t, 2, n)
|
assert.Equal(t, 2, n)
|
||||||
assert.Equal(t, []int64{3, 4}, testutil.EventModRevs(buf))
|
assert.Equal(t, []int64{3, 4}, testutil.EventModRevs(buf))
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ func TestBufferOrdering(t *testing.T) {
|
||||||
|
|
||||||
// This gap is never filled - wait for the timeout
|
// This gap is never filled - wait for the timeout
|
||||||
for {
|
for {
|
||||||
buf, n, _ = b.Range(b.StartRange(0), defaultKeyRange)
|
buf, n, _ = b.Range(startRange(b, 0), defaultKeyRange)
|
||||||
if n == 4 {
|
if n == 4 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -72,10 +73,13 @@ func TestBufferOrdering(t *testing.T) {
|
||||||
|
|
||||||
// Push another event, which will cause the earliest event to fall off
|
// Push another event, which will cause the earliest event to fall off
|
||||||
b.Push(eventWithModRev(7))
|
b.Push(eventWithModRev(7))
|
||||||
buf, n, _ = b.Range(b.StartRange(0), defaultKeyRange)
|
buf, n, _ = b.Range(startRange(b, 2), defaultKeyRange)
|
||||||
assert.Equal(t, 4, n)
|
assert.Equal(t, 4, n)
|
||||||
assert.Equal(t, []int64{3, 4, 6, 7}, testutil.EventModRevs(buf))
|
assert.Equal(t, []int64{3, 4, 6, 7}, testutil.EventModRevs(buf))
|
||||||
|
|
||||||
|
// Prove we can't start a range at a revision that has been trimmed
|
||||||
|
assert.Nil(t, startRange(b, 0))
|
||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
<-done
|
<-done
|
||||||
}
|
}
|
||||||
|
@ -100,7 +104,7 @@ func TestBufferKeyFiltering(t *testing.T) {
|
||||||
Key: []byte("foo/4"),
|
Key: []byte("foo/4"),
|
||||||
}}})
|
}}})
|
||||||
|
|
||||||
slice, _, _ := b.Range(b.StartRange(0), keyRange("bar", "bar0"))
|
slice, _, _ := b.Range(startRange(b, 0), keyRange("bar", "bar0"))
|
||||||
require.Len(t, slice, 2)
|
require.Len(t, slice, 2)
|
||||||
assert.Equal(t, []int64{2, 3}, testutil.EventModRevs(slice))
|
assert.Equal(t, []int64{2, 3}, testutil.EventModRevs(slice))
|
||||||
}
|
}
|
||||||
|
@ -128,3 +132,8 @@ func keyRange(from, to string) adt.IntervalTree {
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultKeyRange = keyRange("foo", "foo0")
|
var defaultKeyRange = keyRange("foo", "foo0")
|
||||||
|
|
||||||
|
func startRange(b *buffer, rev int64) *list.Element {
|
||||||
|
el, _ := b.StartRange(rev)
|
||||||
|
return el
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package watch
|
package watch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"container/list"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
@ -67,9 +68,28 @@ func (m *Mux) watchLoop(w clientv3.WatchChan) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mux) Watch(ctx context.Context, key, end []byte, rev int64, ch chan<- *etcdserverpb.WatchResponse) bool {
|
func (m *Mux) Watch(key, end []byte, rev int64) *Watch {
|
||||||
|
pos, ok := m.buffer.StartRange(rev)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Consider one tree per incoming watch connection (like etcd does)
|
||||||
|
tree := adt.NewIntervalTree()
|
||||||
|
tree.Insert(adt.NewStringAffineInterval(string(key), string(end)), nil)
|
||||||
|
|
||||||
|
return &Watch{m: m, pos: pos, tree: tree}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Watch struct {
|
||||||
|
m *Mux
|
||||||
|
pos *list.Element
|
||||||
|
tree adt.IntervalTree
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watch) Run(ctx context.Context, ch chan<- *etcdserverpb.WatchResponse) *Status {
|
||||||
broadcast := make(chan struct{}, 2)
|
broadcast := make(chan struct{}, 2)
|
||||||
close := m.bcast.Watch(broadcast)
|
close := w.m.bcast.Watch(broadcast)
|
||||||
go func() {
|
go func() {
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
close()
|
close()
|
||||||
|
@ -77,20 +97,15 @@ func (m *Mux) Watch(ctx context.Context, key, end []byte, rev int64, ch chan<- *
|
||||||
|
|
||||||
broadcast <- struct{}{}
|
broadcast <- struct{}{}
|
||||||
|
|
||||||
// TODO: Consider one tree per incoming watch connection (like etcd does)
|
|
||||||
tree := adt.NewIntervalTree()
|
|
||||||
tree.Insert(adt.NewStringAffineInterval(string(key), string(end)), nil)
|
|
||||||
|
|
||||||
pos := m.buffer.StartRange(rev)
|
|
||||||
var n int
|
var n int
|
||||||
for range broadcast {
|
for range broadcast {
|
||||||
resp := &etcdserverpb.WatchResponse{Header: &etcdserverpb.ResponseHeader{}}
|
resp := &etcdserverpb.WatchResponse{Header: &etcdserverpb.ResponseHeader{}}
|
||||||
resp.Events, n, pos = m.buffer.Range(pos, tree)
|
resp.Events, n, w.pos = w.m.buffer.Range(w.pos, w.tree)
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
ch <- resp
|
ch <- resp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Status struct {
|
type Status struct {
|
||||||
|
|
Загрузка…
Ссылка в новой задаче