Skip to content

Commit 1703656

Browse files
authored
xds: generic xDS client transport channel and ads stream implementation (#8144)
1 parent c27e6dc commit 1703656

File tree

23 files changed

+4183
-0
lines changed

23 files changed

+4183
-0
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
*
3+
* Copyright 2017 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
// Package backoff implements the backoff strategy for clients.
20+
//
21+
// This is kept in internal until the clients project decides whether or not to
22+
// allow alternative backoff strategies.
23+
package backoff
24+
25+
import (
26+
"context"
27+
"errors"
28+
rand "math/rand/v2"
29+
"time"
30+
)
31+
32+
// config defines the configuration options for backoff.
33+
type config struct {
34+
// baseDelay is the amount of time to backoff after the first failure.
35+
baseDelay time.Duration
36+
// multiplier is the factor with which to multiply backoffs after a
37+
// failed retry. Should ideally be greater than 1.
38+
multiplier float64
39+
// jitter is the factor with which backoffs are randomized.
40+
jitter float64
41+
// maxDelay is the upper bound of backoff delay.
42+
maxDelay time.Duration
43+
}
44+
45+
// defaultConfig is a backoff configuration with the default values specified
46+
// at https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
47+
//
48+
// This should be useful for callers who want to configure backoff with
49+
// non-default values only for a subset of the options.
50+
var defaultConfig = config{
51+
baseDelay: 1.0 * time.Second,
52+
multiplier: 1.6,
53+
jitter: 0.2,
54+
maxDelay: 120 * time.Second,
55+
}
56+
57+
// DefaultExponential is an exponential backoff implementation using the
58+
// default values for all the configurable knobs defined in
59+
// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
60+
var DefaultExponential = exponential{config: defaultConfig}
61+
62+
// exponential implements exponential backoff algorithm as defined in
63+
// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
64+
type exponential struct {
65+
// Config contains all options to configure the backoff algorithm.
66+
config config
67+
}
68+
69+
// Backoff returns the amount of time to wait before the next retry given the
70+
// number of retries.
71+
func (bc exponential) Backoff(retries int) time.Duration {
72+
if retries == 0 {
73+
return bc.config.baseDelay
74+
}
75+
backoff, max := float64(bc.config.baseDelay), float64(bc.config.maxDelay)
76+
for backoff < max && retries > 0 {
77+
backoff *= bc.config.multiplier
78+
retries--
79+
}
80+
if backoff > max {
81+
backoff = max
82+
}
83+
// Randomize backoff delays so that if a cluster of requests start at
84+
// the same time, they won't operate in lockstep.
85+
backoff *= 1 + bc.config.jitter*(rand.Float64()*2-1)
86+
if backoff < 0 {
87+
return 0
88+
}
89+
return time.Duration(backoff)
90+
}
91+
92+
// ErrResetBackoff is the error to be returned by the function executed by RunF,
93+
// to instruct the latter to reset its backoff state.
94+
var ErrResetBackoff = errors.New("reset backoff state")
95+
96+
// RunF provides a convenient way to run a function f repeatedly until the
97+
// context expires or f returns a non-nil error that is not ErrResetBackoff.
98+
// When f returns ErrResetBackoff, RunF continues to run f, but resets its
99+
// backoff state before doing so. backoff accepts an integer representing the
100+
// number of retries, and returns the amount of time to backoff.
101+
func RunF(ctx context.Context, f func() error, backoff func(int) time.Duration) {
102+
attempt := 0
103+
timer := time.NewTimer(0)
104+
for ctx.Err() == nil {
105+
select {
106+
case <-timer.C:
107+
case <-ctx.Done():
108+
timer.Stop()
109+
return
110+
}
111+
112+
err := f()
113+
if errors.Is(err, ErrResetBackoff) {
114+
timer.Reset(0)
115+
attempt = 0
116+
continue
117+
}
118+
if err != nil {
119+
return
120+
}
121+
timer.Reset(backoff(attempt))
122+
attempt++
123+
}
124+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright 2019 gRPC authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
// Package buffer provides an implementation of an unbounded buffer.
19+
package buffer
20+
21+
import (
22+
"errors"
23+
"sync"
24+
)
25+
26+
// Unbounded is an implementation of an unbounded buffer which does not use
27+
// extra goroutines. This is typically used for passing updates from one entity
28+
// to another within gRPC.
29+
//
30+
// All methods on this type are thread-safe and don't block on anything except
31+
// the underlying mutex used for synchronization.
32+
//
33+
// Unbounded supports values of any type to be stored in it by using a channel
34+
// of `any`. This means that a call to Put() incurs an extra memory allocation,
35+
// and also that users need a type assertion while reading. For performance
36+
// critical code paths, using Unbounded is strongly discouraged and defining a
37+
// new type specific implementation of this buffer is preferred. See
38+
// internal/transport/transport.go for an example of this.
39+
type Unbounded struct {
40+
c chan any
41+
closed bool
42+
closing bool
43+
mu sync.Mutex
44+
backlog []any
45+
}
46+
47+
// NewUnbounded returns a new instance of Unbounded.
48+
func NewUnbounded() *Unbounded {
49+
return &Unbounded{c: make(chan any, 1)}
50+
}
51+
52+
var errBufferClosed = errors.New("Put() called on closed buffer.Unbounded")
53+
54+
// Put adds t to the unbounded buffer.
55+
func (b *Unbounded) Put(t any) error {
56+
b.mu.Lock()
57+
defer b.mu.Unlock()
58+
if b.closing {
59+
return errBufferClosed
60+
}
61+
if len(b.backlog) == 0 {
62+
select {
63+
case b.c <- t:
64+
return nil
65+
default:
66+
}
67+
}
68+
b.backlog = append(b.backlog, t)
69+
return nil
70+
}
71+
72+
// Load sends the earliest buffered data, if any, onto the read channel returned
73+
// by Get(). Users are expected to call this every time they successfully read a
74+
// value from the read channel.
75+
func (b *Unbounded) Load() {
76+
b.mu.Lock()
77+
defer b.mu.Unlock()
78+
if len(b.backlog) > 0 {
79+
select {
80+
case b.c <- b.backlog[0]:
81+
b.backlog[0] = nil
82+
b.backlog = b.backlog[1:]
83+
default:
84+
}
85+
} else if b.closing && !b.closed {
86+
close(b.c)
87+
}
88+
}
89+
90+
// Get returns a read channel on which values added to the buffer, via Put(),
91+
// are sent on.
92+
//
93+
// Upon reading a value from this channel, users are expected to call Load() to
94+
// send the next buffered value onto the channel if there is any.
95+
//
96+
// If the unbounded buffer is closed, the read channel returned by this method
97+
// is closed after all data is drained.
98+
func (b *Unbounded) Get() <-chan any {
99+
return b.c
100+
}
101+
102+
// Close closes the unbounded buffer. No subsequent data may be Put(), and the
103+
// channel returned from Get() will be closed after all the data is read and
104+
// Load() is called for the final time.
105+
func (b *Unbounded) Close() {
106+
b.mu.Lock()
107+
defer b.mu.Unlock()
108+
if b.closing {
109+
return
110+
}
111+
b.closing = true
112+
if len(b.backlog) == 0 {
113+
b.closed = true
114+
close(b.c)
115+
}
116+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright 2019 gRPC authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
package buffer
19+
20+
import (
21+
"sort"
22+
"sync"
23+
"testing"
24+
25+
"github.com/google/go-cmp/cmp"
26+
"google.golang.org/grpc/internal/grpctest"
27+
)
28+
29+
const (
30+
numWriters = 10
31+
numWrites = 10
32+
)
33+
34+
type s struct {
35+
grpctest.Tester
36+
}
37+
38+
func Test(t *testing.T) {
39+
grpctest.RunSubTests(t, s{})
40+
}
41+
42+
// wantReads contains the set of values expected to be read by the reader
43+
// goroutine in the tests.
44+
var wantReads []int
45+
46+
func init() {
47+
for i := 0; i < numWriters; i++ {
48+
for j := 0; j < numWrites; j++ {
49+
wantReads = append(wantReads, i)
50+
}
51+
}
52+
}
53+
54+
// TestSingleWriter starts one reader and one writer goroutine and makes sure
55+
// that the reader gets all the values added to the buffer by the writer.
56+
func (s) TestSingleWriter(t *testing.T) {
57+
ub := NewUnbounded()
58+
reads := []int{}
59+
60+
var wg sync.WaitGroup
61+
wg.Add(1)
62+
go func() {
63+
defer wg.Done()
64+
ch := ub.Get()
65+
for i := 0; i < numWriters*numWrites; i++ {
66+
r := <-ch
67+
reads = append(reads, r.(int))
68+
ub.Load()
69+
}
70+
}()
71+
72+
wg.Add(1)
73+
go func() {
74+
defer wg.Done()
75+
for i := 0; i < numWriters; i++ {
76+
for j := 0; j < numWrites; j++ {
77+
ub.Put(i)
78+
}
79+
}
80+
}()
81+
82+
wg.Wait()
83+
if !cmp.Equal(reads, wantReads) {
84+
t.Errorf("reads: %#v, wantReads: %#v", reads, wantReads)
85+
}
86+
}
87+
88+
// TestMultipleWriters starts multiple writers and one reader goroutine and
89+
// makes sure that the reader gets all the data written by all writers.
90+
func (s) TestMultipleWriters(t *testing.T) {
91+
ub := NewUnbounded()
92+
reads := []int{}
93+
94+
var wg sync.WaitGroup
95+
wg.Add(1)
96+
go func() {
97+
defer wg.Done()
98+
ch := ub.Get()
99+
for i := 0; i < numWriters*numWrites; i++ {
100+
r := <-ch
101+
reads = append(reads, r.(int))
102+
ub.Load()
103+
}
104+
}()
105+
106+
wg.Add(numWriters)
107+
for i := 0; i < numWriters; i++ {
108+
go func(index int) {
109+
defer wg.Done()
110+
for j := 0; j < numWrites; j++ {
111+
ub.Put(index)
112+
}
113+
}(i)
114+
}
115+
116+
wg.Wait()
117+
sort.Ints(reads)
118+
if !cmp.Equal(reads, wantReads) {
119+
t.Errorf("reads: %#v, wantReads: %#v", reads, wantReads)
120+
}
121+
}
122+
123+
// TestClose closes the buffer and makes sure that nothing is sent after the
124+
// buffer is closed.
125+
func (s) TestClose(t *testing.T) {
126+
ub := NewUnbounded()
127+
if err := ub.Put(1); err != nil {
128+
t.Fatalf("Unbounded.Put() = %v; want nil", err)
129+
}
130+
ub.Close()
131+
if err := ub.Put(1); err == nil {
132+
t.Fatalf("Unbounded.Put() = <nil>; want non-nil error")
133+
}
134+
if v, ok := <-ub.Get(); !ok {
135+
t.Errorf("Unbounded.Get() = %v, %v, want %v, %v", v, ok, 1, true)
136+
}
137+
if err := ub.Put(1); err == nil {
138+
t.Fatalf("Unbounded.Put() = <nil>; want non-nil error")
139+
}
140+
ub.Load()
141+
if v, ok := <-ub.Get(); ok {
142+
t.Errorf("Unbounded.Get() = %v, want closed channel", v)
143+
}
144+
if err := ub.Put(1); err == nil {
145+
t.Fatalf("Unbounded.Put() = <nil>; want non-nil error")
146+
}
147+
ub.Close() // ignored
148+
}

0 commit comments

Comments
 (0)