1use std::{
6 convert::Infallible,
7 num::NonZeroUsize,
8 task::{Context, Poll},
9 time::Duration,
10};
11
12use eyre::{Result, eyre};
13use iota_http::metrics::ListenerMetrics;
14use tokio_rustls::rustls::ServerConfig;
15use tonic::{
16 body::Body,
17 codegen::http::{HeaderValue, Request, Response},
18 server::NamedService,
19};
20use tower::{Layer, Service, ServiceBuilder};
21use tower_http::{
22 propagate_header::PropagateHeaderLayer, set_header::SetRequestHeaderLayer, trace::TraceLayer,
23};
24
25use crate::{
26 concurrency::ServiceConcurrencyLimit,
27 config::Config,
28 metrics::{
29 DefaultMetricsCallbackProvider, GRPC_ENDPOINT_PATH_HEADER, MetricsCallbackProvider,
30 MetricsHandler,
31 },
32 multiaddr::{Multiaddr, Protocol},
33};
34
35pub const DEFAULT_GRPC_REQUEST_TIMEOUT: Duration = Duration::from_secs(300);
42
43pub struct ServerBuilder<M: MetricsCallbackProvider = DefaultMetricsCallbackProvider> {
44 config: Config,
45 metrics_provider: M,
46 router: tonic::service::Routes,
47 health_reporter: tonic_health::server::HealthReporter,
48 listener_metrics: Option<ListenerMetrics>,
49}
50
51impl<M: MetricsCallbackProvider> ServerBuilder<M> {
52 pub fn from_config(config: &Config, metrics_provider: M) -> Self {
53 let (health_reporter, health_service) = tonic_health::server::health_reporter();
54 let router = tonic::service::Routes::new(health_service);
55
56 Self {
57 config: config.to_owned(),
58 metrics_provider,
59 router,
60 health_reporter,
61 listener_metrics: None,
62 }
63 }
64
65 pub fn listener_metrics(mut self, metrics: ListenerMetrics) -> Self {
68 self.listener_metrics = Some(metrics);
69 self
70 }
71
72 pub fn health_reporter(&self) -> tonic_health::server::HealthReporter {
73 self.health_reporter.clone()
74 }
75
76 pub fn request_timeout(&self) -> Duration {
79 self.config
80 .request_timeout
81 .unwrap_or(DEFAULT_GRPC_REQUEST_TIMEOUT)
82 }
83
84 pub fn add_service<S>(mut self, svc: S) -> Self
86 where
87 S: Service<Request<Body>, Response = Response<Body>, Error = Infallible>
88 + NamedService
89 + Clone
90 + Send
91 + Sync
92 + 'static,
93 S::Future: Send + 'static,
94 {
95 self.router = self.router.add_service(svc);
96 self
97 }
98
99 pub fn add_service_with_concurrency_limit<S>(
106 mut self,
107 svc: S,
108 limit: NonZeroUsize,
109 load_shed: bool,
110 ) -> Self
111 where
112 S: Service<Request<Body>, Response = Response<Body>, Error = Infallible>
113 + NamedService
114 + Clone
115 + Send
116 + Sync
117 + 'static,
118 S::Future: Send + 'static,
119 {
120 self.router = self
121 .router
122 .add_service(ServiceConcurrencyLimit::new(svc, limit, load_shed));
123 self
124 }
125
126 pub async fn bind(self, addr: &Multiaddr, tls_config: Option<ServerConfig>) -> Result<Server> {
127 let request_timeout = self.request_timeout();
128 let http_config = self.config.http_config().metrics(self.listener_metrics);
129
130 let metrics_provider = self.metrics_provider;
131 let metrics = MetricsHandler::new(metrics_provider.clone());
132 let request_metrics = TraceLayer::new_for_grpc()
133 .on_request(metrics.clone())
134 .on_response(metrics.clone())
135 .on_failure(metrics);
136
137 fn add_path_to_request_header<T>(request: &Request<T>) -> Option<HeaderValue> {
138 let path = request.uri().path();
139 Some(HeaderValue::from_str(path).unwrap())
140 }
141
142 let limiting_layers = ServiceBuilder::new()
143 .option_layer(
144 self.config
145 .load_shed
146 .unwrap_or_default()
147 .then_some(tower::load_shed::LoadShedLayer::new()),
148 )
149 .option_layer(
150 self.config
151 .global_concurrency_limit
152 .map(tower::limit::GlobalConcurrencyLimitLayer::new),
153 );
154
155 let route_layers = ServiceBuilder::new()
156 .map_request(insert_tonic_connect_info)
157 .layer(RequestLifetimeLayer { metrics_provider })
158 .layer(SetRequestHeaderLayer::overriding(
159 GRPC_ENDPOINT_PATH_HEADER.clone(),
160 add_path_to_request_header,
161 ))
162 .layer(request_metrics)
163 .layer(PropagateHeaderLayer::new(GRPC_ENDPOINT_PATH_HEADER.clone()))
164 .layer_fn(move |service| {
165 crate::grpc_timeout::GrpcTimeout::new(service, Some(request_timeout))
166 });
167
168 let mut builder = iota_http::Builder::new().config(http_config);
169
170 let has_tls = tls_config.is_some();
171 if let Some(tls_config) = tls_config {
172 builder = builder.tls_config(tls_config);
173 }
174
175 let server_handle = builder
176 .serve(
177 addr,
178 limiting_layers.service(self.router.into_axum_router().layer(route_layers)),
179 )
180 .map_err(|e| eyre!(e))?;
181
182 let mut local_addr = update_tcp_port_in_multiaddr(addr, server_handle.local_addr().port());
183 if has_tls {
184 local_addr = local_addr.rewrite_http_to_https();
185 }
186 Ok(Server {
187 server_handle,
188 local_addr,
189 health_reporter: self.health_reporter,
190 })
191 }
192}
193
194pub const IOTA_TLS_SERVER_NAME: &str = "iota";
196
197pub struct Server {
198 server_handle: iota_http::ServerHandle,
199 local_addr: Multiaddr,
200 health_reporter: tonic_health::server::HealthReporter,
201}
202
203impl Server {
204 pub async fn serve(self) -> Result<(), tonic::transport::Error> {
205 self.server_handle.wait_for_shutdown().await;
206 Ok(())
207 }
208
209 pub fn trigger_shutdown(&self) {
210 self.server_handle.trigger_shutdown();
211 }
212
213 pub fn local_addr(&self) -> &Multiaddr {
214 &self.local_addr
215 }
216
217 pub fn health_reporter(&self) -> tonic_health::server::HealthReporter {
218 self.health_reporter.clone()
219 }
220
221 pub fn handle(&self) -> &iota_http::ServerHandle {
222 &self.server_handle
223 }
224}
225
226pub fn insert_tonic_connect_info<B>(mut request: Request<B>) -> Request<B> {
230 if let Some(connect_info) = request.extensions().get::<iota_http::ConnectInfo>() {
231 let tonic_connect_info = tonic::transport::server::TcpConnectInfo {
232 local_addr: Some(connect_info.local_addr),
233 remote_addr: Some(connect_info.remote_addr),
234 };
235 request.extensions_mut().insert(tonic_connect_info);
236 }
237 request
238}
239
240fn update_tcp_port_in_multiaddr(addr: &Multiaddr, port: u16) -> Multiaddr {
241 addr.replace(1, |protocol| {
242 if let Protocol::Tcp(_) = protocol {
243 Some(Protocol::Tcp(port))
244 } else {
245 panic!("expected tcp protocol at index 1");
246 }
247 })
248 .expect("tcp protocol at index 1")
249}
250
251#[cfg(test)]
252mod test {
253 use std::{
254 ops::Deref,
255 sync::{Arc, Mutex},
256 time::Duration,
257 };
258
259 use fastcrypto::{ed25519::Ed25519KeyPair, traits::KeyPair};
260 use iota_metrics::test_utils::MetricsReader;
261 use tonic::Code;
262 use tonic_health::pb::{HealthCheckRequest, health_client::HealthClient};
263
264 use crate::{Multiaddr, config::Config, metrics::MetricsCallbackProvider};
265
266 #[test]
267 fn insert_tonic_connect_info_copies_both_addresses() {
268 let local_addr = "127.0.0.1:1".parse().unwrap();
269 let remote_addr = "127.0.0.2:2".parse().unwrap();
270 let mut request = http::Request::new(());
271 request.extensions_mut().insert(iota_http::ConnectInfo {
272 local_addr,
273 remote_addr,
274 });
275
276 let request = super::insert_tonic_connect_info(request);
277
278 let connect_info = request
279 .extensions()
280 .get::<tonic::transport::server::TcpConnectInfo>()
281 .unwrap();
282 assert_eq!(connect_info.local_addr(), Some(local_addr));
283 assert_eq!(connect_info.remote_addr(), Some(remote_addr));
284 }
285
286 #[tokio::test]
287 async fn test_metrics_layer_successful() {
288 #[derive(Clone)]
289 struct Metrics {
290 metrics_called: Arc<Mutex<bool>>,
293 }
294
295 impl MetricsCallbackProvider for Metrics {
296 fn on_request(&self, path: String) {
297 assert_eq!(path, "/grpc.health.v1.Health/Check");
298 }
299
300 fn on_response(
301 &self,
302 path: String,
303 _latency: Duration,
304 status: u16,
305 grpc_status_code: Code,
306 ) {
307 assert_eq!(path, "/grpc.health.v1.Health/Check");
308 assert_eq!(status, 200);
309 assert_eq!(grpc_status_code, Code::Ok);
310 let mut m = self.metrics_called.lock().unwrap();
311 *m = true
312 }
313 }
314
315 let metrics = Metrics {
316 metrics_called: Arc::new(Mutex::new(false)),
317 };
318
319 let address: Multiaddr = "/ip4/127.0.0.1/tcp/0/http".parse().unwrap();
320 let config = Config::new();
321 let keypair = Ed25519KeyPair::generate(&mut rand08::thread_rng());
322
323 let server = config
324 .server_builder_with_metrics(metrics.clone())
325 .bind(
326 &address,
327 Some(iota_tls::create_rustls_server_config(
328 keypair.copy().private(),
329 "test".to_string(),
330 )),
331 )
332 .await
333 .unwrap();
334
335 let address = server.local_addr().to_owned();
336 let channel = config
337 .connect(
338 &address,
339 iota_tls::create_rustls_client_config(
340 keypair.public().to_owned(),
341 "test".to_string(),
342 None,
343 ),
344 )
345 .await
346 .unwrap();
347 let mut client = HealthClient::new(channel);
348
349 client
350 .check(HealthCheckRequest {
351 service: "".to_owned(),
352 })
353 .await
354 .unwrap();
355
356 server.server_handle.shutdown().await;
357
358 assert!(metrics.metrics_called.lock().unwrap().deref());
359 }
360
361 #[tokio::test]
362 async fn listener_metrics_count_the_connections_of_the_server() {
363 let registry = prometheus_filtered::Registry::new();
364 let address: Multiaddr = "/ip4/127.0.0.1/tcp/0/http".parse().unwrap();
365 let config = Config::new();
366 let server = config
367 .server_builder()
368 .listener_metrics(iota_http::metrics::ListenerMetrics::new(
369 "test",
370 ®istry,
371 prometheus_filtered::MetricLevel::Info,
372 ))
373 .bind(&address, None)
374 .await
375 .unwrap();
376
377 let channel = tonic::transport::Endpoint::from_shared(format!(
378 "http://{}",
379 server.local_addr().to_socket_addr().unwrap()
380 ))
381 .unwrap()
382 .connect()
383 .await
384 .unwrap();
385 HealthClient::new(channel)
386 .check(HealthCheckRequest {
387 service: "".to_owned(),
388 })
389 .await
390 .unwrap();
391
392 let reader = MetricsReader::new(®istry).with_prefix("test");
393 assert_eq!(reader.value("inbound_connections_accepted", &[]), 1.0);
394 assert!(
395 !reader.has_family("pending_handshakes_peak"),
396 "a plain listener has no TLS metrics"
397 );
398 server.trigger_shutdown();
399 }
400
401 #[tokio::test]
402 async fn listener_metrics_time_the_tls_handshakes_of_the_server() {
403 let registry = prometheus_filtered::Registry::new();
404 let address: Multiaddr = "/ip4/127.0.0.1/tcp/0/http".parse().unwrap();
405 let config = Config::new();
406 let keypair = Ed25519KeyPair::generate(&mut rand08::thread_rng());
407 let server = config
408 .server_builder()
409 .listener_metrics(iota_http::metrics::ListenerMetrics::new(
410 "test",
411 ®istry,
412 prometheus_filtered::MetricLevel::Info,
413 ))
414 .bind(
415 &address,
416 Some(iota_tls::create_rustls_server_config(
417 keypair.copy().private(),
418 "test".to_string(),
419 )),
420 )
421 .await
422 .unwrap();
423
424 let channel = config
425 .connect(
426 server.local_addr(),
427 iota_tls::create_rustls_client_config(
428 keypair.public().to_owned(),
429 "test".to_string(),
430 None,
431 ),
432 )
433 .await
434 .unwrap();
435 HealthClient::new(channel)
436 .check(HealthCheckRequest {
437 service: "".to_owned(),
438 })
439 .await
440 .unwrap();
441
442 let reader = MetricsReader::new(®istry).with_prefix("test");
443 assert_eq!(
444 reader
445 .histogram_totals("handshake_latency", &[("result", "completed")])
446 .count,
447 1
448 );
449 assert_eq!(reader.value("pending_handshakes_peak", &[]), 1.0);
450 server.trigger_shutdown();
451 }
452
453 #[tokio::test]
454 async fn test_metrics_layer_error() {
455 #[derive(Clone)]
456 struct Metrics {
457 metrics_called: Arc<Mutex<bool>>,
460 }
461
462 impl MetricsCallbackProvider for Metrics {
463 fn on_request(&self, path: String) {
464 assert_eq!(path, "/grpc.health.v1.Health/Check");
465 }
466
467 fn on_response(
468 &self,
469 path: String,
470 _latency: Duration,
471 status: u16,
472 grpc_status_code: Code,
473 ) {
474 assert_eq!(path, "/grpc.health.v1.Health/Check");
475 assert_eq!(status, 200);
476 assert_eq!(grpc_status_code, Code::NotFound);
479 let mut m = self.metrics_called.lock().unwrap();
480 *m = true
481 }
482 }
483
484 let metrics = Metrics {
485 metrics_called: Arc::new(Mutex::new(false)),
486 };
487
488 let address: Multiaddr = "/ip4/127.0.0.1/tcp/0/http".parse().unwrap();
489 let config = Config::new();
490 let keypair = Ed25519KeyPair::generate(&mut rand08::thread_rng());
491
492 let server = config
493 .server_builder_with_metrics(metrics.clone())
494 .bind(
495 &address,
496 Some(iota_tls::create_rustls_server_config(
497 keypair.copy().private(),
498 "test".to_string(),
499 )),
500 )
501 .await
502 .unwrap();
503 let address = server.local_addr().to_owned();
504 let channel = config
505 .connect(
506 &address,
507 iota_tls::create_rustls_client_config(
508 keypair.public().to_owned(),
509 "test".to_string(),
510 None,
511 ),
512 )
513 .await
514 .unwrap();
515 let mut client = HealthClient::new(channel);
516
517 let _ = client
521 .check(HealthCheckRequest {
522 service: "non-existing-service".to_owned(),
523 })
524 .await;
525
526 server.server_handle.shutdown().await;
527
528 assert!(metrics.metrics_called.lock().unwrap().deref());
529 }
530
531 async fn test_multiaddr(address: Multiaddr) {
532 let config = Config::new();
533 let keypair = Ed25519KeyPair::generate(&mut rand08::thread_rng());
534
535 let server_handle = config
536 .server_builder()
537 .bind(
538 &address,
539 Some(iota_tls::create_rustls_server_config(
540 keypair.copy().private(),
541 "test".to_string(),
542 )),
543 )
544 .await
545 .unwrap();
546 let address = server_handle.local_addr().to_owned();
547 let channel = config
548 .connect(
549 &address,
550 iota_tls::create_rustls_client_config(
551 keypair.public().to_owned(),
552 "test".to_string(),
553 None,
554 ),
555 )
556 .await
557 .unwrap();
558 let mut client = HealthClient::new(channel);
559
560 client
561 .check(HealthCheckRequest {
562 service: "".to_owned(),
563 })
564 .await
565 .unwrap();
566
567 server_handle.server_handle.shutdown().await;
568 }
569
570 #[tokio::test]
571 async fn dns() {
572 let address: Multiaddr = "/dns/localhost/tcp/0/http".parse().unwrap();
573 test_multiaddr(address).await;
574 }
575
576 #[tokio::test]
577 async fn ip4() {
578 let address: Multiaddr = "/ip4/127.0.0.1/tcp/0/http".parse().unwrap();
579 test_multiaddr(address).await;
580 }
581
582 #[tokio::test]
583 async fn ip6() {
584 let address: Multiaddr = "/ip6/::1/tcp/0/http".parse().unwrap();
585 test_multiaddr(address).await;
586 }
587
588 #[test]
589 fn request_timeout_falls_back_to_the_default() {
590 let unset = Config::new();
591 assert_eq!(
592 unset.server_builder().request_timeout(),
593 super::DEFAULT_GRPC_REQUEST_TIMEOUT
594 );
595
596 let mut configured = Config::new();
597 configured.request_timeout = Some(Duration::from_secs(7));
598 assert_eq!(
599 configured.server_builder().request_timeout(),
600 Duration::from_secs(7)
601 );
602 }
603
604 #[derive(Clone)]
608 struct DrainBody;
609
610 impl tower::Service<http::Request<tonic::body::Body>> for DrainBody {
611 type Response = http::Response<tonic::body::Body>;
612 type Error = std::convert::Infallible;
613 type Future = futures::future::BoxFuture<'static, Result<Self::Response, Self::Error>>;
614
615 fn poll_ready(
616 &mut self,
617 _cx: &mut std::task::Context<'_>,
618 ) -> std::task::Poll<Result<(), Self::Error>> {
619 std::task::Poll::Ready(Ok(()))
620 }
621
622 fn call(&mut self, request: http::Request<tonic::body::Body>) -> Self::Future {
623 use http_body_util::BodyExt as _;
624 Box::pin(async move {
625 let _ = request.into_body().collect().await;
626 Ok(tonic::Status::new(Code::Ok, "").into_http())
627 })
628 }
629 }
630
631 impl tonic::server::NamedService for DrainBody {
632 const NAME: &'static str = "test.DrainBody";
633 }
634
635 enum TestBody {
638 Empty,
639 Stalled,
640 }
641
642 impl http_body::Body for TestBody {
643 type Data = bytes::Bytes;
644 type Error = std::convert::Infallible;
645
646 fn poll_frame(
647 self: std::pin::Pin<&mut Self>,
648 _cx: &mut std::task::Context<'_>,
649 ) -> std::task::Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
650 match *self {
651 TestBody::Empty => std::task::Poll::Ready(None),
652 TestBody::Stalled => std::task::Poll::Pending,
653 }
654 }
655
656 fn is_end_stream(&self) -> bool {
657 matches!(self, TestBody::Empty)
658 }
659 }
660
661 #[tokio::test]
664 async fn server_timeout_releases_a_stalled_request_and_its_admission_slot() {
665 const REQUEST_TIMEOUT: Duration = Duration::from_millis(200);
666
667 let mut config = Config::new();
668 config.request_timeout = Some(REQUEST_TIMEOUT);
669 let address: Multiaddr = "/ip4/127.0.0.1/tcp/0/http".parse().unwrap();
670 let server = config
671 .server_builder()
672 .add_service_with_concurrency_limit(DrainBody, std::num::NonZeroUsize::MIN, false)
673 .bind(&address, None)
674 .await
675 .unwrap();
676
677 let stream = tokio::net::TcpStream::connect(server.local_addr().to_socket_addr().unwrap())
678 .await
679 .unwrap();
680 let (mut sender, connection) = hyper::client::conn::http2::handshake(
681 hyper_util::rt::TokioExecutor::new(),
682 hyper_util::rt::TokioIo::new(stream),
683 )
684 .await
685 .unwrap();
686 tokio::spawn(connection);
687
688 let request = |body| {
689 http::Request::post("/test.DrainBody/Drain")
690 .header("content-type", "application/grpc")
691 .body(body)
692 .unwrap()
693 };
694 let grpc_status = |response: &http::Response<hyper::body::Incoming>| {
695 response.headers()["grpc-status"]
696 .to_str()
697 .unwrap()
698 .to_owned()
699 };
700
701 let stalled = tokio::time::timeout(
703 REQUEST_TIMEOUT * 25,
704 sender.send_request(request(TestBody::Stalled)),
705 )
706 .await
707 .expect("the server must answer a stalled request once its deadline passes")
708 .unwrap();
709 assert_eq!(
710 grpc_status(&stalled),
711 (Code::DeadlineExceeded as i32).to_string()
712 );
713
714 let served = sender.send_request(request(TestBody::Empty)).await.unwrap();
716 assert_eq!(grpc_status(&served), (Code::Ok as i32).to_string());
717
718 server.server_handle.shutdown().await;
719 }
720
721 #[tokio::test]
724 async fn stream_cap_bounds_the_requests_one_connection_can_open() {
725 #[derive(Clone, Default)]
726 struct Started(Arc<std::sync::atomic::AtomicUsize>);
727
728 impl MetricsCallbackProvider for Started {
729 fn on_request(&self, _path: String) {}
730
731 fn on_response(
732 &self,
733 _path: String,
734 _latency: Duration,
735 _status: u16,
736 _grpc_status_code: Code,
737 ) {
738 }
739
740 fn on_start(&self, _path: &str) {
741 self.0.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
742 }
743 }
744
745 const STREAM_CAP: u32 = 2;
746
747 let started = Started::default();
748 let mut config = Config::new();
749 config.http2_max_concurrent_streams = Some(STREAM_CAP);
750 let address: Multiaddr = "/ip4/127.0.0.1/tcp/0/http".parse().unwrap();
751 let server = config
752 .server_builder_with_metrics(started.clone())
753 .add_service(DrainBody)
754 .bind(&address, None)
755 .await
756 .unwrap();
757
758 let stream = tokio::net::TcpStream::connect(server.local_addr().to_socket_addr().unwrap())
759 .await
760 .unwrap();
761 let (sender, connection) = hyper::client::conn::http2::handshake(
762 hyper_util::rt::TokioExecutor::new(),
763 hyper_util::rt::TokioIo::new(stream),
764 )
765 .await
766 .unwrap();
767 tokio::spawn(connection);
768
769 let in_flight: Vec<_> = (0..STREAM_CAP + 1)
772 .map(|_| {
773 let mut sender = sender.clone();
774 tokio::spawn(async move {
775 sender
776 .send_request(
777 http::Request::post("/test.DrainBody/Drain")
778 .body(TestBody::Stalled)
779 .unwrap(),
780 )
781 .await
782 })
783 })
784 .collect();
785 tokio::time::sleep(Duration::from_millis(300)).await;
786
787 assert_eq!(
788 started.0.load(std::sync::atomic::Ordering::SeqCst),
789 STREAM_CAP as usize,
790 "the server must only see as many requests as the stream cap allows"
791 );
792
793 for request in in_flight {
794 request.abort();
795 }
796 server.server_handle.shutdown().await;
797 }
798}
799
800#[derive(Clone)]
801struct RequestLifetimeLayer<M: MetricsCallbackProvider> {
802 metrics_provider: M,
803}
804
805impl<M: MetricsCallbackProvider, S> Layer<S> for RequestLifetimeLayer<M> {
806 type Service = RequestLifetime<M, S>;
807
808 fn layer(&self, inner: S) -> Self::Service {
809 RequestLifetime {
810 inner,
811 metrics_provider: self.metrics_provider.clone(),
812 path: None,
813 }
814 }
815}
816
817#[derive(Clone)]
818struct RequestLifetime<M: MetricsCallbackProvider, S> {
819 inner: S,
820 metrics_provider: M,
821 path: Option<String>,
822}
823
824impl<M: MetricsCallbackProvider, S, RequestBody> Service<Request<RequestBody>>
825 for RequestLifetime<M, S>
826where
827 S: Service<Request<RequestBody>>,
828{
829 type Response = S::Response;
830 type Error = S::Error;
831 type Future = S::Future;
832
833 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
834 self.inner.poll_ready(cx)
835 }
836
837 fn call(&mut self, request: Request<RequestBody>) -> Self::Future {
838 if self.path.is_none() {
839 let path = request.uri().path().to_string();
840 self.metrics_provider.on_start(&path);
841 self.path = Some(path);
842 }
843 self.inner.call(request)
844 }
845}
846
847impl<M: MetricsCallbackProvider, S> Drop for RequestLifetime<M, S> {
848 fn drop(&mut self) {
849 if let Some(path) = &self.path {
850 self.metrics_provider.on_drop(path)
851 }
852 }
853}