Watch afresh and read from nothing at each shadow run, so a candidate deleted while it was down is not answered and no earlier watch stays open (issue 1126, review of L2b round 2)
This commit is contained in:
@@ -431,6 +431,19 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) {
|
|||||||
k, _ := delivery.PlacementCandidateKey("same", "n1")
|
k, _ := delivery.PlacementCandidateKey("same", "n1")
|
||||||
sb.del(delivery.PlacementCandidatesBucket, k)
|
sb.del(delivery.PlacementCandidatesBucket, k)
|
||||||
waitFor("the deleted group's answer stays", func(a map[string]delivery.CandidateAnswer) bool { _, ok := a["same"]; return !ok && len(a) == 3 })
|
waitFor("the deleted group's answer stays", func(a map[string]delivery.CandidateAnswer) bool { _, ok := a["same"]; return !ok && len(a) == 3 })
|
||||||
|
|
||||||
|
// Review of L2b, round 2: a watch ends while the link stays; a group's key is deleted before the shadow watches
|
||||||
|
// again. The new run reads afresh, so that group is no longer answered.
|
||||||
|
sb.missing(delivery.PlacementCandidatesBucket, true)
|
||||||
|
sb.end(delivery.ModulesBucket)
|
||||||
|
dropsKey, _ := delivery.PlacementCandidateKey("drops", "n1")
|
||||||
|
sb.del(delivery.PlacementCandidatesBucket, dropsKey)
|
||||||
|
time.Sleep(30 * time.Millisecond)
|
||||||
|
sb.missing(delivery.PlacementCandidatesBucket, false)
|
||||||
|
waitFor("a group deleted while the shadow was down is still answered", func(a map[string]delivery.CandidateAnswer) bool {
|
||||||
|
_, ok := a["drops"]
|
||||||
|
return !ok && len(a) == 2
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// patternBus is a bus whose every watch gets the updates of its bucket matching its pattern, as the bus's own watches
|
// patternBus is a bus whose every watch gets the updates of its bucket matching its pattern, as the bus's own watches
|
||||||
@@ -439,6 +452,26 @@ type patternBus struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
watches []patternWatch
|
watches []patternWatch
|
||||||
absent map[string]bool
|
absent map[string]bool
|
||||||
|
// kv is each bucket's keys now; once replay is set, a new watch gets those matching it and then Caught, as a watch
|
||||||
|
// of the bus's own does.
|
||||||
|
kv map[string]map[string]agent.Update
|
||||||
|
replay bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// end ends every watch of a bucket, as a watch the bus drops, and from then on each new watch replays what is there.
|
||||||
|
func (b *patternBus) end(bucket string) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
b.replay = true
|
||||||
|
kept := b.watches[:0]
|
||||||
|
for _, w := range b.watches {
|
||||||
|
if w.bucket == bucket {
|
||||||
|
close(w.ch)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kept = append(kept, w)
|
||||||
|
}
|
||||||
|
b.watches = kept
|
||||||
}
|
}
|
||||||
|
|
||||||
// missing makes a bucket's watch fail, as one the bus holds no state for, or work again.
|
// missing makes a bucket's watch fail, as one the bus holds no state for, or work again.
|
||||||
@@ -466,6 +499,14 @@ func (b *patternBus) watch(_ context.Context, bucket, pattern string) (<-chan ag
|
|||||||
}
|
}
|
||||||
w := patternWatch{bucket: bucket, pattern: pattern, ch: make(chan agent.Update, 64)}
|
w := patternWatch{bucket: bucket, pattern: pattern, ch: make(chan agent.Update, 64)}
|
||||||
b.watches = append(b.watches, w)
|
b.watches = append(b.watches, w)
|
||||||
|
if b.replay {
|
||||||
|
for key, u := range b.kv[bucket] {
|
||||||
|
if matches(pattern, key) {
|
||||||
|
w.ch <- u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.ch <- agent.Update{Caught: true}
|
||||||
|
}
|
||||||
return w.ch, nil
|
return w.ch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,9 +526,25 @@ func matches(pattern, key string) bool {
|
|||||||
func (b *patternBus) send(bucket string, u agent.Update) {
|
func (b *patternBus) send(bucket string, u agent.Update) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
if !u.Caught {
|
||||||
|
if b.kv == nil {
|
||||||
|
b.kv = map[string]map[string]agent.Update{}
|
||||||
|
}
|
||||||
|
if b.kv[bucket] == nil {
|
||||||
|
b.kv[bucket] = map[string]agent.Update{}
|
||||||
|
}
|
||||||
|
if u.Deleted {
|
||||||
|
delete(b.kv[bucket], u.Key)
|
||||||
|
} else {
|
||||||
|
b.kv[bucket][u.Key] = u
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, w := range b.watches {
|
for _, w := range b.watches {
|
||||||
if w.bucket == bucket && (u.Caught || matches(w.pattern, u.Key)) {
|
if w.bucket == bucket && (u.Caught || matches(w.pattern, u.Key)) {
|
||||||
w.ch <- u
|
select {
|
||||||
|
case w.ch <- u:
|
||||||
|
default: // a watch of a run that ended reads no more
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,14 @@ func (s *Shadow) Keep(ctx context.Context, backoff func(try int) time.Duration)
|
|||||||
|
|
||||||
// Run answers until ctx ends or an input's watch ends.
|
// Run answers until ctx ends or an input's watch ends.
|
||||||
func (s *Shadow) Run(ctx context.Context) error {
|
func (s *Shadow) Run(ctx context.Context) error {
|
||||||
|
// Each run watches afresh and starts from nothing read: what changed while it was down (a candidate key deleted)
|
||||||
|
// is then not missed, and the watches of a run before are not left open (review of L2b, round 2).
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
s.mu.Lock()
|
||||||
|
s.candidates = map[string]map[string]candidateKey{}
|
||||||
|
s.c = New(s.c.env)
|
||||||
|
s.mu.Unlock()
|
||||||
c := s.c
|
c := s.c
|
||||||
node := c.env.Node
|
node := c.env.Node
|
||||||
ins := []input{
|
ins := []input{
|
||||||
|
|||||||
Reference in New Issue
Block a user