Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 35 additions & 17 deletions metadata/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,14 @@ func AppendToOutgoingContext(ctx context.Context, kv ...string) context.Context
panic(fmt.Sprintf("metadata: AppendToOutgoingContext got an odd number of input pairs for metadata: %d", len(kv)))
}
md, _ := ctx.Value(mdOutgoingKey{}).(rawMD)
added := make([][]string, len(md.added)+1)
copy(added, md.added)
kvCopy := make([]string, 0, len(kv))
for i := 0; i < len(kv); i += 2 {
kvCopy = append(kvCopy, strings.ToLower(kv[i]), kv[i+1])
}
added[len(added)-1] = kvCopy
return context.WithValue(ctx, mdOutgoingKey{}, rawMD{md: md.md, added: added})
return context.WithValue(ctx, mdOutgoingKey{}, rawMD{
md: md.md,
added: &deltaKV{kv: kvCopy, prev: md.added},
})
}

// FromIncomingContext returns the incoming metadata in ctx if it exists.
Expand Down Expand Up @@ -250,8 +250,16 @@ func fromOutgoingContextRaw(ctx context.Context) (MD, [][]string, bool) {
if !ok {
return nil, nil, false
}

return raw.md, raw.added, true
// Collect delta nodes in FIFO order (oldest first).
n := 0
for d := raw.added; d != nil; d = d.prev {
n++
}
added := make([][]string, n)
for i, d := n-1, raw.added; d != nil; i, d = i-1, d.prev {
added[i] = d.kv
}
return raw.md, added, true
}

// FromOutgoingContext returns the outgoing metadata in ctx if it exists.
Expand All @@ -264,8 +272,8 @@ func FromOutgoingContext(ctx context.Context) (MD, bool) {
}

mdSize := len(raw.md)
for i := range raw.added {
mdSize += len(raw.added[i]) / 2
for d := raw.added; d != nil; d = d.prev {
mdSize += len(d.kv) / 2
}

out := make(MD, mdSize)
Expand All @@ -276,20 +284,30 @@ func FromOutgoingContext(ctx context.Context) (MD, bool) {
key := strings.ToLower(k)
out[key] = copyOf(v)
}
for _, added := range raw.added {
if len(added)%2 == 1 {
panic(fmt.Sprintf("metadata: FromOutgoingContext got an odd number of input pairs for metadata: %d", len(added)))
}

for i := 0; i < len(added); i += 2 {
key := strings.ToLower(added[i])
out[key] = append(out[key], added[i+1])
// Merge appended kv pairs in FIFO order. Collect nodes newest-first,
// then replay oldest-first so later appends override earlier ones.
var nodes []*deltaKV
for d := raw.added; d != nil; d = d.prev {
nodes = append(nodes, d)
}
for i := len(nodes) - 1; i >= 0; i-- {
kv := nodes[i].kv
for j := 0; j < len(kv); j += 2 {
out[kv[j]] = append(out[kv[j]], kv[j+1])
}
}
return out, ok
}

// deltaKV is a node in a singly-linked list of key-value slices appended
// via AppendToOutgoingContext. The list is newest-first: each node's prev
// field points to the older delta. Keys in kv are already lowercased.
type deltaKV struct {
kv []string
prev *deltaKV
}

type rawMD struct {
md MD
added [][]string
added *deltaKV
}
41 changes: 41 additions & 0 deletions metadata/metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,28 @@ func BenchmarkAppendToOutgoingContext(b *testing.B) {
}
}

// BenchmarkAppendToOutgoingContextN measures the cost of N sequential
// AppendToOutgoingContext calls on a fresh context each iteration.
// With the old [][]string implementation each call was O(len(added)),
// making N calls O(N²) total. The linked-list implementation is O(1)
// per call and O(N) total.
func BenchmarkAppendToOutgoingContextN(b *testing.B) {
for _, n := range []int{1, 5, 10, 50} {
b.Run(strconv.Itoa(n), func(b *testing.B) {
b.ReportAllocs()
tCtx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
for iter := 0; iter < b.N; iter++ {
ctx := tCtx
for i := 0; i < n; i++ {
ctx = AppendToOutgoingContext(ctx, "k1", "v1", "k2", "v2")
}
_ = ctx
}
})
}
}

func BenchmarkFromOutgoingContext(b *testing.B) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
Expand All @@ -379,6 +401,25 @@ func BenchmarkFromOutgoingContext(b *testing.B) {
}
}

// BenchmarkFromOutgoingContextN measures the read path after N appends.
func BenchmarkFromOutgoingContextN(b *testing.B) {
for _, n := range []int{1, 5, 10, 50} {
b.Run(strconv.Itoa(n), func(b *testing.B) {
b.ReportAllocs()
tCtx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
ctx := NewOutgoingContext(tCtx, MD{"k-base": {"v-base"}})
for i := 0; i < n; i++ {
ctx = AppendToOutgoingContext(ctx, "k"+strconv.Itoa(i), "v"+strconv.Itoa(i))
}
b.ResetTimer()
for iter := 0; iter < b.N; iter++ {
FromOutgoingContext(ctx)
}
})
}
}

func BenchmarkFromIncomingContext(b *testing.B) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
Expand Down
Loading