tor_memquota/lib.rs
1#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![cfg_attr(not(feature = "memquota"), allow(unused))]
4
5//! ## Intended behaviour
6//!
7//! In normal operation we try to track as little state as possible, cheaply.
8//! We do track total memory use in nominal bytes
9//! (but a little approximately).
10//!
11//! When we exceed the quota, we engage a more expensive algorithm:
12//! we build a heap to select oldest victims, and
13//! we use the heap to keep reducing memory
14//! until we go below a low-water mark (hysteresis).
15//!
16//! ## Key concepts
17//!
18//! * **Tracker**:
19//! Instance of the memory quota system
20//! Each tracker has a notion of how much memory its participants
21//! are allowed to use, in aggregate.
22//! Tracks memory usage by all the Accounts and Participants.
23//! Different Trackers are completely independent.
24//!
25//! * **Account**:
26//! all memory used within the same Account is treated equally,
27//! and reclamation also happens on an account-by-account basis.
28//! (Each Account is with one Tracker.)
29//!
30//! See [the `mq_queue` docs](mq_queue/index.html#use-in-arti)
31//! for we use Accounts in Arti to track memory for the various queues.
32//!
33//! * **Participant**:
34//! one data structure that uses memory.
35//! Each Participant is linked to *one* Account. An account has *one or more* Participants.
36//! (An Account can exist with zero Participants, but can't then claim memory.)
37//! A Participant provides a `dyn IsParticipant` to the memory system;
38//! in turn, the memory system provides the Participant with a `Participation` -
39//! a handle for tracking memory alloc/free.
40//!
41//! Actual memory allocation is handled by the participant itself,
42//! using the global heap:
43//! for each allocation, the Participant *both*
44//! calls [`claim`](mtracker::Participation::claim)
45//! *and* allocates the actual object,
46//! and later, *both* frees the actual object *and*
47//! calls [`release`](mtracker::Participation::release).
48//!
49//! * **Child Account**/**Parent Account**:
50//! An Account may have a Parent.
51//! When a tracker requests memory reclamation from a Parent,
52//! it will also request it of all that Parent's Children (but not vice versa).
53//!
54//! The account structure and reclamation strategy for Arti is defined in
55//! `tor-proto`, and documented in `tor_proto::memquota`.
56//!
57//! * **Data age**:
58//! Each Participant must be able to say what the oldest data is, that it is storing.
59//! The reclamation policy is to try to free the oldest data.
60//!
61//! * **Reclamation**:
62//! When a Tracker decides that too much memory is being used,
63//! it will select a victim Account based on the data age.
64//! It will then ask *every Participant* in that Account,
65//! and every Participant in every Child of that Account,
66//! to reclaim memory.
67//! A Participant responds by freeing at least some memory,
68//! according to the reclamation request, and tells the Tracker when it has done so.
69//!
70//! * **Reclamation strategy**:
71//! To avoid too-frequent reclamation, once reclamation has started,
72//! it will continue until a low-water mark is reached, significantly lower than the quota.
73//! I.e. the system has a hysteresis.
74//!
75//! The only currently implemented higher-level Participant is
76//! [`mq_queue`], a queue which responds to a reclamation request
77//! by completely destroying itself, freeing all its data,
78//! and reporting it has been closed.
79//!
80//! * <div id="is-approximate">
81//!
82//! **Approximate** (both in time and space):
83//! The memory quota system is not completely precise.
84//! Participants need not report their use precisely,
85//! but the errors should be reasonably small, and bounded.
86//! Likewise, the enforcement is not precise:
87//! reclamation may start slightly too early, or too late;
88//! but the memory use will be bounded below by O(number of participants)
89//! and above by O(1) (plus errors from the participants).
90//! Reclamation is not immediate, and is dependent on task scheduling;
91//! during memory pressure the quota may be exceeded;
92//! new allocations are not prevented while attempts at reclamation are ongoing.
93//!
94//! </div>
95//!
96// TODO we haven't implemented the queue wrapper yet
97// ! * **Queues**:
98// ! We provide a higher-level API that wraps an mpsc queue and turns it into a Participant.
99// !
100//! ## Ownership and Arc keeping-alive
101//!
102//! * Somewhere, someone must keep an `Account` to keep the account open.
103//! Ie, the principal object corresponding to the accountholder should contain an `Account`.
104//!
105//! * `Arc<MemoryTracker>` holds `Weak<dyn IsParticipant>`.
106//! If the tracker finds the `IsParticipant` has vanished,
107//! it assumes this means that the Participant is being destroyed and
108//! it can treat all of the memory it claimed as freed.
109//!
110//! * Each participant holds a `Participation`.
111//! A `Participation` may be invalidated by collapse of the underlying Account,
112//! which may be triggered in any number of ways.
113//!
114//! * A `Participation` does *not* keep its `Account` alive.
115//! Ie, it has only a weak reference to the Account.
116//!
117//! * A Participant's implementor of `IsParticipant` may hold a `Participation`.
118//! If the `impl IsParticipant` is also the principal accountholder object,
119//! it must hold an `Account` too.
120//!
121//! * Child/parent accounts do not imply any keeping-alive relationship.
122//! It's just that a reclamation request to a parent (if it still exists)
123//! will also be made to its children.
124//!
125//!
126//! ```text
127//! accountholder =======================================>* Participant
128//! (impl IsParticipant)
129//! ||
130//! || ^ ||
131//! || | ||
132//! || global Weak<dyn>| ||
133//! || || | ||
134//! \/* \/ | ||
135//! | ||
136//! Account *===========> MemoryTracker ------------------' ||
137//! ||
138//! ^ ||
139//! | \/
140//! |
141//! `-------------------------------------------------* Participation
142//!
143//!
144//!
145//! accountholder which is also directly the Participant ==============\
146//! (impl IsParticipant) ||
147//! ^ ||
148//! || | ||
149//! || | ||
150//! || global |Weak<dyn> ||
151//! || || | ||
152//! \/ \/ | ||
153//! ||
154//! Account *===========> MemoryTracker ||
155//! ||
156//! ^ ||
157//! | \/
158//! |
159//! `-------------------------------------------------* Participation
160//!
161//! ```
162//!
163//! ## Panics and state corruption
164//!
165//! This library is intended to be entirely panic-free,
166//! even in the case of accounting errors, arithmetic overflow, etc.
167//!
168//! In the case of sufficiently bad account errors,
169//! a Participant, or a whole Account, or the whole MemoryQuotaTracker,
170//! may become unusable,
171//! in which case methods will return errors with kind [`tor_error::ErrorKind::Internal`].
172//
173// TODO MEMQUOTA: We ought to account for the fixed overhead of each stream, circuit, and
174// channel. For example, DataWriterImpl holds a substantial fixed-length buffer. A
175// complication is that we want to know the "data age", which is possibly the time this stream
176// was last used.
177
178// @@ begin lint list maintained by maint/add_warning @@
179#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
180#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
181#![warn(missing_docs)]
182#![warn(noop_method_call)]
183#![warn(unreachable_pub)]
184#![warn(clippy::all)]
185#![deny(clippy::await_holding_lock)]
186#![deny(clippy::cargo_common_metadata)]
187#![deny(clippy::cast_lossless)]
188#![deny(clippy::checked_conversions)]
189#![warn(clippy::cognitive_complexity)]
190#![deny(clippy::debug_assert_with_mut_call)]
191#![deny(clippy::exhaustive_enums)]
192#![deny(clippy::exhaustive_structs)]
193#![deny(clippy::expl_impl_clone_on_copy)]
194#![deny(clippy::fallible_impl_from)]
195#![deny(clippy::implicit_clone)]
196#![deny(clippy::large_stack_arrays)]
197#![warn(clippy::manual_ok_or)]
198#![deny(clippy::missing_docs_in_private_items)]
199#![warn(clippy::needless_borrow)]
200#![warn(clippy::needless_pass_by_value)]
201#![warn(clippy::option_option)]
202#![deny(clippy::print_stderr)]
203#![deny(clippy::print_stdout)]
204#![warn(clippy::rc_buffer)]
205#![deny(clippy::ref_option_ref)]
206#![warn(clippy::semicolon_if_nothing_returned)]
207#![warn(clippy::trait_duplication_in_bounds)]
208#![deny(clippy::unchecked_duration_subtraction)]
209#![deny(clippy::unnecessary_wraps)]
210#![warn(clippy::unseparated_literal_suffix)]
211#![deny(clippy::unwrap_used)]
212#![deny(clippy::mod_module_files)]
213#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
214#![allow(clippy::uninlined_format_args)]
215#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
216#![allow(clippy::result_large_err)] // temporary workaround for arti#587
217#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
218#![allow(clippy::needless_lifetimes)] // See arti#1765
219//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
220
221// TODO #1176
222#![allow(clippy::blocks_in_conditions)]
223//
224// See `Panics` in the crate-level docs, above.
225//
226// This lint sometimes has bugs, but it seems to DTRT for me as of 1.81.0-beta.6.
227// If it breaks, these bug(s) may be relevant:
228// https://github.com/rust-lang/rust-clippy/issues/11220
229// https://github.com/rust-lang/rust-clippy/issues/11145
230// https://github.com/rust-lang/rust-clippy/issues/10209
231#![warn(clippy::arithmetic_side_effects)]
232
233// Internal supporting modules
234#[macro_use]
235mod drop_bomb;
236#[macro_use]
237mod refcount;
238#[macro_use]
239mod memory_cost_derive;
240
241mod drop_reentrancy;
242mod if_enabled;
243mod internal_prelude;
244mod utils;
245
246// Modules with public items
247mod config;
248mod error;
249pub mod memory_cost;
250pub mod mq_queue;
251pub mod mtracker;
252
253/// For trait sealing
254mod private {
255 /// Inaccessible trait
256 pub trait Sealed {}
257}
258
259/// Names exported for testing
260#[cfg(feature = "testing")]
261pub mod testing {
262 use super::*;
263 pub use config::ConfigInner;
264}
265
266//---------- re-exports at the crate root ----------
267
268pub use config::{Config, ConfigBuilder};
269pub use error::{Error, MemoryReclaimedError, StartupError};
270pub use if_enabled::EnabledToken;
271pub use memory_cost::HasMemoryCost;
272pub use memory_cost_derive::{assert_copy_static, HasMemoryCostStructural};
273pub use mtracker::{Account, MemoryQuotaTracker};
274pub use utils::ArcMemoryQuotaTrackerExt;
275
276#[doc(hidden)]
277pub use derive_deftly;
278
279/// `Result` whose `Err` is [`tor_memtrack::Error`](Error)
280pub type Result<T> = std::result::Result<T, Error>;