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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
//! Process-wide ceiling for the page render caches.
//!
//! Rendering a page memoises what it decoded — the wavelet background, the JB2
//! mask, the converted RGB pixmaps, the composited tiles (see
//! [`crate::djvu_render::PageLayers`]). That makes the second render of a page
//! nearly free, and it is why a viewer can pan and zoom without re-decoding.
//!
//! Until 0.33 nothing bounded it. The eviction API
//! ([`crate::DjVuDocument::enforce_cache_budget`] and friends) needed
//! `&mut DjVuDocument`, and every render entry point holds a shared `&DjVuPage`
//! — so a program that only rendered could never shrink what it grew. A sweep
//! through a colour book cost about 5.3 MB per page and never gave any of it
//! back.
//!
//! This module closes that: every page cache registers itself here, every cache
//! fill reports its new size, and when the total goes over
//! [`budget`] the least-recently-used layers are dropped until it is under
//! again. The default ceiling is [`DEFAULT_BUDGET`]; set your own with
//! [`set_budget`], or lift it entirely with `set_budget(usize::MAX)`.
//!
//! The unit of eviction is a *layer*, not a page (#813): each decoded
//! background, mask, converted pixmap and tile store carries its own last-used
//! tick and size, and a sweep ranks them across every live page. A page whose
//! mask was just used keeps its mask while its stale background goes. Only the
//! layer being filled at that moment is protected, so the resident total can
//! exceed the ceiling by at most one layer. (Until this change the sweep
//! dropped whole pages and protected the whole page being rendered, so the
//! overshoot was a page's cache and a warm layer went with its cold
//! neighbours.)
//!
//! Eviction is safe at any moment. A cached layer is handed to a render as a
//! shared handle, so dropping the cache's own handle mid-render only means the
//! render finishes with the copy it already holds. A render in progress
//! therefore holds the layers it has already fetched whether or not the cache
//! still does; that memory is the render's, not the cache's, and is not part
//! of the resident total.
//!
//! The ceiling is process-wide on purpose: memory is a process-wide resource,
//! and a page does not know which document it belongs to. Per-document control
//! is still available through [`crate::DjVuDocument::enforce_cache_budget`],
//! which stays page-granular.
//!
//! See PERF_EXPERIMENTS.md READ_CACHE_BOUNDED and RENDER_CACHE_LAYER_EVICT.
use ;
use ;
use crate;
/// The ceiling applied when the program sets none: 256 MiB.
///
/// Large enough that a viewer keeps a working set of full-resolution pages
/// warm, small enough that a batch sweep over a long book does not grow without
/// limit.
pub const DEFAULT_BUDGET: usize = 256 * 1024 * 1024;
/// The current ceiling. `usize::MAX` means "no ceiling".
static BUDGET: AtomicUsize = new;
/// Bytes held by every registered page cache, kept current by
/// `PageLayers::report_bytes` rather than re-measured on each fill.
static RESIDENT: AtomicUsize = new;
/// Every live page cache, weakly held so a dropped page needs no unregister
/// step. Dead entries are pruned by the next sweep.
static REGISTRY: = new;
/// The ceiling on the total resident bytes of all page render caches.
/// Set the ceiling. Pass `usize::MAX` to render without one (the behaviour of
/// 0.32 and earlier).
///
/// The new ceiling is applied immediately: if the caches are already over it,
/// this sweeps. Returns the bytes freed.
/// Approximate resident bytes held by every page render cache in this process.
/// Drop least-recently-used cached layers until the total is at most
/// [`budget`]. Returns the bytes freed; 0 when already under the ceiling.
///
/// Renders call this on their own. Call it directly after freeing documents, or
/// at a moment of your choosing in a memory-sensitive program.
/// Drop every registered page cache, whatever the ceiling. Returns bytes freed.
///
/// Intended for tests and for a program that wants a known-cold starting point;
/// the caches rebuild lazily and identically.
/// The registry length at which `register` compacts away dead entries.
static PRUNE_AT: AtomicUsize = new;
/// Register a newly created page cache. Called once per page, on the first
/// access to its cache.
pub
/// Fold a cache's size change into the process-wide total; returns the total.
pub
/// The hot-path check: sweep only when `total` is already over the ceiling.
///
/// `keep` identifies the layer the caller is filling right now (see
/// `PageLayers::layer_id`). It is never evicted — evicting it would drop the
/// value the caller is about to return and guarantee a re-decode on the very
/// next access. Every other layer, on the same page or another, is fair game.
pub
/// Snapshot the live caches, pruning entries whose page is gone.
/// Evict least-recently-used layers, across all pages, until the total is
/// within the ceiling.