1use core::str;
2use std::{collections::HashSet, sync::Arc, time::Duration};
3
4use bytes::Bytes;
5#[cfg(test)]
6use proptest_derive::Arbitrary;
7
8use crate::{
9 body::Body,
10 component::bindings::fastly::compute::types::Error as ComponentError,
11 wiggle_abi::types::{BodyHandle, CacheOverrideTag, FastlyStatus},
12};
13
14use http::{HeaderMap, HeaderValue};
15
16mod store;
17mod variance;
18
19use store::{CacheData, CacheKeyObjects, GetBodyBuilder, ObjectMeta, Obligation};
20pub use variance::VaryRule;
21
22#[derive(Debug, thiserror::Error)]
23#[non_exhaustive]
24pub enum Error {
25 #[error("invalid key")]
26 InvalidKey,
27
28 #[error("handle is not writeable")]
29 CannotWrite,
30
31 #[error("no entry for key in cache")]
32 Missing,
33
34 #[error("invalid argument: {0}")]
35 InvalidArgument(&'static str),
36
37 #[error("cache entry's body is currently being read by another body")]
38 HandleBodyUsed,
39
40 #[error("cache entry is not revalidatable")]
41 NotRevalidatable,
42}
43
44impl From<Error> for crate::Error {
45 fn from(value: Error) -> Self {
46 crate::Error::CacheError(value)
47 }
48}
49
50impl From<&Error> for FastlyStatus {
51 fn from(value: &Error) -> Self {
52 match value {
53 Error::InvalidKey => FastlyStatus::Inval,
56 Error::InvalidArgument(_) => FastlyStatus::Inval,
57 Error::CannotWrite => FastlyStatus::Badf,
58 Error::Missing => FastlyStatus::None,
59 Error::HandleBodyUsed => FastlyStatus::Badf,
60 Error::NotRevalidatable => FastlyStatus::Badf,
61 }
62 }
63}
64
65impl From<Error> for ComponentError {
66 fn from(value: Error) -> Self {
67 match value {
68 Error::InvalidKey => ComponentError::InvalidArgument,
71 Error::InvalidArgument(_) => ComponentError::InvalidArgument,
72 Error::CannotWrite => ComponentError::AuxiliaryError,
73 Error::Missing => ComponentError::CannotRead,
74 Error::HandleBodyUsed => ComponentError::AuxiliaryError,
75 Error::NotRevalidatable => ComponentError::AuxiliaryError,
76 }
77 }
78}
79
80#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
82#[cfg_attr(test, derive(Arbitrary))]
83pub struct CacheKey(
84 #[cfg_attr(test, proptest(filter = "|f| f.len() <= CacheKey::MAX_LENGTH"))] Vec<u8>,
85);
86
87impl CacheKey {
88 pub const MAX_LENGTH: usize = 4096;
90}
91
92impl TryFrom<&Vec<u8>> for CacheKey {
93 type Error = Error;
94
95 fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
96 value.as_slice().try_into()
97 }
98}
99
100impl TryFrom<Vec<u8>> for CacheKey {
101 type Error = Error;
102
103 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
104 if value.len() > Self::MAX_LENGTH {
105 Err(Error::InvalidKey)
106 } else {
107 Ok(CacheKey(value))
108 }
109 }
110}
111
112impl TryFrom<&[u8]> for CacheKey {
113 type Error = Error;
114
115 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
116 if value.len() > CacheKey::MAX_LENGTH {
117 Err(Error::InvalidKey)
118 } else {
119 Ok(CacheKey(value.to_owned()))
120 }
121 }
122}
123
124impl TryFrom<&str> for CacheKey {
125 type Error = Error;
126
127 fn try_from(value: &str) -> Result<Self, Self::Error> {
128 value.as_bytes().try_into()
129 }
130}
131
132#[derive(Debug)]
134pub struct CacheEntry {
135 key: CacheKey,
136 found: Option<Found>,
137 go_get: Option<Obligation>,
138
139 always_use_requested_range: bool,
149}
150
151impl CacheEntry {
152 pub fn with_always_use_requested_range(self, always_use_requested_range: bool) -> Self {
155 Self {
156 always_use_requested_range,
157 ..self
158 }
159 }
160
161 pub fn stub(&self) -> CacheEntry {
163 Self {
164 key: self.key.clone(),
165 found: None,
166 go_get: None,
167 always_use_requested_range: false,
168 }
169 }
170
171 pub fn key(&self) -> &CacheKey {
173 &self.key
174 }
175 pub fn found(&self) -> Option<&Found> {
177 self.found.as_ref()
178 }
179
180 pub fn found_mut(&mut self) -> Option<&mut Found> {
182 self.found.as_mut()
183 }
184
185 pub fn go_get(&self) -> Option<&Obligation> {
187 self.go_get.as_ref()
188 }
189
190 pub fn cancel(&mut self) -> bool {
193 self.go_get.take().is_some()
194 }
195
196 pub async fn body(&self, from: Option<u64>, to: Option<u64>) -> Result<Body, crate::Error> {
198 let found = self
199 .found
200 .as_ref()
201 .ok_or(crate::Error::CacheError(Error::Missing))?;
202 found
203 .get_body()
204 .with_range(from, to)
205 .with_always_use_requested_range(self.always_use_requested_range)
206 .build()
207 .await
208 }
209
210 pub fn insert(
214 &mut self,
215 options: WriteOptions,
216 body: Body,
217 ) -> Result<CacheEntry, crate::Error> {
218 let go_get = self.go_get.take().ok_or(Error::NotRevalidatable)?;
219 let found = go_get.insert(options, body);
220 Ok(CacheEntry {
221 key: self.key.clone(),
222 found: Some(found),
223 go_get: None,
224 always_use_requested_range: self.always_use_requested_range,
225 })
226 }
227
228 pub async fn update(&mut self, options: WriteOptions) -> Result<(), crate::Error> {
231 let go_get = self.go_get.take().ok_or(Error::NotRevalidatable)?;
232 match go_get.update(options).await {
233 Ok(()) => Ok(()),
234 Err((go_get, err)) => {
235 self.go_get = Some(go_get);
237 Err(err)
238 }
239 }
240 }
241}
242
243#[derive(Debug)]
245pub struct Found {
246 data: CacheData,
247
248 pub last_body_handle: Option<BodyHandle>,
255}
256
257impl From<CacheData> for Found {
258 fn from(data: CacheData) -> Self {
259 Found {
260 data,
261 last_body_handle: None,
262 }
263 }
264}
265
266impl Found {
267 fn get_body(&self) -> GetBodyBuilder<'_> {
268 self.data.body()
269 }
270
271 pub fn meta(&self) -> &ObjectMeta {
273 self.data.get_meta()
274 }
275
276 pub fn length(&self) -> Option<u64> {
278 self.data.get_meta().length()
279 }
280}
281
282pub struct Cache {
289 inner: moka::future::Cache<CacheKey, Arc<CacheKeyObjects>>,
290}
291
292impl Default for Cache {
293 fn default() -> Self {
294 let inner = moka::future::Cache::builder()
297 .eviction_listener(|key, _value, cause| {
298 tracing::info!("cache eviction of {key:?}: {cause:?}")
299 })
300 .build();
301 Cache { inner }
302 }
303}
304
305impl Cache {
306 pub async fn lookup(&self, key: &CacheKey, headers: &HeaderMap) -> CacheEntry {
308 let found = self
309 .inner
310 .get_with_by_ref(key, async { Default::default() })
311 .await
312 .get(headers)
313 .map(|data| Found::from((*data).clone()));
314 CacheEntry {
315 key: key.clone(),
316 found,
317 go_get: None,
318 always_use_requested_range: false,
319 }
320 }
321
322 pub async fn transaction_lookup(
324 &self,
325 key: &CacheKey,
326 headers: &HeaderMap,
327 ok_to_wait: bool,
328 ) -> CacheEntry {
329 let (found, obligation) = self
330 .inner
331 .get_with_by_ref(key, async { Default::default() })
332 .await
333 .transaction_get(headers, ok_to_wait)
334 .await;
335 CacheEntry {
336 key: key.clone(),
337 found: found.map(|v| Found::from((*v).clone())),
338 go_get: obligation,
339 always_use_requested_range: false,
340 }
341 }
342
343 pub async fn insert(
347 &self,
348 key: &CacheKey,
349 request_headers: HeaderMap,
350 options: WriteOptions,
351 body: Body,
352 ) {
353 self.inner
354 .get_with_by_ref(key, async { Default::default() })
355 .await
356 .insert(request_headers, options, body, None);
357 }
358
359 pub fn purge(&self, key: SurrogateKey, soft_purge: bool) -> usize {
365 self.inner
366 .iter()
367 .map(|(_, entry)| entry.purge(&key, soft_purge))
368 .sum()
369 }
370}
371
372#[derive(Default, Clone)]
374pub struct WriteOptions {
375 pub max_age: Duration,
376 pub initial_age: Duration,
377 pub stale_while_revalidate: Duration,
378 pub vary_rule: VaryRule,
379 pub user_metadata: Bytes,
380 pub length: Option<u64>,
381 pub sensitive_data: bool,
382 pub edge_max_age: Duration,
383 pub surrogate_keys: SurrogateKeySet,
384}
385
386impl WriteOptions {
387 pub fn new(max_age: Duration) -> Self {
388 WriteOptions {
389 max_age,
390 initial_age: Duration::ZERO,
391 stale_while_revalidate: Duration::ZERO,
392 vary_rule: VaryRule::default(),
393 user_metadata: Bytes::new(),
394 length: None,
395 sensitive_data: false,
396 edge_max_age: max_age,
397 surrogate_keys: Default::default(),
398 }
399 }
400}
401
402#[derive(Clone, Debug, Default)]
404pub enum CacheOverride {
405 #[default]
407 None,
408 Pass,
410 Override {
414 ttl: Option<u32>,
415 stale_while_revalidate: Option<u32>,
416 pci: bool,
417 surrogate_key: Option<HeaderValue>,
418 },
419}
420
421impl CacheOverride {
422 pub fn is_pass(&self) -> bool {
423 matches!(self, Self::Pass)
424 }
425
426 pub fn from_abi(
431 tag: u32,
432 ttl: u32,
433 swr: u32,
434 surrogate_key: Option<HeaderValue>,
435 ) -> Option<Self> {
436 CacheOverrideTag::from_bits(tag).map(|tag| {
437 if tag.contains(CacheOverrideTag::PASS) {
438 return CacheOverride::Pass;
439 }
440 if tag.is_empty() && surrogate_key.is_none() {
441 return CacheOverride::None;
442 }
443 let ttl = if tag.contains(CacheOverrideTag::TTL) {
444 Some(ttl)
445 } else {
446 None
447 };
448 let stale_while_revalidate = if tag.contains(CacheOverrideTag::STALE_WHILE_REVALIDATE) {
449 Some(swr)
450 } else {
451 None
452 };
453 let pci = tag.contains(CacheOverrideTag::PCI);
454 CacheOverride::Override {
455 ttl,
456 stale_while_revalidate,
457 pci,
458 surrogate_key,
459 }
460 })
461 }
462}
463
464const MAX_SURROGATE_KEYS_LENGTH: usize = 16 * 1024;
466const MAX_SURROGATE_KEY_LENGTH: usize = 1024;
468
469#[derive(Debug, Default, Clone)]
470pub struct SurrogateKeySet(HashSet<SurrogateKey>);
471
472impl std::fmt::Display for SurrogateKeySet {
473 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
474 write!(f, "{{")?;
475 for (i, item) in self.0.iter().enumerate() {
476 if i == 0 {
477 write!(f, "{item}")?;
478 } else {
479 write!(f, " {item}")?;
480 }
481 }
482 write!(f, "}}")
483 }
484}
485
486impl TryFrom<&[u8]> for SurrogateKeySet {
487 type Error = crate::Error;
488
489 fn try_from(s: &[u8]) -> Result<Self, Self::Error> {
490 if s.len() > MAX_SURROGATE_KEYS_LENGTH {
491 return Err(
492 Error::InvalidArgument("surrogate key set exceeds maximum length (16Ki)").into(),
493 );
494 }
495 let result: Result<HashSet<_>, _> = s
496 .split(|c| *c == b' ')
497 .filter(|sk| !sk.is_empty())
498 .map(SurrogateKey::try_from)
499 .collect();
500 Ok(SurrogateKeySet(result?))
501 }
502}
503
504impl std::str::FromStr for SurrogateKeySet {
505 type Err = crate::Error;
506
507 fn from_str(s: &str) -> Result<Self, Self::Err> {
508 s.as_bytes().try_into()
509 }
510}
511
512#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone)]
514pub struct SurrogateKey(String);
515
516impl std::fmt::Display for SurrogateKey {
517 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
518 self.0.fmt(f)
519 }
520}
521
522impl TryFrom<&[u8]> for SurrogateKey {
523 type Error = Error;
524
525 fn try_from(b: &[u8]) -> Result<Self, Self::Error> {
526 if b.len() > MAX_SURROGATE_KEY_LENGTH {
527 return Err(Error::InvalidArgument(
528 "surrogate key exceeds maximum length (1024)",
529 ));
530 }
531
532 if !b.iter().all(|c| c.is_ascii_graphic()) {
533 return Err(Error::InvalidArgument(
534 "surrogate key contains characters other than graphical ASCII",
535 ));
536 }
537 let s = unsafe {
538 str::from_utf8_unchecked(b)
540 };
541 Ok(SurrogateKey(s.to_owned()))
542 }
543}
544
545impl std::str::FromStr for SurrogateKey {
546 type Err = Error;
547
548 fn from_str(s: &str) -> Result<Self, Self::Err> {
549 s.as_bytes().try_into()
550 }
551}
552
553#[cfg(test)]
554mod tests {
555 use std::rc::Rc;
556
557 use http::HeaderName;
558 use proptest::prelude::*;
559
560 use super::*;
561
562 proptest! {
563 #[test]
564 fn reject_cache_key_too_long(l in 4097usize..5000) {
565 let mut v : Vec<u8> = Vec::new();
566 v.resize(l, 0);
567 CacheKey::try_from(&v).unwrap_err();
568 }
569 }
570
571 proptest! {
572 #[test]
573 fn accept_valid_cache_key_len(l in 0usize..4096) {
574 let mut v : Vec<u8> = Vec::new();
575 v.resize(l, 0);
576 let _ = CacheKey::try_from(&v).unwrap();
577 }
578 }
579
580 proptest! {
581 #[test]
582 fn nontransactional_insert_lookup(
583 key in any::<CacheKey>(),
584 max_age in any::<u32>(),
585 initial_age in any::<u32>(),
586 value in any::<Vec<u8>>()) {
587 let cache = Cache::default();
588
589 let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
592 rt.block_on(async {
593 let empty = cache.lookup(&key, &HeaderMap::default()).await;
594 assert!(empty.found().is_none());
595 assert!(empty.go_get().is_none());
597
598 let write_options = WriteOptions {
599 max_age: Duration::from_secs(max_age as u64),
600 initial_age: Duration::from_secs(initial_age as u64),
601 ..Default::default()
602 };
603
604 cache.insert(&key, HeaderMap::default(), write_options, value.clone().into()).await;
605
606 let nonempty = cache.lookup(&key, &HeaderMap::default()).await;
607 let found = nonempty.found().expect("should have found inserted key");
608 let got = found.get_body().build().await.unwrap().read_into_vec().await.unwrap();
609 assert_eq!(got, value);
610 });
611 }
612 }
613
614 #[test]
615 fn parse_surrogate_keys() {
616 let keys: SurrogateKeySet = "keyA keyB".parse().unwrap();
617 let key_a: SurrogateKey = "keyA".parse().unwrap();
618 let key_b: SurrogateKey = "keyB".parse().unwrap();
619 assert_eq!(keys.0.len(), 2);
620 assert!(keys.0.contains(&key_a));
621 assert!(keys.0.contains(&key_b));
622 }
623
624 #[tokio::test]
625 async fn insert_immediately_stale() {
626 let cache = Cache::default();
627 let key = ([1u8].as_slice()).try_into().unwrap();
628
629 let write_options = WriteOptions {
631 max_age: Duration::from_secs(1),
632 initial_age: Duration::from_secs(2),
633 ..Default::default()
634 };
635
636 let mut body = Body::empty();
637 body.push_back([1u8].as_slice());
638
639 cache
640 .insert(&key, HeaderMap::default(), write_options, body)
641 .await;
642
643 let nonempty = cache.lookup(&key, &HeaderMap::default()).await;
644 let found = nonempty.found().expect("should have found inserted key");
645 assert!(!found.meta().is_fresh());
646 }
647
648 #[tokio::test]
649 async fn test_vary() {
650 let cache = Cache::default();
651 let key = ([1u8].as_slice()).try_into().unwrap();
652
653 let header_name = HeaderName::from_static("x-viceroy-test");
654 let request_headers: HeaderMap = [(header_name.clone(), HeaderValue::from_static("test"))]
655 .into_iter()
656 .collect();
657
658 let write_options = WriteOptions {
659 max_age: Duration::from_secs(100),
660 vary_rule: VaryRule::new([&header_name].into_iter()),
661 ..Default::default()
662 };
663 let body = Body::empty();
664 cache
665 .insert(&key, request_headers.clone(), write_options, body)
666 .await;
667
668 let empty_headers = cache.lookup(&key, &HeaderMap::default()).await;
669 assert!(empty_headers.found().is_none());
670
671 let matched_headers = cache.lookup(&key, &request_headers).await;
672 assert!(matched_headers.found.is_some());
673
674 let r2_headers: HeaderMap = [(header_name.clone(), HeaderValue::from_static("assert"))]
675 .into_iter()
676 .collect();
677 let mismatched_headers = cache.lookup(&key, &r2_headers).await;
678 assert!(mismatched_headers.found.is_none());
679 }
680
681 #[tokio::test]
682 async fn insert_stale_while_revalidate() {
683 let cache = Cache::default();
684 let key = ([1u8].as_slice()).try_into().unwrap();
685
686 let write_options = WriteOptions {
688 max_age: Duration::from_secs(1),
689 initial_age: Duration::from_secs(2),
690 stale_while_revalidate: Duration::from_secs(10),
691 ..Default::default()
692 };
693
694 let mut body = Body::empty();
695 body.push_back([1u8].as_slice());
696 cache
697 .insert(&key, HeaderMap::default(), write_options, body)
698 .await;
699
700 let nonempty = cache.lookup(&key, &HeaderMap::default()).await;
701 let found = nonempty.found().expect("should have found inserted key");
702 assert!(!found.meta().is_fresh());
703 assert!(found.meta().is_usable());
704 }
705
706 #[tokio::test]
707 async fn insert_and_revalidate() {
708 let cache = Cache::default();
709 let key = ([1u8].as_slice()).try_into().unwrap();
710
711 let write_options = WriteOptions {
713 max_age: Duration::from_secs(1),
714 initial_age: Duration::from_secs(2),
715 stale_while_revalidate: Duration::from_secs(10),
716 ..Default::default()
717 };
718
719 let mut body = Body::empty();
720 body.push_back([1u8].as_slice());
721 cache
722 .insert(&key, HeaderMap::default(), write_options, body)
723 .await;
724
725 let mut txn1 = cache
726 .transaction_lookup(&key, &HeaderMap::default(), true)
727 .await;
728 let found = txn1.found().expect("should have found inserted key");
729 assert!(!found.meta().is_fresh());
730 assert!(found.meta().is_usable());
731
732 assert!(txn1.go_get().is_some());
733
734 {
736 let txn2 = cache
737 .transaction_lookup(&key, &HeaderMap::default(), true)
738 .await;
739 let found = txn2.found().expect("should have found inserted key");
740 assert!(!found.meta().is_fresh());
741 assert!(found.meta().is_usable());
742 assert!(txn2.go_get().is_none());
743 }
744
745 txn1.update(WriteOptions {
746 max_age: Duration::from_secs(10),
747 stale_while_revalidate: Duration::from_secs(10),
748 ..WriteOptions::default()
749 })
750 .await
751 .unwrap();
752
753 let txn3 = cache
755 .transaction_lookup(&key, &HeaderMap::default(), true)
756 .await;
757 assert!(txn3.go_get().is_none());
758 let found = txn3.found().expect("should find updated entry");
759 assert!(found.meta().is_usable());
760 assert!(found.meta().is_fresh());
761 }
762
763 #[tokio::test]
764 async fn cannot_revalidate_first_insert() {
765 let cache = Cache::default();
766 let key = ([1u8].as_slice()).try_into().unwrap();
767
768 let mut txn1 = cache
769 .transaction_lookup(&key, &HeaderMap::default(), false)
770 .await;
771 assert!(txn1.found().is_none());
772 let opts = WriteOptions {
773 max_age: Duration::from_secs(10),
774 stale_while_revalidate: Duration::from_secs(10),
775 ..WriteOptions::default()
776 };
777 txn1.update(opts.clone()).await.unwrap_err();
778
779 txn1.insert(opts.clone(), Body::empty()).unwrap();
781 }
782
783 #[tokio::test]
784 async fn purge_by_surrogate_key() {
785 let cache = Cache::default();
786 let key: CacheKey = ([1u8].as_slice()).try_into().unwrap();
787
788 let surrogate_keys: SurrogateKeySet = "one two three".parse().unwrap();
789 let opts = WriteOptions {
790 max_age: Duration::from_secs(100),
791 surrogate_keys: surrogate_keys.clone(),
792 ..WriteOptions::default()
793 };
794
795 cache
796 .insert(&key, HeaderMap::default(), opts.clone(), Body::empty())
797 .await;
798 let result = cache.lookup(&key, &HeaderMap::default()).await;
799 assert!(result.found().is_some());
800
801 assert_eq!(cache.purge("two".parse().unwrap(), false), 1);
802 let result = cache.lookup(&key, &HeaderMap::default()).await;
803 assert!(result.found().is_none());
804 }
805
806 #[tokio::test]
807 async fn purge_variant_by_surrogate_key() {
808 let cache = Rc::new(Cache::default());
809 let key: CacheKey = ([1u8].as_slice()).try_into().unwrap();
810
811 let vary_header = HeaderName::from_static("opus-9");
814 let insert = {
815 let cache = cache.clone();
816 let key = key.clone();
817
818 move |surrogate_keys: &str, variation: &str| {
819 let mut headers = HeaderMap::new();
820 headers.insert(vary_header.clone(), variation.try_into().unwrap());
821 let surrogate_keys: SurrogateKeySet = surrogate_keys.parse().unwrap();
822 let opts = WriteOptions {
823 max_age: Duration::from_secs(100),
824 surrogate_keys: surrogate_keys.clone(),
825 vary_rule: VaryRule::new([&vary_header]),
826 ..WriteOptions::default()
827 };
828 let cache = cache.clone();
829 let key = key.clone();
830 async move {
831 cache
832 .insert(&key, headers.clone(), opts.clone(), Body::empty())
833 .await;
834 headers
835 }
836 }
837 };
838
839 let h1 = insert("one two three", "twinkle twinkle").await;
840 let h2 = insert("one three", "abcdefg").await;
841 assert!(cache.lookup(&key, &h1).await.found().is_some());
842 assert!(cache.lookup(&key, &h2).await.found().is_some());
843
844 assert_eq!(cache.purge("two".parse().unwrap(), false), 1);
845 assert!(cache.lookup(&key, &h1).await.found().is_none());
846 assert!(cache.lookup(&key, &h2).await.found().is_some());
847 }
848
849 #[tokio::test]
850 async fn soft_purge() {
851 let cache = Cache::default();
852 let key: CacheKey = ([1u8].as_slice()).try_into().unwrap();
853
854 let surrogate_keys: SurrogateKeySet = "one two three".parse().unwrap();
855 let opts = WriteOptions {
856 max_age: Duration::from_secs(100),
857 surrogate_keys: surrogate_keys.clone(),
858 ..WriteOptions::default()
859 };
860
861 cache
862 .insert(&key, HeaderMap::default(), opts.clone(), Body::empty())
863 .await;
864 let result = cache.lookup(&key, &HeaderMap::default()).await;
865 assert!(result.found().is_some());
866
867 assert_eq!(cache.purge("two".parse().unwrap(), true), 1);
868 let result = cache
869 .transaction_lookup(&key, &HeaderMap::default(), false)
870 .await;
871 let found = result.found().unwrap();
872 assert!(!found.meta().is_fresh());
873 assert!(found.meta().is_usable());
874 assert!(result.go_get().is_some());
875 }
876}