mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-06-10 08:00:19 +08:00
fix(discov): prevent unbounded memory growth on duplicate etcd PUT events (#5580)
This commit is contained in:
@@ -201,6 +201,81 @@ func TestContainer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainer_DuplicateAdd(t *testing.T) {
|
||||
c := newContainer(false)
|
||||
// Simulate 100 duplicate PUT events for the same key+value.
|
||||
for i := 0; i < 100; i++ {
|
||||
c.OnAdd(internal.KV{Key: "etcd-key", Val: "host:1234"})
|
||||
}
|
||||
assert.ElementsMatch(t, []string{"host:1234"}, c.GetValues())
|
||||
// Internal slice must not have grown beyond one entry.
|
||||
c.lock.Lock()
|
||||
assert.Len(t, c.values["host:1234"], 1)
|
||||
c.lock.Unlock()
|
||||
}
|
||||
|
||||
func TestContainer_KeyValueChange(t *testing.T) {
|
||||
c := newContainer(false)
|
||||
c.OnAdd(internal.KV{Key: "etcd-key", Val: "host:1234"})
|
||||
assert.ElementsMatch(t, []string{"host:1234"}, c.GetValues())
|
||||
|
||||
// Key moves to a different server value.
|
||||
c.OnAdd(internal.KV{Key: "etcd-key", Val: "host:5678"})
|
||||
assert.ElementsMatch(t, []string{"host:5678"}, c.GetValues())
|
||||
|
||||
// Old server must be fully removed; a subsequent delete must leave nothing.
|
||||
c.OnDelete(internal.KV{Key: "etcd-key", Val: "host:5678"})
|
||||
assert.Empty(t, c.GetValues())
|
||||
}
|
||||
|
||||
// TestContainer_ExclusiveMode verifies that adding successive keys for the same
|
||||
// value in exclusive mode leaves only the latest key and evicts all prior ones.
|
||||
func TestContainer_ExclusiveMode(t *testing.T) {
|
||||
c := newContainer(true)
|
||||
c.OnAdd(internal.KV{Key: "key1", Val: "server1"})
|
||||
c.OnAdd(internal.KV{Key: "key2", Val: "server1"})
|
||||
c.OnAdd(internal.KV{Key: "key3", Val: "server1"})
|
||||
|
||||
assert.ElementsMatch(t, []string{"server1"}, c.GetValues())
|
||||
c.lock.Lock()
|
||||
assert.Equal(t, []string{"key3"}, c.values["server1"], "only the latest key must remain")
|
||||
assert.NotContains(t, c.mapping, "key1", "key1 must have been evicted")
|
||||
assert.NotContains(t, c.mapping, "key2", "key2 must have been evicted")
|
||||
assert.Equal(t, "server1", c.mapping["key3"])
|
||||
c.lock.Unlock()
|
||||
}
|
||||
|
||||
// TestContainer_ExclusiveMode_MultipleEvictions injects 3 keys for the same
|
||||
// value directly into internal state and then triggers the exclusive eviction
|
||||
// loop via OnAdd. This exercises the range-over-previous fix: iterating over
|
||||
// the live slice (range keys) would corrupt iteration when doRemoveKey
|
||||
// compacts the shared underlying array in-place, causing the second and third
|
||||
// keys to be skipped; ranging over the deep copy (range previous) is safe.
|
||||
func TestContainer_ExclusiveMode_MultipleEvictions(t *testing.T) {
|
||||
c := newContainer(true)
|
||||
|
||||
// Bypass the exclusive invariant to simulate 3 pre-existing keys for the
|
||||
// same value — the state that would expose the in-place aliasing bug.
|
||||
c.lock.Lock()
|
||||
c.values["server1"] = []string{"key1", "key2", "key3"}
|
||||
c.mapping["key1"] = "server1"
|
||||
c.mapping["key2"] = "server1"
|
||||
c.mapping["key3"] = "server1"
|
||||
c.lock.Unlock()
|
||||
|
||||
// Adding key4 must evict all three existing keys via the exclusive loop.
|
||||
c.OnAdd(internal.KV{Key: "key4", Val: "server1"})
|
||||
|
||||
assert.ElementsMatch(t, []string{"server1"}, c.GetValues())
|
||||
c.lock.Lock()
|
||||
assert.Equal(t, []string{"key4"}, c.values["server1"], "all prior keys must be evicted")
|
||||
assert.NotContains(t, c.mapping, "key1", "key1 must be evicted")
|
||||
assert.NotContains(t, c.mapping, "key2", "key2 must be evicted")
|
||||
assert.NotContains(t, c.mapping, "key3", "key3 must be evicted")
|
||||
assert.Equal(t, "server1", c.mapping["key4"])
|
||||
c.lock.Unlock()
|
||||
}
|
||||
|
||||
func TestSubscriber(t *testing.T) {
|
||||
sub := new(Subscriber)
|
||||
Exclusive()(sub)
|
||||
|
||||
Reference in New Issue
Block a user