Merge pull request #177 from djdongjin/metadata-clone
Add MD.Clone function
This commit is contained in:
commit
3b8c8b7557
28
metadata.go
28
metadata.go
@ -62,6 +62,34 @@ func (m MD) Append(key string, values ...string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clone returns a copy of MD or nil if it's nil.
|
||||||
|
// It's copied from golang's `http.Header.Clone` implementation:
|
||||||
|
// https://cs.opensource.google/go/go/+/refs/tags/go1.23.4:src/net/http/header.go;l=94
|
||||||
|
func (m MD) Clone() MD {
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find total number of values.
|
||||||
|
nv := 0
|
||||||
|
for _, vv := range m {
|
||||||
|
nv += len(vv)
|
||||||
|
}
|
||||||
|
sv := make([]string, nv) // shared backing array for headers' values
|
||||||
|
m2 := make(MD, len(m))
|
||||||
|
for k, vv := range m {
|
||||||
|
if vv == nil {
|
||||||
|
// Preserve nil values.
|
||||||
|
m2[k] = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n := copy(sv, vv)
|
||||||
|
m2[k] = sv[:n:n]
|
||||||
|
sv = sv[n:]
|
||||||
|
}
|
||||||
|
return m2
|
||||||
|
}
|
||||||
|
|
||||||
func (m MD) setRequest(r *Request) {
|
func (m MD) setRequest(r *Request) {
|
||||||
for k, values := range m {
|
for k, values := range m {
|
||||||
for _, v := range values {
|
for _, v := range values {
|
||||||
|
@ -18,6 +18,8 @@ package ttrpc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -106,3 +108,100 @@ func TestMetadataContext(t *testing.T) {
|
|||||||
t.Errorf("invalid metadata value: %q", bar)
|
t.Errorf("invalid metadata value: %q", bar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMetadataClone(t *testing.T) {
|
||||||
|
var metadata MD
|
||||||
|
m2 := metadata.Clone()
|
||||||
|
if m2 != nil {
|
||||||
|
t.Error("MD.Clone() on nil metadata should return nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata = MD{"nil": nil, "foo": {"bar"}, "baz": {"qux", "quxx"}}
|
||||||
|
m2 = metadata.Clone()
|
||||||
|
|
||||||
|
if len(metadata) != len(m2) {
|
||||||
|
t.Errorf("unexpected number of keys: %d, expected: %d", len(m2), len(metadata))
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range metadata {
|
||||||
|
v2, ok := m2[k]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("key not found: %s", k)
|
||||||
|
}
|
||||||
|
if v == nil && v2 == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v == nil || v2 == nil {
|
||||||
|
t.Errorf("unexpected nil value: %v, expected: %v", v2, v)
|
||||||
|
}
|
||||||
|
if len(v) != len(v2) {
|
||||||
|
t.Errorf("unexpected number of values: %d, expected: %d", len(v2), len(v))
|
||||||
|
}
|
||||||
|
for i := range v {
|
||||||
|
if v[i] != v2[i] {
|
||||||
|
t.Errorf("unexpected value: %s, expected: %s", v2[i], v[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMetadataCloneConcurrent(t *testing.T) {
|
||||||
|
metadata := make(MD)
|
||||||
|
metadata.Set("foo", "bar")
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
m2 := metadata.Clone()
|
||||||
|
m2.Set("foo", "baz")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
// Concurrent modification should clone the metadata first to avoid panic
|
||||||
|
// due to concurrent map writes.
|
||||||
|
if val, ok := metadata.Get("foo"); !ok {
|
||||||
|
t.Error("metadata not found")
|
||||||
|
} else if val[0] != "bar" {
|
||||||
|
t.Errorf("invalid metadata value: %q", val[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func simpleClone(src MD) MD {
|
||||||
|
md := MD{}
|
||||||
|
for k, v := range src {
|
||||||
|
md[k] = append(md[k], v...)
|
||||||
|
}
|
||||||
|
return md
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMetadataClone(b *testing.B) {
|
||||||
|
for _, sz := range []int{5, 10, 20, 50} {
|
||||||
|
b.Run(fmt.Sprintf("size=%d", sz), func(b *testing.B) {
|
||||||
|
metadata := make(MD)
|
||||||
|
for i := 0; i < sz; i++ {
|
||||||
|
metadata.Set("foo"+fmt.Sprint(i), "bar"+fmt.Sprint(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = metadata.Clone()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSimpleMetadataClone(b *testing.B) {
|
||||||
|
for _, sz := range []int{5, 10, 20, 50} {
|
||||||
|
b.Run(fmt.Sprintf("size=%d", sz), func(b *testing.B) {
|
||||||
|
metadata := make(MD)
|
||||||
|
for i := 0; i < sz; i++ {
|
||||||
|
metadata.Set("foo"+fmt.Sprint(i), "bar"+fmt.Sprint(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = simpleClone(metadata)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user