Policy 与重试状态机
主线位置:主要在请求主线上。
Retry::poll_ready原样转发给内层;重试全部发生在响应 future 里,而且 future 每次重试前要自己去问内层poll_ready。就绪主线在这里被搬进了请求主线。
stateDiagram-v2 [*] --> Called: 调用 call Called --> [*]: 不重试,返回结果 Called --> Waiting: retry 返回 Some Waiting --> Retrying: 等待完成 Retrying --> Retrying: 内层 Pending Retrying --> Called: 就绪后再 call Retrying --> [*]: poll_ready 报错
Retry 自己不知道什么算失败、要等多久、最多重试几次。这些都交给一个 Policy,Retry 只负责一件事:把「发请求 → 看结果 → 等一会 → 再发」这个循环做成一个 future。
Policy:两个方法
Policy(文档注释已删):
pub trait Policy<Req, Res, E> {
type Future: Future<Output = ()>;
fn retry(&mut self, req: &mut Req, result: &mut Result<Res, E>) -> Option<Self::Future>;
fn clone_request(&mut self, req: &Req) -> Option<Req>;
}
retry在每次内层返回结果后被调用(前提是这次留了请求副本,见下一条),成功和失败都会调用,由策略自己判断哪种结果算「失败」。返回None表示不重试,这个结果直接交给调用方;返回Some(fut)表示要重试,Retry先等fut完成再重发。fut的输出是(),它只表示「等多久」:可以是一个Sleep(退避),也可以是立即完成的future::ready(())。req和result都是&mut。改req,下一次重试发出去的就是改过的请求(比如在请求里记一个重试次数);改result,可以把失败改成成功,或者在放弃时换成一个「重试用尽」的错误。clone_request在每次发请求之前被调用,留一份请求的副本给下次重试用。请求的所有权要交给内层的call,发出去就拿不回来,所以只能事先复制。返回None表示这个请求复制不了(比如 body 是一个只能读一次的流),这次发出之后retry根本不会被调用,结果原样返回。
req 和 result 改成 &mut 是 0.5.0 的破坏性改动(#584),PR 给出的两个用途正是上面说的:在请求里标记「这是重试」,以及在重试用尽时改写最终的响应。
policy.rs 末尾有一行只在测试里编译的 _obj_safe,用 Box<dyn Policy<...>> 当参数类型,保证这个 trait 一直是 object safe 的。
retry 返回的 future 以前要「生出」下一个策略0.5.0 之前,Policy 的方法都是 &self,retry 返回的 future 输出是 Self:要重试时,策略得造一个新的自己(比如 Attempts(self.0 - 1)),future 完成后 Retry 用它替换掉旧的。响应 future 的第二个状态也不叫 Waiting,叫 Checking,完成时把新策略写回 policy 字段。
#681(0.5.0)把方法改成 &mut self,future 输出改成 ()。PR 的理由:「The motivation for this change is to simplify the trait a bit. By the trait methods having mutable references it means that for each retry request session you can mutate the local policy. This is because the policy is cloned for each individual request that arrives into the retry middleware. In addition, this allows the Policy trait to be object safe.」旧的 trait 声明是 pub trait Policy<Req, Res, E>: Sized(future 要输出 Self),带着 Sized 就不能做成 dyn;这次改动去掉了 Sized,同时加上了上面那行 _obj_safe 检查。状态名也在这次改成了 Waiting。
Retry::call:先发请求,再复制自己
Retry 只有两个字段,#[derive(Clone)]:
#[derive(Clone, Debug)]
pub struct Retry<P, S> {
policy: P,
service: S,
}
poll_ready 和 call:
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
// NOTE: the Future::poll impl for ResponseFuture assumes that Retry::poll_ready is
// equivalent to Ready.service.poll_ready. If this ever changes, that code must be updated
// as well.
self.service.poll_ready(cx)
}
fn call(&mut self, request: Request) -> Self::Future {
let cloned = self.policy.clone_request(&request);
let future = self.service.call(request);
ResponseFuture::new(cloned, self.clone(), future)
}
call 做三件事,顺序有讲究:
- 先
clone_request,因为下一步request就被移走了。 - 用
self.service发第一次请求。这个服务刚被调用方poll_ready过,第一次发送走的是正常的就绪主线。 self.clone()复制整个Retry,策略和内层服务一起,放进响应 future。之后的重试都用这份副本,不再碰self。
所以 Retry 的 Service 实现要求 S: Clone 和 P: Clone(第 72 行)。Retry 的文档(第 22 行起)说得直白:内层不能 Clone 就在下面套一层 Buffer;内层不能 Clone 时的编译错误见 Clone 不满足。
复制发生在 call 之后。像 ConcurrencyLimit 这样把就绪状态存在自己身上的服务,副本不带许可;这没关系,因为副本在重试前一定会被重新 poll_ready(见下一节)。
代价是每个请求都复制一次,不管最后有没有重试,也不管 clone_request 有没有返回 None。下面的程序让内层每次都失败,clone 时打印一行:
tower = { version = "=0.5.3", features = ["retry"] }
tokio = { version = "=1.54.1", features = ["macros", "rt"] }
use std::{
future::{ready, Ready},
task::{Context, Poll},
};
use tower::{retry::{Policy, Retry}, Service, ServiceExt};
struct Inner;
impl Clone for Inner {
fn clone(&self) -> Self {
println!(" Inner::clone");
Inner
}
}
impl Service<u32> for Inner {
type Response = u32;
type Error = String;
type Future = Ready<Result<u32, String>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), String>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: u32) -> Self::Future {
println!(" Inner::call({req})");
ready(Err(format!("failed {req}")))
}
}
#[derive(Clone)]
struct Once {
can_clone: bool,
used: bool,
}
impl Policy<u32, u32, String> for Once {
type Future = Ready<()>;
fn retry(&mut self, _: &mut u32, _: &mut Result<u32, String>) -> Option<Ready<()>> {
println!(" Policy::retry used={}", self.used);
if self.used {
return None;
}
self.used = true;
Some(ready(()))
}
fn clone_request(&mut self, req: &u32) -> Option<u32> {
println!(" Policy::clone_request -> {}", if self.can_clone { "Some" } else { "None" });
self.can_clone.then_some(*req)
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
for can_clone in [true, false] {
println!("can_clone = {can_clone}");
let mut svc = Retry::new(Once { can_clone, used: false }, Inner);
let svc = svc.ready().await.unwrap();
let fut = svc.call(1);
println!(" -- call returned, awaiting");
println!(" result: {:?}", fut.await);
}
}
M2 Max、rustc 1.99.0、--release,连跑 3 次输出相同:
can_clone = true
Policy::clone_request -> Some
Inner::call(1)
Inner::clone
-- call returned, awaiting
Policy::retry used=false
Policy::clone_request -> Some
Inner::call(1)
Policy::retry used=true
result: Err("failed 1")
can_clone = false
Policy::clone_request -> None
Inner::call(1)
Inner::clone
-- call returned, awaiting
result: Err("failed 1")
- 两轮都是
clone_request → Inner::call → Inner::clone,和源码的顺序一致;can_clone = false时照样复制了一次。 - 重试那次没有新的
Inner::clone:用的是 future 里那份副本。重试前又调了一次clone_request,给可能的下一次重试留副本。 clone_request返回None时,retry一次都没被调用。
内层服务的 clone 越贵(比如一整摞中间件,每层都有 Arc),这笔固定开销越明显。
策略:每个请求一份副本
self.clone() 里也包括策略,所以每个请求(连同它的所有重试,文档叫 request session)用的是自己那份策略。在 retry 里改字段(比如剩余次数减一)只影响这一个请求;要跨请求共享状态,文档建议放进 Arc,下一页的 TpsBudget 就是这么用的。
但第一次 clone_request 是在 self.policy 上调的,发生在 self.clone() 之前。下面的策略在两个方法里各数一个数,对同一个 Retry 连发 3 个请求,每个请求重试一次:
tower = { version = "=0.5.3", features = ["retry"] }
tokio = { version = "=1.54.1", features = ["macros", "rt"] }
use std::{
convert::Infallible,
future::{ready, Ready},
};
use tower::{retry::{Policy, Retry}, service_fn, Service, ServiceExt};
#[derive(Clone)]
struct Counting {
clones: u32, // bumped in clone_request
retries: u32, // bumped in retry
}
impl Policy<u32, u32, Infallible> for Counting {
type Future = Ready<()>;
fn retry(&mut self, req: &mut u32, _: &mut Result<u32, Infallible>) -> Option<Ready<()>> {
self.retries += 1;
println!(" req {req}: retry clones={} retries={}", self.clones, self.retries);
(self.retries < 2).then(|| ready(()))
}
fn clone_request(&mut self, req: &u32) -> Option<u32> {
self.clones += 1;
println!(" req {req}: clone_request clones={}", self.clones);
Some(*req)
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
let inner = service_fn(|x: u32| async move { Ok::<_, Infallible>(x) });
let mut svc = Retry::new(Counting { clones: 0, retries: 0 }, inner);
for req in 1..=3 {
svc.ready().await.unwrap().call(req).await.unwrap();
}
}
(这个策略连成功也重试一次,只是为了让 retry 被调用两次。)
clone_request 时,打印的 clones 是几?retries 每个请求都从 0 开始,这一点上面已经说了。clones 呢?是 1(和 retries 一样每个请求重新开始),3,还是 5(每个请求调两次,前两个请求一共调了 4 次)?完整输出在文末。
ResponseFuture:三个状态
ResponseFuture 和它的 State:
pub struct ResponseFuture<P, S, Request>
where
P: Policy<Request, S::Response, S::Error>,
S: Service<Request>,
{
request: Option<Request>,
#[pin]
retry: Retry<P, S>,
#[pin]
state: State<S::Future, P::Future>,
}
enum State<F, P> {
// Polling the future from [`Service::call`]
Called {
#[pin]
future: F
},
// Polling the future from [`Policy::retry`]
Waiting {
#[pin]
waiting: P
},
// Polling [`Service::poll_ready`] after [`Waiting`] was OK.
Retrying,
}
request是clone_request留下的副本,None表示这个请求不能重试。retry是call里复制出来的那份Retry,重试时用它的service和policy。state里同一时刻只有一个 future:内层的响应 future,或者策略给的等待 future。
poll 是一个 loop,每个状态做完就切到下一个,直到某处返回 Pending 或最终结果:
loop {
match this.state.as_mut().project() {
StateProj::Called { future } => {
let mut result = ready!(future.poll(cx));
if let Some(req) = &mut this.request {
match this.retry.policy.retry(req, &mut result) {
Some(waiting) => {
this.state.set(State::Waiting { waiting });
}
None => return Poll::Ready(result),
}
} else {
// request wasn't cloned, so no way to retry it
return Poll::Ready(result);
}
}
StateProj::Waiting { waiting } => {
ready!(waiting.poll(cx));
this.state.set(State::Retrying);
}
StateProj::Retrying => {
// NOTE: we assume here that
// ...
ready!(this.retry.as_mut().project().service.poll_ready(cx))?;
let req = this
.request
.take()
.expect("retrying requires cloned request");
*this.request = this.retry.policy.clone_request(&req);
this.state.set(State::Called {
future: this.retry.as_mut().project().service.call(req),
});
}
}
}
逐个状态看:
Called:等内层的响应。拿到结果后,有副本就问策略。策略说重试,state.set(Waiting)把旧的响应 future 换掉,它和这次的result一起被丢弃;策略说不重试,结果原样返回。传给retry的req是副本,策略改的就是下次要发的那个。Waiting:等策略给的 future。这里只关心它完成,不关心输出。Retrying:对副本服务调poll_ready。Pending就整个 futurePending,内层就绪时唤醒;报错就用?直接返回这个错误,不问策略;就绪了就取出请求副本,先再复制一份留给下一次,然后call,回到Called。expect("retrying requires cloned request")不会触发:只有request是Some时才会进入Waiting,Retrying是从Waiting来的。
Retrying 前面那段 NOTE 注释(上面省略了)说明这里调的是 service.poll_ready 而不是 Retry::poll_ready,理由是「to avoid adding an Unpin bound to the Policy」。这样做的前提是 Retry::poll_ready 一直只是原样转发,所以 mod.rs 的 poll_ready 里有一段对应的 NOTE,提醒以后改那边时要连这里一起改。
下面用 tower-test 的 mock 当内层服务,一步一步推着这个 future 走。测试代码扮演内层:allow(n) 控制它的 poll_ready 放行几次,next_request 取出请求,send_error / send_response 回答。策略最多重试一次,每次重试把请求里的 attempt 加一;等待 future 是一个 Notify,什么时候放行由测试决定。
tower = { version = "=0.5.3", features = ["retry"] }
tower-test = "0.4"
tokio-test = "0.4"
tokio = { version = "=1.54.1", features = ["macros", "rt", "sync"] }
use std::{future::Future, pin::Pin, sync::Arc};
use tokio::sync::Notify;
use tokio_test::task;
use tower::retry::{Policy, Retry};
use tower_test::mock;
type BoxError = Box<dyn std::error::Error + Send + Sync>;
#[derive(Clone, Debug, PartialEq)]
struct Req {
body: &'static str,
attempt: u32,
}
#[derive(Clone)]
struct Attempts {
left: u32,
gate: Arc<Notify>, // the test decides when the wait is over
}
impl Policy<Req, &'static str, BoxError> for Attempts {
type Future = Pin<Box<dyn Future<Output = ()>>>;
fn retry(
&mut self,
req: &mut Req,
result: &mut Result<&'static str, BoxError>,
) -> Option<Self::Future> {
let verdict = match result {
Err(_) if self.left > 0 => "retry",
_ => "stop",
};
println!(" policy.retry(attempt={}, {:?}) left={} -> {verdict}",
req.attempt, result.as_ref().map_err(|e| e.to_string()), self.left);
if verdict == "stop" {
return None;
}
self.left -= 1;
req.attempt += 1; // the next attempt sends the mutated request
let gate = self.gate.clone();
Some(Box::pin(async move { gate.notified().await }))
}
fn clone_request(&mut self, req: &Req) -> Option<Req> {
println!(" policy.clone_request(attempt={})", req.attempt);
Some(req.clone())
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
let gate = Arc::new(Notify::new());
let policy = Attempts { left: 1, gate: gate.clone() };
let (mut svc, mut handle) = mock::spawn_with(|m| Retry::new(policy.clone(), m));
println!("A. error, wait, retry");
handle.allow(1);
println!("svc.poll_ready -> {:?}", svc.poll_ready().map_err(|e| e.to_string()));
let mut fut = task::spawn(svc.call(Req { body: "hello", attempt: 0 }));
let (req, send) = handle.next_request().await.unwrap();
println!("inner got {req:?}");
send.send_error("boom");
println!("fut.poll -> {:?} (Called -> Waiting)", fut.poll().map(|_| ()));
gate.notify_one();
println!("gate opened, fut woken: {}", fut.is_woken());
println!("fut.poll -> {:?} (Waiting -> Retrying, inner not ready)", fut.poll().map(|_| ()));
println!("inner has a request? {}", handle.poll_request().is_ready());
handle.allow(1);
println!("allow(1), fut woken: {}", fut.is_woken());
println!("fut.poll -> {:?} (Retrying -> Called)", fut.poll().map(|_| ()));
let (req, send) = handle.next_request().await.unwrap();
println!("inner got {req:?}");
send.send_response("world");
println!("fut.poll -> {:?}", fut.poll().map(|r| r.map_err(|e| e.to_string())));
println!("B. a second request gets a fresh policy clone");
handle.allow(1);
println!("svc.poll_ready -> {:?}", svc.poll_ready().map_err(|e| e.to_string()));
let mut fut = task::spawn(svc.call(Req { body: "again", attempt: 0 }));
let (_, send) = handle.next_request().await.unwrap();
send.send_error("boom");
println!("fut.poll -> {:?}", fut.poll().map(|_| ()));
gate.notify_one();
handle.allow(1);
println!("fut.poll -> {:?}", fut.poll().map(|_| ()));
let (_, send) = handle.next_request().await.unwrap();
send.send_error("boom again");
println!("fut.poll -> {:?}", fut.poll().map(|r| r.map_err(|e| e.to_string())));
println!("C. inner poll_ready fails while Retrying");
handle.allow(1);
println!("svc.poll_ready -> {:?}", svc.poll_ready().map_err(|e| e.to_string()));
let mut fut = task::spawn(svc.call(Req { body: "third", attempt: 0 }));
let (_, send) = handle.next_request().await.unwrap();
send.send_error("boom");
println!("fut.poll -> {:?}", fut.poll().map(|_| ()));
handle.send_error("inner broken");
gate.notify_one();
println!("fut.poll -> {:?}", fut.poll().map(|r| r.map_err(|e| e.to_string())));
}
M2 Max、rustc 1.99.0、--release,连跑 3 次输出相同:
A. error, wait, retry
svc.poll_ready -> Ready(Ok(()))
policy.clone_request(attempt=0)
inner got Req { body: "hello", attempt: 0 }
policy.retry(attempt=0, Err("boom")) left=1 -> retry
fut.poll -> Pending (Called -> Waiting)
gate opened, fut woken: true
fut.poll -> Pending (Waiting -> Retrying, inner not ready)
inner has a request? false
allow(1), fut woken: true
policy.clone_request(attempt=1)
fut.poll -> Pending (Retrying -> Called)
inner got Req { body: "hello", attempt: 1 }
policy.retry(attempt=1, Ok("world")) left=0 -> stop
fut.poll -> Ready(Ok("world"))
B. a second request gets a fresh policy clone
svc.poll_ready -> Ready(Ok(()))
policy.clone_request(attempt=0)
policy.retry(attempt=0, Err("boom")) left=1 -> retry
fut.poll -> Pending
policy.clone_request(attempt=1)
fut.poll -> Pending
policy.retry(attempt=1, Err("boom again")) left=0 -> stop
fut.poll -> Ready(Err("boom again"))
C. inner poll_ready fails while Retrying
svc.poll_ready -> Ready(Ok(()))
policy.clone_request(attempt=0)
policy.retry(attempt=0, Err("boom")) left=1 -> retry
fut.poll -> Pending
fut.poll -> Ready(Err("inner broken"))
对照状态机读:
- A:
allow(1)只放行一次,第一次call用掉了。第一次fut.poll拿到boom,策略说重试,进Waiting,等待 future 没完成,Pending。打开闸门后 future 被唤醒,第二次poll走到Retrying:副本服务的poll_ready是Pending,内层也确实没收到新请求。allow(1)唤醒了这个 future(等待内层就绪的是它,不是调用方的svc),第三次poll发出重试,内层收到的请求里attempt是 1,就是策略改过的那个。最终成功,策略照样被问了一次,返回None。 - B:同一个
svc的第二个请求,策略的left又是 1,因为它用的是一份新复制的策略。第二次失败时left=0,策略放弃,boom again原样返回。 - C:
Handle::send_error让内层下一次poll_ready报错。重试前的poll_ready拿到这个错,future 直接以它结束,策略没有被调用(没有第二行policy.retry)。也就是说,策略只看得到内层call的结果,看不到poll_ready的错误。按 Service trait 的约定,poll_ready报错表示这个服务实例已经不能用了,重试也没有意义。
重试在 future 里问就绪
Retrying 里的 poll_ready 是在响应 future 里调用的,也就是在等待响应的那个任务里。对调用方来说,它只 poll_ready 过一次、call 过一次,之后所有的重试、等待、再就绪都藏在 future 里。
这决定了别的中间件放在 Retry 外面还是里面,效果完全不同:
- 放在
Retry外面:它只看到一个请求。外层的call只发生一次,重试不会经过它。 - 放在
Retry里面:每次重试都是一次新的poll_ready+call。
下面拿 ConcurrencyLimit 试两种顺序,上限 1,用 GlobalConcurrencyLimitLayer::with_semaphore 把信号量握在手里,看每一步还剩几个许可:
tower = { version = "=0.5.3", features = ["retry", "limit"] }
tower-test = "0.4"
tokio-test = "0.4"
tokio = { version = "=1.54.1", features = ["macros", "rt", "sync"] }
use std::{future::Future, pin::Pin, sync::Arc};
use tokio::sync::{Notify, Semaphore};
use tokio_test::task;
use tower::{limit::GlobalConcurrencyLimitLayer, retry::{Policy, RetryLayer}, Layer, Service};
use tower_test::mock;
type BoxError = Box<dyn std::error::Error + Send + Sync>;
#[derive(Clone)]
struct OneRetry {
left: u32,
gate: Arc<Notify>,
}
impl Policy<&'static str, &'static str, BoxError> for OneRetry {
type Future = Pin<Box<dyn Future<Output = ()>>>;
fn retry(
&mut self,
_: &mut &'static str,
result: &mut Result<&'static str, BoxError>,
) -> Option<Self::Future> {
if result.is_ok() || self.left == 0 {
return None;
}
self.left -= 1;
let gate = self.gate.clone();
Some(Box::pin(async move { gate.notified().await }))
}
fn clone_request(&mut self, req: &&'static str) -> Option<&'static str> {
Some(*req)
}
}
async fn run<S>(name: &str, svc: S, sem: Arc<Semaphore>, gate: Arc<Notify>, mut handle: mock::Handle<&'static str, &'static str>)
where
S: Service<&'static str, Response = &'static str>,
S::Error: std::fmt::Debug,
{
println!("{name}");
let mut svc = mock::Spawn::new(svc);
assert!(svc.poll_ready().is_ready());
let mut fut = task::spawn(svc.call("hi"));
let (_, send) = handle.next_request().await.unwrap();
println!(" first attempt in flight: available = {}", sem.available_permits());
send.send_error("boom");
assert!(fut.poll().is_pending());
println!(" waiting to retry: available = {}", sem.available_permits());
gate.notify_one();
assert!(fut.poll().is_pending());
let (_, send) = handle.next_request().await.unwrap();
println!(" retry in flight: available = {}", sem.available_permits());
send.send_response("ok");
println!(" result: {:?}", fut.poll());
drop(fut);
println!(" future dropped: available = {}", sem.available_permits());
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
let gate = Arc::new(Notify::new());
let policy = OneRetry { left: 1, gate: gate.clone() };
// Retry outside, limit inside: each attempt takes its own permit
let sem = Arc::new(Semaphore::new(1));
let (inner, handle) = mock::pair::<&'static str, &'static str>();
let svc = RetryLayer::new(policy.clone())
.layer(GlobalConcurrencyLimitLayer::with_semaphore(sem.clone()).layer(inner));
run("Retry(ConcurrencyLimit(mock))", svc, sem, gate.clone(), handle).await;
// Limit outside, Retry inside: the whole retry session holds one permit
let sem = Arc::new(Semaphore::new(1));
let (inner, handle) = mock::pair::<&'static str, &'static str>();
let svc = GlobalConcurrencyLimitLayer::with_semaphore(sem.clone())
.layer(RetryLayer::new(policy.clone()).layer(inner));
run("ConcurrencyLimit(Retry(mock))", svc, sem, gate.clone(), handle).await;
}
Retry(ConcurrencyLimit(mock)) 那一组,waiting to retry 一行的 available 是 0 还是 1?第一次请求失败,策略说要重试,future 正在 Waiting 里等闸门。这时候上限为 1 的信号量里还剩几个许可?ConcurrencyLimit(Retry(mock)) 那一组呢?完整输出在文末。
同样的道理也适用于 Timeout:Timeout::call 每调用一次就新建一个 Sleep。套在 Retry 外面,整个重试会话(包括所有等待)共用一个期限;套在里面,每次尝试各自计时,超时产生的 Elapsed 错误会交给策略判断要不要重试。
ServiceBuilder 里写 .retry(policy)(builder/mod.rs 第 249 行)就是加一层 RetryLayer,先写的在外层,所以 .retry(p).concurrency_limit(1) 是上面第一种顺序。层的顺序规则见 ServiceBuilder。
动脑筋答案
clones 是几
M2 Max、rustc 1.99.0、--release,连跑 3 次输出相同:
req 1: clone_request clones=1
req 1: retry clones=1 retries=1
req 1: clone_request clones=2
req 1: retry clones=2 retries=2
req 2: clone_request clones=2
req 2: retry clones=2 retries=1
req 2: clone_request clones=3
req 2: retry clones=3 retries=2
req 3: clone_request clones=3
req 3: retry clones=3 retries=1
req 3: clone_request clones=4
req 3: retry clones=4 retries=2
答案是 3。
- 每个请求的第一次
clone_request在Retry::call里,作用在服务自己的self.policy上,所以这一次+1留在了服务里:1、2、3。 - 紧接着
self.clone()把已经加过的值复制进 future,所以每个请求的会话副本从 1、2、3 起步。 - 重试前的
clone_request在 future 里,作用在副本上,+1随着 future 一起丢掉:第一个请求里变成 2,但第二个请求又从服务上的 2 开始。 retries只在副本上改,所以每个请求都从 1 开始。
结论:只在 retry 里改策略的字段是安全的;在 clone_request 里改,第一次的修改会漏到之后所有请求上。
剩几个许可
M2 Max、rustc 1.99.0、--release,连跑 3 次输出相同:
Retry(ConcurrencyLimit(mock))
first attempt in flight: available = 0
waiting to retry: available = 1
retry in flight: available = 0
result: Ready(Ok("ok"))
future dropped: available = 1
ConcurrencyLimit(Retry(mock))
first attempt in flight: available = 0
waiting to retry: available = 0
retry in flight: available = 0
result: Ready(Ok("ok"))
future dropped: available = 1
- 第一组
waiting to retry是 1。许可在ConcurrencyLimit的响应 future 里,这个 future 是Called状态里的future。进入Waiting时state.set把它换掉,许可随之归还。退避期间不占并发名额,重试前副本服务的poll_ready再去排队取一个。 - 第二组一直是 0。
ConcurrencyLimit在外层,它的许可包着整个Retry的响应 future,从第一次发送一直占到 future 被丢弃,等待期间也占着。 - 两组的
result那一行之后许可都还没还:最后一次的响应 future 还在ResponseFuture里,要等调用方丢掉它。这和 ConcurrencyLimit 里「许可的寿命是响应 future 的寿命」是同一回事。
小结
Policy::retry看每次call的结果,返回None结束,返回Some(fut)等fut完成后重试;可以改请求和结果。clone_request返回None的请求不会重试。Retry::poll_ready原样转发。call先复制请求、再发请求、最后复制整个Retry进 future,所以内层和策略都要Clone,每个请求都付一次复制的钱。- 响应 future 在
Called → Waiting → Retrying → Called之间循环。Retrying自己调副本服务的poll_ready,报错直接返回,不经过策略。 - 策略每个请求一份副本,
retry里的修改不会跨请求;但第一次clone_request改的是服务自己的策略。 - 重试藏在响应 future 里:
Retry外面的中间件只看到一个请求,里面的看到每一次尝试。ConcurrencyLimit放里面,退避期间不占许可。