Skip to content

Commit c6760cf

Browse files
authored
Add the name resolver API (#2285)
1 parent 78880bd commit c6760cf

File tree

13 files changed

+2035
-77
lines changed

13 files changed

+2035
-77
lines changed

grpc/Cargo.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@ license = "Apache-2.0"
77

88
[dependencies]
99
url = "2.5.0"
10-
tokio = { version = "1.37.0", features = ["sync"] }
10+
tokio = { version = "1.37.0", features = ["sync", "rt", "net", "time", "macros"] }
1111
tonic = { version = "0.13.0", path = "../tonic", default-features = false, features = ["codegen"] }
1212
futures-core = "0.3.31"
13+
hickory-resolver = { version = "0.25.1", optional = true }
14+
rand = "0.8.5"
15+
parking_lot = "0.12.4"
16+
bytes = "1.10.1"
17+
18+
[dev-dependencies]
19+
hickory-server = "0.25.2"
20+
21+
[features]
22+
default = ["dns"]
23+
dns = ["dep:hickory-resolver"]

grpc/src/byte_str.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
*
3+
* Copyright 2025 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+
use core::str;
20+
use std::ops::Deref;
21+
22+
use bytes::Bytes;
23+
24+
/// A cheaply cloneable and sliceable chunk of contiguous memory.
25+
#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
26+
pub struct ByteStr {
27+
// Invariant: bytes contains valid UTF-8
28+
bytes: Bytes,
29+
}
30+
31+
impl Deref for ByteStr {
32+
type Target = str;
33+
34+
#[inline]
35+
fn deref(&self) -> &str {
36+
let b: &[u8] = self.bytes.as_ref();
37+
// The invariant of `bytes` is that it contains valid UTF-8 allows us
38+
// to unwrap.
39+
str::from_utf8(b).unwrap()
40+
}
41+
}
42+
43+
impl From<String> for ByteStr {
44+
#[inline]
45+
fn from(src: String) -> ByteStr {
46+
ByteStr {
47+
// Invariant: src is a String so contains valid UTF-8.
48+
bytes: Bytes::from(src),
49+
}
50+
}
51+
}

grpc/src/client/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
use std::fmt::Display;
2020

2121
pub mod channel;
22-
pub mod service;
2322
pub(crate) mod load_balancing;
2423
pub(crate) mod name_resolution;
24+
pub mod service;
2525
pub mod service_config;
2626

2727
/// A representation of the current state of a gRPC channel, also used for the
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/*
2+
*
3+
* Copyright 2025 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+
use rand::Rng;
20+
use std::time::Duration;
21+
22+
#[derive(Clone)]
23+
pub struct BackoffConfig {
24+
/// The amount of time to backoff after the first failure.
25+
pub base_delay: Duration,
26+
27+
/// The factor with which to multiply backoffs after a
28+
/// failed retry. Should ideally be greater than 1.
29+
pub multiplier: f64,
30+
31+
/// The factor with which backoffs are randomized.
32+
pub jitter: f64,
33+
34+
/// The upper bound of backoff delay.
35+
pub max_delay: Duration,
36+
}
37+
38+
pub struct ExponentialBackoff {
39+
config: BackoffConfig,
40+
41+
/// The delay for the next retry, without the random jitter. Store as f64
42+
/// to avoid rounding errors.
43+
next_delay_secs: f64,
44+
}
45+
46+
/// This is a backoff configuration with the default values specified
47+
/// at https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
48+
///
49+
/// This should be useful for callers who want to configure backoff with
50+
/// non-default values only for a subset of the options.
51+
pub const DEFAULT_EXPONENTIAL_CONFIG: BackoffConfig = BackoffConfig {
52+
base_delay: Duration::from_secs(1),
53+
multiplier: 1.6,
54+
jitter: 0.2,
55+
max_delay: Duration::from_secs(120),
56+
};
57+
58+
impl BackoffConfig {
59+
fn validate(&self) -> Result<(), &'static str> {
60+
// Check that the arguments are in valid ranges.
61+
// 0 <= base_dealy <= max_delay
62+
if self.base_delay > self.max_delay {
63+
Err("base_delay must be greater than max_delay")?;
64+
}
65+
// 1 <= multiplier
66+
if self.multiplier < 1.0 {
67+
Err("multiplier must be greater than 1.0")?;
68+
}
69+
// 0 <= jitter <= 1
70+
if self.jitter < 0.0 {
71+
Err("jitter must be greater than or equal to 0")?;
72+
}
73+
if self.jitter > 1.0 {
74+
Err("jitter must be less than or equal to 1")?
75+
}
76+
Ok(())
77+
}
78+
}
79+
80+
impl ExponentialBackoff {
81+
pub fn new(config: BackoffConfig) -> Result<Self, &'static str> {
82+
config.validate()?;
83+
let next_delay_secs = config.base_delay.as_secs_f64();
84+
Ok(ExponentialBackoff {
85+
config,
86+
next_delay_secs,
87+
})
88+
}
89+
90+
pub fn reset(&mut self) {
91+
self.next_delay_secs = self.config.base_delay.as_secs_f64();
92+
}
93+
94+
pub fn backoff_duration(&mut self) -> Duration {
95+
let next_delay = self.next_delay_secs;
96+
let cur_delay =
97+
next_delay * (1.0 + self.config.jitter * rand::thread_rng().gen_range(-1.0..1.0));
98+
self.next_delay_secs = self
99+
.config
100+
.max_delay
101+
.as_secs_f64()
102+
.min(next_delay * self.config.multiplier);
103+
Duration::from_secs_f64(cur_delay)
104+
}
105+
}
106+
107+
#[cfg(test)]
108+
mod tests {
109+
use std::time::Duration;
110+
111+
use crate::client::name_resolution::backoff::{
112+
BackoffConfig, ExponentialBackoff, DEFAULT_EXPONENTIAL_CONFIG,
113+
};
114+
115+
// Epsilon for floating point comparisons if needed, though Duration
116+
// comparisons are often better.
117+
const EPSILON: f64 = 1e-9;
118+
119+
#[test]
120+
fn default_config_is_valid() {
121+
let result = ExponentialBackoff::new(DEFAULT_EXPONENTIAL_CONFIG.clone());
122+
assert_eq!(result.is_ok(), true);
123+
}
124+
125+
#[test]
126+
fn base_less_than_max() {
127+
let config = BackoffConfig {
128+
base_delay: Duration::from_secs(10),
129+
multiplier: 123.0,
130+
jitter: 0.0,
131+
max_delay: Duration::from_secs(100),
132+
};
133+
let mut backoff = ExponentialBackoff::new(config).unwrap();
134+
assert_eq!(backoff.backoff_duration(), Duration::from_secs(10));
135+
}
136+
137+
#[test]
138+
fn base_more_than_max() {
139+
let config = BackoffConfig {
140+
multiplier: 123.0,
141+
jitter: 0.0,
142+
base_delay: Duration::from_secs(100),
143+
max_delay: Duration::from_secs(10),
144+
};
145+
let result = ExponentialBackoff::new(config);
146+
assert_eq!(result.is_err(), true);
147+
}
148+
149+
#[test]
150+
fn negative_multiplier() {
151+
let config = BackoffConfig {
152+
multiplier: -123.0,
153+
jitter: 0.0,
154+
base_delay: Duration::from_secs(10),
155+
max_delay: Duration::from_secs(100),
156+
};
157+
let result = ExponentialBackoff::new(config);
158+
assert_eq!(result.is_err(), true);
159+
}
160+
161+
#[test]
162+
fn negative_jitter() {
163+
let config = BackoffConfig {
164+
multiplier: 1.0,
165+
jitter: -10.0,
166+
base_delay: Duration::from_secs(10),
167+
max_delay: Duration::from_secs(100),
168+
};
169+
let result = ExponentialBackoff::new(config);
170+
assert_eq!(result.is_err(), true);
171+
}
172+
173+
#[test]
174+
fn jitter_greater_than_one() {
175+
let config = BackoffConfig {
176+
multiplier: 1.0,
177+
jitter: 2.0,
178+
base_delay: Duration::from_secs(10),
179+
max_delay: Duration::from_secs(100),
180+
};
181+
let result = ExponentialBackoff::new(config);
182+
assert_eq!(result.is_err(), true);
183+
}
184+
185+
#[test]
186+
fn backoff_reset_no_jitter() {
187+
let config = BackoffConfig {
188+
multiplier: 2.0,
189+
jitter: 0.0,
190+
base_delay: Duration::from_secs(1),
191+
max_delay: Duration::from_secs(15),
192+
};
193+
let mut backoff = ExponentialBackoff::new(config.clone()).unwrap();
194+
assert_eq!(backoff.backoff_duration(), Duration::from_secs(1));
195+
assert_eq!(backoff.backoff_duration(), Duration::from_secs(2));
196+
assert_eq!(backoff.backoff_duration(), Duration::from_secs(4));
197+
assert_eq!(backoff.backoff_duration(), Duration::from_secs(8));
198+
assert_eq!(backoff.backoff_duration(), Duration::from_secs(15));
199+
// Duration is capped to max_delay.
200+
assert_eq!(backoff.backoff_duration(), Duration::from_secs(15));
201+
202+
// reset and repeat.
203+
backoff.reset();
204+
assert_eq!(backoff.backoff_duration(), Duration::from_secs(1));
205+
assert_eq!(backoff.backoff_duration(), Duration::from_secs(2));
206+
assert_eq!(backoff.backoff_duration(), Duration::from_secs(4));
207+
assert_eq!(backoff.backoff_duration(), Duration::from_secs(8));
208+
assert_eq!(backoff.backoff_duration(), Duration::from_secs(15));
209+
// Duration is capped to max_delay.
210+
assert_eq!(backoff.backoff_duration(), Duration::from_secs(15));
211+
}
212+
213+
#[test]
214+
fn backoff_with_jitter() {
215+
let config = BackoffConfig {
216+
multiplier: 2.0,
217+
jitter: 0.2,
218+
base_delay: Duration::from_secs(1),
219+
max_delay: Duration::from_secs(15),
220+
};
221+
let mut backoff = ExponentialBackoff::new(config.clone()).unwrap();
222+
// 0.8 <= duration <= 1.2.
223+
let duration = backoff.backoff_duration();
224+
assert_eq!(duration.gt(&Duration::from_secs_f64(0.8 - EPSILON)), true);
225+
assert_eq!(duration.lt(&Duration::from_secs_f64(1.2 + EPSILON)), true);
226+
// 1.6 <= duration <= 2.4.
227+
let duration = backoff.backoff_duration();
228+
assert_eq!(duration.gt(&Duration::from_secs_f64(1.6 - EPSILON)), true);
229+
assert_eq!(duration.lt(&Duration::from_secs_f64(2.4 + EPSILON)), true);
230+
// 3.2 <= duration <= 4.8.
231+
let duration = backoff.backoff_duration();
232+
assert_eq!(duration.gt(&Duration::from_secs_f64(3.2 - EPSILON)), true);
233+
assert_eq!(duration.lt(&Duration::from_secs_f64(4.8 + EPSILON)), true);
234+
}
235+
}

0 commit comments

Comments
 (0)