1#![cfg_attr(test, allow(clippy::unwrap_used))]
2#![doc(html_favicon_url = "https://salvo.rs/favicon-32x32.png")]
86#![doc(html_logo_url = "https://salvo.rs/images/logo.svg")]
87#![cfg_attr(docsrs, feature(doc_cfg))]
88
89use std::borrow::Borrow;
90use std::collections::{HashMap, VecDeque};
91use std::error::Error as StdError;
92use std::fmt::{self, Debug, Formatter};
93use std::hash::Hash;
94use std::sync::{Arc, Mutex, MutexGuard};
95
96use bytes::Bytes;
97use salvo_core::handler::Skipper;
98use salvo_core::http::{HeaderMap, ResBody, StatusCode};
99use salvo_core::{Depot, Error, FlowCtrl, Handler, Request, Response, async_trait};
100use tokio::sync::Notify;
101
102mod skipper;
103pub use skipper::MethodSkipper;
104
105#[macro_use]
106mod cfg;
107
108cfg_feature! {
109 #![feature = "moka-store"]
110
111 pub mod moka_store;
112 pub use moka_store::{MokaStore};
113}
114
115pub trait CacheIssuer: Send + Sync + 'static {
117 type Key: Hash + Eq + Send + Sync + 'static;
119 fn issue(
121 &self,
122 req: &mut Request,
123 depot: &Depot,
124 ) -> impl Future<Output = Option<Self::Key>> + Send;
125}
126impl<F, K> CacheIssuer for F
127where
128 F: Fn(&mut Request, &Depot) -> Option<K> + Send + Sync + 'static,
129 K: Hash + Eq + Send + Sync + 'static,
130{
131 type Key = K;
132 async fn issue(&self, req: &mut Request, depot: &Depot) -> Option<Self::Key> {
133 (self)(req, depot)
134 }
135}
136
137#[derive(Clone, Debug)]
139pub struct RequestIssuer {
140 use_scheme: bool,
141 use_authority: bool,
142 use_path: bool,
143 use_query: bool,
144 use_method: bool,
145}
146impl Default for RequestIssuer {
147 fn default() -> Self {
148 Self::new()
149 }
150}
151impl RequestIssuer {
152 #[must_use]
154 pub fn new() -> Self {
155 Self {
156 use_scheme: true,
157 use_authority: true,
158 use_path: true,
159 use_query: true,
160 use_method: true,
161 }
162 }
163 #[must_use]
165 pub fn use_scheme(mut self, value: bool) -> Self {
166 self.use_scheme = value;
167 self
168 }
169 #[must_use]
171 pub fn use_authority(mut self, value: bool) -> Self {
172 self.use_authority = value;
173 self
174 }
175 #[must_use]
177 pub fn use_path(mut self, value: bool) -> Self {
178 self.use_path = value;
179 self
180 }
181 #[must_use]
183 pub fn use_query(mut self, value: bool) -> Self {
184 self.use_query = value;
185 self
186 }
187 #[must_use]
189 pub fn use_method(mut self, value: bool) -> Self {
190 self.use_method = value;
191 self
192 }
193}
194
195impl CacheIssuer for RequestIssuer {
196 type Key = String;
197 async fn issue(&self, req: &mut Request, _depot: &Depot) -> Option<Self::Key> {
198 let mut key = String::with_capacity(req.uri().path().len() + 16);
199 if self.use_scheme
200 && let Some(scheme) = req.uri().scheme_str()
201 {
202 key.push_str(scheme);
203 key.push_str("://");
204 }
205 if self.use_authority
206 && let Some(authority) = req.uri().authority()
207 {
208 key.push_str(authority.as_str());
209 }
210 if self.use_path {
211 key.push_str(req.uri().path());
212 }
213 if self.use_query
214 && let Some(query) = req.uri().query()
215 {
216 key.push('?');
217 key.push_str(query);
218 }
219 if self.use_method {
220 key.push('|');
221 key.push_str(req.method().as_str());
222 }
223 Some(key)
224 }
225}
226
227pub trait CacheStore: Send + Sync + 'static {
229 type Error: StdError + Sync + Send + 'static;
231 type Key: Hash + Eq + Send + Clone + 'static;
233 fn load_entry<Q>(&self, key: &Q) -> impl Future<Output = Option<CachedEntry>> + Send
235 where
236 Self::Key: Borrow<Q>,
237 Q: Hash + Eq + Sync;
238 fn save_entry(
240 &self,
241 key: Self::Key,
242 data: CachedEntry,
243 ) -> impl Future<Output = Result<(), Self::Error>> + Send;
244}
245
246fn mutex_lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
247 match mutex.lock() {
248 Ok(guard) => guard,
249 Err(poisoned) => poisoned.into_inner(),
250 }
251}
252
253struct InFlight<K> {
254 entries: Mutex<HashMap<K, Arc<Flight>>>,
255}
256
257impl<K> Default for InFlight<K> {
258 fn default() -> Self {
259 Self {
260 entries: Mutex::new(HashMap::new()),
261 }
262 }
263}
264
265impl<K> InFlight<K>
266where
267 K: Hash + Eq + Clone,
268{
269 fn enter(self: &Arc<Self>, key: K) -> FlightPermit<K> {
270 let mut entries = mutex_lock(&self.entries);
271 if let Some(flight) = entries.get(&key) {
272 return FlightPermit::Follower(flight.clone());
273 }
274
275 let flight = Arc::new(Flight::default());
276 entries.insert(key.clone(), flight.clone());
277 FlightPermit::Leader(FlightGuard {
278 key: Some(key),
279 flight,
280 in_flight: self.clone(),
281 })
282 }
283}
284
285impl<K> InFlight<K>
286where
287 K: Hash + Eq,
288{
289 fn remove(&self, key: &K, flight: &Arc<Flight>) {
290 let mut entries = mutex_lock(&self.entries);
291 if entries
292 .get(key)
293 .is_some_and(|current| Arc::ptr_eq(current, flight))
294 {
295 entries.remove(key);
296 }
297 }
298}
299
300enum FlightPermit<K>
301where
302 K: Hash + Eq,
303{
304 Leader(FlightGuard<K>),
305 Follower(Arc<Flight>),
306}
307
308struct FlightGuard<K>
309where
310 K: Hash + Eq,
311{
312 key: Option<K>,
313 flight: Arc<Flight>,
314 in_flight: Arc<InFlight<K>>,
315}
316
317impl<K> FlightGuard<K>
318where
319 K: Hash + Eq,
320{
321 fn finish(mut self, entry: Option<CachedEntry>) {
322 self.complete(entry);
323 }
324
325 fn complete(&mut self, entry: Option<CachedEntry>) {
326 if let Some(key) = self.key.take() {
327 self.flight.finish(entry);
328 self.in_flight.remove(&key, &self.flight);
329 }
330 }
331}
332
333impl<K> Drop for FlightGuard<K>
334where
335 K: Hash + Eq,
336{
337 fn drop(&mut self) {
338 self.complete(None);
339 }
340}
341
342#[derive(Default)]
343struct Flight {
344 state: Mutex<FlightState>,
345 notify: Notify,
346}
347
348#[derive(Default)]
349struct FlightState {
350 done: bool,
351 entry: Option<CachedEntry>,
352}
353
354impl Flight {
355 async fn wait(&self) {
356 loop {
357 let notified = self.notify.notified();
358 if mutex_lock(&self.state).done {
359 return;
360 }
361 notified.await;
362 }
363 }
364
365 fn finish(&self, entry: Option<CachedEntry>) {
366 *mutex_lock(&self.state) = FlightState { done: true, entry };
367 self.notify.notify_waiters();
368 }
369
370 fn entry(&self) -> Option<CachedEntry> {
371 mutex_lock(&self.state).entry.clone()
372 }
373}
374
375#[derive(Clone, Debug, PartialEq)]
380#[non_exhaustive]
381pub enum CachedBody {
382 None,
384 Once(Bytes),
386 Chunks(VecDeque<Bytes>),
388}
389impl TryFrom<&ResBody> for CachedBody {
390 type Error = Error;
391 fn try_from(body: &ResBody) -> Result<Self, Self::Error> {
392 match body {
393 ResBody::None => Ok(Self::None),
394 ResBody::Once(bytes) => Ok(Self::Once(bytes.to_owned())),
395 ResBody::Chunks(chunks) => Ok(Self::Chunks(chunks.to_owned())),
396 _ => Err(Error::other("unsupported body type")),
397 }
398 }
399}
400impl From<CachedBody> for ResBody {
401 fn from(body: CachedBody) -> Self {
402 match body {
403 CachedBody::None => Self::None,
404 CachedBody::Once(bytes) => Self::Once(bytes),
405 CachedBody::Chunks(chunks) => Self::Chunks(chunks),
406 }
407 }
408}
409
410#[derive(Clone, Debug)]
412#[non_exhaustive]
413pub struct CachedEntry {
414 pub status: Option<StatusCode>,
416 pub headers: HeaderMap,
418 pub body: CachedBody,
422}
423impl CachedEntry {
424 pub fn new(status: Option<StatusCode>, headers: HeaderMap, body: CachedBody) -> Self {
426 Self {
427 status,
428 headers,
429 body,
430 }
431 }
432
433 pub fn status(&self) -> Option<StatusCode> {
435 self.status
436 }
437
438 pub fn headers(&self) -> &HeaderMap {
440 &self.headers
441 }
442
443 pub fn body(&self) -> &CachedBody {
447 &self.body
448 }
449}
450
451#[non_exhaustive]
470pub struct Cache<S, I>
471where
472 S: CacheStore,
473{
474 pub store: S,
476 pub issuer: I,
478 pub skipper: Box<dyn Skipper>,
480 in_flight: Arc<InFlight<S::Key>>,
481}
482impl<S, I> Debug for Cache<S, I>
483where
484 S: CacheStore + Debug,
485 I: Debug,
486{
487 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
488 f.debug_struct("Cache")
489 .field("store", &self.store)
490 .field("issuer", &self.issuer)
491 .finish()
492 }
493}
494
495impl<S, I> Cache<S, I>
496where
497 S: CacheStore,
498{
499 #[inline]
501 #[must_use]
502 pub fn new(store: S, issuer: I) -> Self {
503 let skipper = MethodSkipper::new().skip_all().skip_get(false);
504 Self {
505 store,
506 issuer,
507 skipper: Box::new(skipper),
508 in_flight: Arc::new(InFlight::default()),
509 }
510 }
511 #[inline]
513 #[must_use]
514 pub fn skipper(mut self, skipper: impl Skipper) -> Self {
515 self.skipper = Box::new(skipper);
516 self
517 }
518}
519
520async fn call_next_and_cache<S>(
521 store: &S,
522 key: S::Key,
523 req: &mut Request,
524 depot: &mut Depot,
525 res: &mut Response,
526 ctrl: &mut FlowCtrl,
527) -> Option<CachedEntry>
528where
529 S: CacheStore,
530{
531 ctrl.call_next(req, depot, res).await;
532 let cached_data = cached_response(res)?;
533 if let Err(e) = store.save_entry(key, cached_data.clone()).await {
534 tracing::error!(error = ?e, "cache failed");
535 }
536 Some(cached_data)
537}
538
539fn cached_response(res: &Response) -> Option<CachedEntry> {
540 if res.body.is_stream() || res.body.is_error() {
541 return None;
542 }
543 let headers = res.headers().clone();
544 let body = match TryInto::<CachedBody>::try_into(&res.body) {
545 Ok(body) => body,
546 Err(e) => {
547 tracing::error!(error = ?e, "cache failed");
548 return None;
549 }
550 };
551 Some(CachedEntry::new(res.status_code, headers, body))
552}
553
554#[async_trait]
555impl<S, I> Handler for Cache<S, I>
556where
557 S: CacheStore<Key = I::Key>,
558 I: CacheIssuer,
559 I::Key: Clone,
560{
561 async fn handle(
562 &self,
563 req: &mut Request,
564 depot: &mut Depot,
565 res: &mut Response,
566 ctrl: &mut FlowCtrl,
567 ) {
568 if self.skipper.skipped(req, depot) {
569 ctrl.call_next(req, depot, res).await;
570 return;
571 }
572 let Some(key) = self.issuer.issue(req, depot).await else {
573 return;
574 };
575 let Some(cache) = self.store.load_entry(&key).await else {
576 match self.in_flight.enter(key.clone()) {
577 FlightPermit::Leader(guard) => {
578 let cached_data =
579 call_next_and_cache(&self.store, key, req, depot, res, ctrl).await;
580 guard.finish(cached_data);
581 }
582 FlightPermit::Follower(flight) => {
583 flight.wait().await;
584 if let Some(cache) = flight.entry() {
585 respond_from_cache(res, cache);
586 ctrl.skip_rest();
587 } else {
588 call_next_and_cache(&self.store, key, req, depot, res, ctrl).await;
589 }
590 }
591 }
592 return;
593 };
594 respond_from_cache(res, cache);
595 ctrl.skip_rest();
596 }
597}
598
599fn respond_from_cache(res: &mut Response, cache: CachedEntry) {
600 let CachedEntry {
601 status,
602 headers,
603 body,
604 } = cache;
605 if let Some(status) = status {
606 res.status_code(status);
607 }
608 *res.headers_mut() = headers;
609 *res.body_mut() = body.into();
610}
611
612#[cfg(test)]
613mod tests {
614 use std::collections::VecDeque;
615 use std::sync::Arc;
616 use std::sync::atomic::{AtomicUsize, Ordering};
617
618 use bytes::Bytes;
619 use salvo_core::http::HeaderMap;
620 use salvo_core::prelude::*;
621 use salvo_core::test::{ResponseExt, TestClient};
622 use time::OffsetDateTime;
623
624 use super::*;
625
626 #[handler]
627 async fn cached() -> String {
628 format!(
629 "Hello World, my birth time is {}",
630 OffsetDateTime::now_utc()
631 )
632 }
633
634 #[derive(Debug)]
635 struct SlowCached {
636 calls: Arc<AtomicUsize>,
637 }
638
639 #[async_trait]
640 impl Handler for SlowCached {
641 async fn handle(
642 &self,
643 _req: &mut Request,
644 _depot: &mut Depot,
645 res: &mut Response,
646 _ctrl: &mut FlowCtrl,
647 ) {
648 let call = self.calls.fetch_add(1, Ordering::SeqCst) + 1;
649 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
650 res.render(format!("backend call {call}"));
651 }
652 }
653
654 #[tokio::test]
655 async fn test_cache() {
656 let cache = Cache::new(
657 MokaStore::builder()
658 .time_to_live(std::time::Duration::from_secs(5))
659 .build(),
660 RequestIssuer::default(),
661 );
662 let router = Router::new().hoop(cache).goal(cached);
663 let service = Service::new(router);
664
665 let mut res = TestClient::get("http://127.0.0.1:5801")
666 .send(&service)
667 .await;
668 assert_eq!(res.status_code.unwrap(), StatusCode::OK);
669
670 let content0 = res.take_string().await.unwrap();
671
672 let mut res = TestClient::get("http://127.0.0.1:5801")
673 .send(&service)
674 .await;
675 assert_eq!(res.status_code.unwrap(), StatusCode::OK);
676
677 let content1 = res.take_string().await.unwrap();
678 assert_eq!(content0, content1);
679
680 tokio::time::sleep(tokio::time::Duration::from_secs(6)).await;
681 let mut res = TestClient::post("http://127.0.0.1:5801")
682 .send(&service)
683 .await;
684 let content2 = res.take_string().await.unwrap();
685
686 assert_ne!(content0, content2);
687 }
688
689 #[test]
691 fn test_request_issuer_new() {
692 let issuer = RequestIssuer::new();
693 assert!(issuer.use_scheme);
694 assert!(issuer.use_authority);
695 assert!(issuer.use_path);
696 assert!(issuer.use_query);
697 assert!(issuer.use_method);
698 }
699
700 #[test]
701 fn test_request_issuer_default() {
702 let issuer = RequestIssuer::default();
703 assert!(issuer.use_scheme);
704 assert!(issuer.use_authority);
705 assert!(issuer.use_path);
706 assert!(issuer.use_query);
707 assert!(issuer.use_method);
708 }
709
710 #[test]
711 fn test_request_issuer_use_scheme() {
712 let issuer = RequestIssuer::new().use_scheme(false);
713 assert!(!issuer.use_scheme);
714 assert!(issuer.use_authority);
715 }
716
717 #[test]
718 fn test_request_issuer_use_authority() {
719 let issuer = RequestIssuer::new().use_authority(false);
720 assert!(issuer.use_scheme);
721 assert!(!issuer.use_authority);
722 }
723
724 #[test]
725 fn test_request_issuer_use_path() {
726 let issuer = RequestIssuer::new().use_path(false);
727 assert!(!issuer.use_path);
728 }
729
730 #[test]
731 fn test_request_issuer_use_query() {
732 let issuer = RequestIssuer::new().use_query(false);
733 assert!(!issuer.use_query);
734 }
735
736 #[test]
737 fn test_request_issuer_use_method() {
738 let issuer = RequestIssuer::new().use_method(false);
739 assert!(!issuer.use_method);
740 }
741
742 #[test]
743 fn test_request_issuer_chain() {
744 let issuer = RequestIssuer::new()
745 .use_scheme(false)
746 .use_authority(false)
747 .use_path(true)
748 .use_query(false)
749 .use_method(true);
750 assert!(!issuer.use_scheme);
751 assert!(!issuer.use_authority);
752 assert!(issuer.use_path);
753 assert!(!issuer.use_query);
754 assert!(issuer.use_method);
755 }
756
757 #[test]
758 fn test_request_issuer_debug() {
759 let issuer = RequestIssuer::new();
760 let debug_str = format!("{issuer:?}");
761 assert!(debug_str.contains("RequestIssuer"));
762 assert!(debug_str.contains("use_scheme"));
763 }
764
765 #[test]
766 fn test_request_issuer_clone() {
767 let issuer = RequestIssuer::new().use_scheme(false);
768 let cloned = issuer.clone();
769 assert_eq!(issuer.use_scheme, cloned.use_scheme);
770 assert_eq!(issuer.use_authority, cloned.use_authority);
771 }
772
773 #[test]
775 fn test_cached_body_none() {
776 let body = CachedBody::None;
777 assert_eq!(body, CachedBody::None);
778 }
779
780 #[test]
781 fn test_cached_body_once() {
782 let bytes = Bytes::from("test data");
783 let body = CachedBody::Once(bytes.clone());
784 assert_eq!(body, CachedBody::Once(bytes));
785 }
786
787 #[test]
788 fn test_cached_body_chunks() {
789 let mut chunks = VecDeque::new();
790 chunks.push_back(Bytes::from("chunk1"));
791 chunks.push_back(Bytes::from("chunk2"));
792 let body = CachedBody::Chunks(chunks.clone());
793 assert_eq!(body, CachedBody::Chunks(chunks));
794 }
795
796 #[test]
797 fn test_cached_body_try_from_res_body_none() {
798 let res_body = ResBody::None;
799 let result: Result<CachedBody, _> = (&res_body).try_into();
800 assert_eq!(result.unwrap(), CachedBody::None);
801 }
802
803 #[test]
804 fn test_cached_body_try_from_res_body_once() {
805 let bytes = Bytes::from("test");
806 let res_body = ResBody::Once(bytes.clone());
807 let result: Result<CachedBody, _> = (&res_body).try_into();
808 assert_eq!(result.unwrap(), CachedBody::Once(bytes));
809 }
810
811 #[test]
812 fn test_cached_body_try_from_res_body_chunks() {
813 let mut chunks = VecDeque::new();
814 chunks.push_back(Bytes::from("chunk1"));
815 chunks.push_back(Bytes::from("chunk2"));
816 let res_body = ResBody::Chunks(chunks.clone());
817 let result: Result<CachedBody, _> = (&res_body).try_into();
818 assert_eq!(result.unwrap(), CachedBody::Chunks(chunks));
819 }
820
821 #[test]
822 fn test_cached_body_into_res_body_none() {
823 let cb = CachedBody::None;
824 let res_body: ResBody = cb.into();
825 assert!(matches!(res_body, ResBody::None));
826 }
827
828 #[test]
829 fn test_cached_body_into_res_body_once() {
830 let bytes = Bytes::from("test");
831 let cb = CachedBody::Once(bytes.clone());
832 let res_body: ResBody = cb.into();
833 assert!(matches!(res_body, ResBody::Once(b) if b == bytes));
834 }
835
836 #[test]
837 fn test_cached_body_into_res_body_chunks() {
838 let mut chunks = VecDeque::new();
839 chunks.push_back(Bytes::from("chunk1"));
840 let cb = CachedBody::Chunks(chunks);
841 let res_body: ResBody = cb.into();
842 assert!(matches!(res_body, ResBody::Chunks(_)));
843 }
844
845 #[test]
846 fn test_cached_body_debug() {
847 let body = CachedBody::None;
848 let debug_str = format!("{body:?}");
849 assert!(debug_str.contains("None"));
850
851 let body = CachedBody::Once(Bytes::from("test"));
852 let debug_str = format!("{body:?}");
853 assert!(debug_str.contains("Once"));
854 }
855
856 #[test]
857 fn test_cached_body_clone() {
858 let body = CachedBody::Once(Bytes::from("test"));
859 let cloned = body.clone();
860 assert_eq!(body, cloned);
861 }
862
863 #[test]
865 fn test_cached_entry_new() {
866 let entry = CachedEntry::new(Some(StatusCode::OK), HeaderMap::new(), CachedBody::None);
867 assert_eq!(entry.status, Some(StatusCode::OK));
868 assert!(entry.headers.is_empty());
869 assert_eq!(entry.body, CachedBody::None);
870 }
871
872 #[test]
873 fn test_cached_entry_status() {
874 let entry = CachedEntry::new(
875 Some(StatusCode::NOT_FOUND),
876 HeaderMap::new(),
877 CachedBody::None,
878 );
879 assert_eq!(entry.status(), Some(StatusCode::NOT_FOUND));
880 }
881
882 #[test]
883 fn test_cached_entry_status_none() {
884 let entry = CachedEntry::new(None, HeaderMap::new(), CachedBody::None);
885 assert_eq!(entry.status(), None);
886 }
887
888 #[test]
889 fn test_cached_entry_headers() {
890 let mut headers = HeaderMap::new();
891 headers.insert("Content-Type", "application/json".parse().unwrap());
892 let entry = CachedEntry::new(Some(StatusCode::OK), headers.clone(), CachedBody::None);
893 assert_eq!(entry.headers().len(), 1);
894 assert!(entry.headers().contains_key("Content-Type"));
895 }
896
897 #[test]
898 fn test_cached_entry_body() {
899 let body = CachedBody::Once(Bytes::from("test body"));
900 let entry = CachedEntry::new(Some(StatusCode::OK), HeaderMap::new(), body.clone());
901 assert_eq!(entry.body(), &body);
902 }
903
904 #[test]
905 fn test_cached_entry_debug() {
906 let entry = CachedEntry::new(Some(StatusCode::OK), HeaderMap::new(), CachedBody::None);
907 let debug_str = format!("{entry:?}");
908 assert!(debug_str.contains("CachedEntry"));
909 assert!(debug_str.contains("status"));
910 }
911
912 #[test]
913 fn test_cached_entry_clone() {
914 let entry = CachedEntry::new(
915 Some(StatusCode::OK),
916 HeaderMap::new(),
917 CachedBody::Once(Bytes::from("test")),
918 );
919 let cloned = entry.clone();
920 assert_eq!(entry.status, cloned.status);
921 assert_eq!(entry.body, cloned.body);
922 }
923
924 #[test]
926 fn test_cache_new() {
927 let cache = Cache::new(MokaStore::<String>::new(100), RequestIssuer::default());
928 assert!(format!("{cache:?}").contains("Cache"));
929 }
930
931 #[test]
932 fn test_cache_debug() {
933 let cache = Cache::new(MokaStore::<String>::new(100), RequestIssuer::default());
934 let debug_str = format!("{cache:?}");
935 assert!(debug_str.contains("Cache"));
936 assert!(debug_str.contains("store"));
937 assert!(debug_str.contains("issuer"));
938 }
939
940 #[tokio::test]
941 async fn test_cache_same_path_same_content() {
942 let cache = Cache::new(
943 MokaStore::builder()
944 .time_to_live(std::time::Duration::from_secs(60))
945 .build(),
946 RequestIssuer::default(),
947 );
948 let router = Router::new().hoop(cache).goal(cached);
949 let service = Service::new(router);
950
951 let mut res1 = TestClient::get("http://127.0.0.1:5801/same-path")
952 .send(&service)
953 .await;
954 let content1 = res1.take_string().await.unwrap();
955
956 let mut res2 = TestClient::get("http://127.0.0.1:5801/same-path")
957 .send(&service)
958 .await;
959 let content2 = res2.take_string().await.unwrap();
960
961 assert_eq!(content1, content2);
963 }
964
965 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
966 async fn test_cache_coalesces_concurrent_misses() {
967 let calls = Arc::new(AtomicUsize::new(0));
968 let cache = Cache::new(
969 MokaStore::builder()
970 .time_to_live(std::time::Duration::from_secs(60))
971 .build(),
972 RequestIssuer::default(),
973 );
974 let router = Arc::new(Router::new().hoop(cache).goal(SlowCached {
975 calls: calls.clone(),
976 }));
977 let barrier = Arc::new(tokio::sync::Barrier::new(16));
978
979 let mut tasks = Vec::new();
980 for _ in 0..16 {
981 let router = router.clone();
982 let barrier = barrier.clone();
983 tasks.push(tokio::spawn(async move {
984 barrier.wait().await;
985 let mut res = TestClient::get("http://127.0.0.1:5801").send(router).await;
986 res.take_string().await.unwrap()
987 }));
988 }
989
990 let mut bodies = Vec::new();
991 for task in tasks {
992 bodies.push(task.await.unwrap());
993 }
994
995 assert_eq!(calls.load(Ordering::SeqCst), 1);
996 assert!(bodies.iter().all(|body| body == &bodies[0]));
997 }
998}