1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
use crate;
use Bytes;
use DashMap;
use ;
/// A server reply as a [`CacheStore`] holds it: opaque, cheap to clone, and
/// already detached from the network buffers it was decoded from.
///
/// A store keeps it and hands it back; it cannot read it. The bytes stay inside
/// the crate because handing them out is what would pin a recycled read buffer
/// for as long as the cache holds the entry. So a store can live anywhere in the
/// process — shared between clients, with an eviction policy of its own, counted
/// or instrumented — but cannot serialize an entry to disk or to another
/// machine.
;
/// Where a [`Cache`](crate::cache::Cache) keeps what it has already read.
///
/// The default store is [`MokaStore`], an in-process cache with a TTL and a
/// capacity. Implement this to back the cache with something else: a store
/// shared by several clients, one with an eviction policy of its own, one that
/// counts its hits, or one that survives the process.
///
/// # The two levels
///
/// An entry is addressed by a pair: the Redis `key`, which is what an
/// invalidation names, and a `subkey` identifying the exact command that read
/// it — `GET k` and `GETRANGE k 0 3` are different values of the same key. So a
/// store must be able to drop every subkey of one key at once, which is what
/// [`invalidate`](Self::invalidate) does; how it lays that out is its own
/// business.
///
/// # What the cache guarantees
///
/// Every method is called from the caller's task, except
/// [`invalidate`](Self::invalidate) and [`invalidate_all`](Self::invalidate_all),
/// which also run on the task reading the server's invalidation pushes. So a
/// store must be `Sync` and must not block.
///
/// Nothing here returns an error: a local cache that cannot answer answers
/// [`None`] and the value is read from the server instead. A store that cannot
/// *invalidate*, however, would serve stale data for good — one that can fail
/// that way must drop everything rather than return quietly.
///
/// Staleness is not the store's problem otherwise: the cache re-checks the
/// invalidation generation after every insert and removes what raced it.
///
/// # Writing one
///
/// Implement the three async methods with plain `async fn`; the
/// `-> impl Future + Send` shape is what the declarations need to say, and an
/// `async fn` in the impl satisfies it. Nothing is boxed: a hit is the path this
/// whole feature exists to make fast, and [`Cache`](crate::cache::Cache) is
/// generic over the store rather than holding a `dyn`, which is what lets the
/// futures stay unboxed.
/// The sub-cache [`MokaStore`] keeps under one Redis key: one entry per
/// distinct command read from it.
type SubCache = ;
type MokaCache = Cache;
/// A [`CacheStore`] builder over [`moka`], the default store.
pub type MokaStoreBuilder = CacheBuilder;
/// The default [`CacheStore`]: an in-process [`moka`] cache with a TTL and a
/// capacity, holding one sub-map per Redis key.
///
/// The two levels are what make an invalidation one `moka` removal rather than a
/// scan: the server names a key, and every command that read it goes with it.
;