volo_grpc/client/layer/
timeout.rs1use std::{
2 future::Future,
3 pin::Pin,
4 task::{Context, Poll},
5 time::Duration,
6};
7
8use metainfo::METAINFO;
9use motore::{Service, layer::Layer};
10use pin_project::pin_project;
11use tokio::time::{self, Sleep};
12
13use crate::{Request, context::ClientContext, metadata::MetadataValue, status::Status};
14
15#[derive(Debug, Clone)]
17pub struct Timeout<S> {
18 inner: S,
19}
20
21impl<S> Timeout<S> {
22 pub fn new(inner: S) -> Self {
23 Self { inner }
24 }
25}
26
27#[derive(Clone, Default, Copy)]
28pub struct TimeoutLayer;
29
30impl TimeoutLayer {
31 pub fn new() -> Self {
32 Self
33 }
34}
35
36impl<S> Layer<S> for TimeoutLayer {
37 type Service = Timeout<S>;
38
39 fn layer(self, inner: S) -> Self::Service {
40 Timeout { inner }
41 }
42}
43
44impl<S, T> Service<ClientContext, Request<T>> for Timeout<S>
45where
46 S: Service<ClientContext, Request<T>, Error = Status> + Send + Sync,
47 T: Send + 'static,
48{
49 type Response = S::Response;
50 type Error = Status;
51
52 async fn call(
53 &self,
54 cx: &mut ClientContext,
55 mut req: Request<T>,
56 ) -> Result<Self::Response, Self::Error> {
57 let config_timeout = cx.rpc_info.config().rpc_timeout();
58
59 let mi_timeout = METAINFO.with(|m| m.borrow().get::<Duration>().cloned());
60
61 let timeout_duration = match (config_timeout, mi_timeout) {
63 (None, None) => None,
64 (None, Some(t)) | (Some(t), None) => Some(t),
65 (Some(t1), Some(t2)) => Some(t1.min(t2)),
66 };
67
68 if let Some(timeout) = timeout_duration {
69 let header_val = duration_to_grpc_timeout(timeout);
70 if let Ok(meta_val) = MetadataValue::from_str(&header_val) {
72 req.metadata_mut()
73 .insert(crate::metadata::GRPC_TIMEOUT_HEADER, meta_val);
74 } else {
75 tracing::warn!("Invalid grpc-timeout value: {}", header_val);
76 }
77 }
78
79 let sleep = timeout_duration.map(time::sleep);
80 let inner = self.inner.call(cx, req);
81
82 ResponseFuture {
83 inner,
84 sleep: sleep.map(OptionPin::Some).unwrap_or(OptionPin::None),
85 }
86 .await
87 }
88}
89
90fn duration_to_grpc_timeout(duration: Duration) -> String {
109 let secs = duration.as_secs();
110 let nanos = duration.subsec_nanos();
111
112 if nanos == 0 {
113 if secs % 3600 == 0 {
114 let hrs = secs / 3600;
115 format!("{hrs}H")
116 } else if secs % 60 == 0 {
117 let mins = secs / 60;
118 format!("{mins}M")
119 } else {
120 format!("{secs}S")
121 }
122 } else if secs == 0 && nanos % 1_000_000 == 0 {
123 let millis = nanos / 1_000_000;
124 format!("{millis}m")
125 } else if secs == 0 && nanos % 1_000 == 0 {
126 let micros = nanos / 1_000;
127 format!("{micros}u")
128 } else if secs == 0 {
129 format!("{nanos}n")
130 } else {
131 let total_nanos = secs * 1_000_000_000 + nanos as u64;
132 format!("{total_nanos}n")
133 }
134}
135
136#[pin_project]
137pub struct ResponseFuture<F> {
138 #[pin]
139 inner: F,
140 #[pin]
141 sleep: OptionPin<Sleep>,
142}
143
144#[pin_project(project = OptionPinProj)]
145pub enum OptionPin<T> {
146 Some(#[pin] T),
147 None,
148}
149
150impl<F, R> Future for ResponseFuture<F>
151where
152 F: Future<Output = Result<R, Status>>,
153{
154 type Output = Result<R, Status>;
155
156 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
157 let this = self.project();
158
159 if let Poll::Ready(res) = this.inner.poll(cx) {
160 return Poll::Ready(res);
161 }
162
163 if let OptionPinProj::Some(sleep) = this.sleep.project() {
164 futures_util::ready!(sleep.poll(cx));
165 let err = Status::deadline_exceeded("timeout");
166 return Poll::Ready(Err(err));
167 }
168
169 Poll::Pending
170 }
171}
172
173#[cfg(test)]
174mod tests {
175
176 use super::*;
177 #[test]
178 fn test_hours() {
179 let converted_duration = duration_to_grpc_timeout(Duration::from_secs(3 * 3600));
180 assert_eq!("3H", converted_duration);
181 }
182
183 #[test]
184 fn test_minutes() {
185 let converted_duration = duration_to_grpc_timeout(Duration::from_secs(60));
186 assert_eq!("1M", converted_duration);
187 }
188
189 #[test]
190 fn test_seconds() {
191 let converted_duration = duration_to_grpc_timeout(Duration::from_secs(42));
192 assert_eq!("42S", converted_duration);
193 }
194
195 #[test]
196 fn test_milliseconds() {
197 let converted_duration = duration_to_grpc_timeout(Duration::from_millis(13));
198 assert_eq!("13m", converted_duration);
199 }
200
201 #[test]
202 fn test_microseconds() {
203 let converted_duration = duration_to_grpc_timeout(Duration::from_micros(2));
204 assert_eq!("2u", converted_duration);
205 }
206
207 #[test]
208 fn test_nanoseconds() {
209 let converted_duration = duration_to_grpc_timeout(Duration::from_nanos(82));
210 assert_eq!("82n", converted_duration);
211 }
212}