summaryrefslogtreecommitdiff
path: root/crates/tor-memquota/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/tor-memquota/src/lib.rs')
-rw-r--r--crates/tor-memquota/src/lib.rs206
1 files changed, 206 insertions, 0 deletions
diff --git a/crates/tor-memquota/src/lib.rs b/crates/tor-memquota/src/lib.rs
new file mode 100644
index 000000000..f6d5ad21b
--- /dev/null
+++ b/crates/tor-memquota/src/lib.rs
@@ -0,0 +1,206 @@
+#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
+#![doc = include_str!("../README.md")]
+
+//! ## Intended behavour
+//!
+//! In normal operation we try to track as little state as possible, cheaply.
+//! We do track total memory use in nominal bytes
+//! (but a little approximately).
+//!
+//! When we exceed the quota, we engage a more expensive algorithm:
+//! we build a heap to select oldest victims, and
+//! we use the heap to keep reducing memory
+//! until we go below a low-water mark (hysteresis).
+//!
+//! ## Key concepts
+//!
+//! * **Tracker**:
+//! Instance of the memory quota system
+//! Each tracker has a notion of how much memory its participants
+//! are allowed to use, in aggregate.
+//! Tracks memory usage by all the Accounts and Participants.
+//! Different Trackers are completely independent.
+//!
+//! * **Account**:
+//! all memory used within the same Account is treated equally,
+//! and reclamation also happens on an account-by-account basis.
+//! (Each Account is with one Tracker.)
+//!
+//! * **Participant**:
+//! one data structure that uses memory.
+//! Each Participant is linked to *one* Account. An account has *one or more* Participants.
+//! (An Account can exist with zero Participants, but can't then claim memory.)
+//! A Participant provides a `dyn IsParticipant` to the memory system;
+//! in turn, the memory system provides the Participant with a `Participation` -
+//! a handle for tracking memory alloc/free.
+//!
+//! * **Child Account**/**Parent Account**:
+//! An Account may have a Parent.
+//! When a tracker requests memory reclamation from a Parent,
+//! it will also request it of all that Parent's Children (but not vice versa).
+//!
+//! * **Data age**:
+//! Each Participant must be able to say what the oldest data is, that it is storing.
+//! The reclamation policy is to try to free the oldest data.
+//!
+//! * **Reclamation**:
+//! When a Tracker decides that too much memory is being used,
+//! it will select a victim Account based on the data age.
+//! It will then ask *every Participant* in that Account,
+//! and every Participant in every Child of that Account,
+//! to reclaim memory.
+//! A Participant responds by freeing at least some memory,
+//! according to the reclamation request, and tells the Tracker when it has done so.
+//!
+//! * **Reclamation strategy**:
+//! To avoid too-frequent reclamation, once reclamation has started,
+//! it will continue until a low-water mark is reached, significantly lower than the quota.
+//! I.e. the system has a hysteresis.
+// TODO we haven't implemented the queue wrapper yet
+// ! The only currently implemented higher-level Participant is
+// ! a queue which responds to a reclamation request
+// ! by completely destroying itself and freeing all its data.
+//!
+//! * **Approximate** (both in time and space):
+//! The memory quota system is not completely precise.
+//! Participants need not report their use precisely,
+//! but the errors should be reasonably small, and bounded.
+//! Likewise, the enforcement is not precise:
+//! reclamation may start slightly too early, or too late;
+//! but the memory use will be bounded below by O(number of participants)
+//! and above by O(1) (plus errors from the participants).
+//! Reclamation is not immediate, and is dependent on task scheduling;
+//! during memory pressure the quota may be exceeded;
+//! new allocations are not prevented while attempts at reclamation are ongoing.
+//!
+// TODO we haven't implemented the queue wrapper yet
+// ! * **Queues**:
+// ! We provide a higher-level API that wraps an mpsc queue and turns it into a Participant.
+// !
+//! ## Ownership and Arc keeping-alive
+//!
+//! * Somewhere, someone must keep an `Account` to keep the account open.
+//! Ie, the principal object corresponding to the accountholder should contain an `Account`.
+//!
+//! * `Arc<MemoryTracker>` holds `Weak<dyn IsParticipant>`.
+//! If the tracker finds the `IsParticipant` has vanished,
+//! it assumes this means that the Participant is being destroyed and
+//! it can treat all of the memory it claimed as freed.
+//!
+//! * Each participant holds a `Participation`.
+//! A `Participation` may be invalidated by collapse of the underlying Account,
+//! which may be triggered in any number of ways.
+//!
+//! * A `Participation` does *not* keep its `Account` alive.
+//! Ie, it has only a weak reference to the Account.
+//!
+//! * A Participant's implementor of `IsParticipant` may hold a `Participation`.
+//! If the `impl IsParticipant` is also the principal accountholder object,
+//! it must hold an `Account` too.
+//!
+//! * Child/parent accounts do not imply any keeping-alive relationship.
+//! It's just that a reclamation request to a parent (if it still exists)
+//! will also be made to its children.
+//!
+//!
+//! ```text
+//! accountholder =======================================>* Participant
+//! (impl IsParticipant)
+//! ||
+//! || ^ ||
+//! || | ||
+//! || global Weak<dyn>| ||
+//! || || | ||
+//! \/* \/ | ||
+//! | ||
+//! Account *===========> MemoryTracker ------------------' ||
+//! ||
+//! ^ ||
+//! | \/
+//! |
+//! `-------------------------------------------------* Participation
+//!
+//!
+//!
+//! accountholder which is also directly the Participant ==============\
+//! (impl IsParticipant) ||
+//! ^ ||
+//! || | ||
+//! || | ||
+//! || global |Weak<dyn> ||
+//! || || | ||
+//! \/ \/ | ||
+//! ||
+//! Account *===========> MemoryTracker ||
+//! ||
+//! ^ ||
+//! | \/
+//! |
+//! `-------------------------------------------------* Participation
+//!
+//! ```
+
+// @@ begin lint list maintained by maint/add_warning @@
+#![cfg_attr(not(ci_arti_stable), allow(renamed_and_removed_lints))]
+#![cfg_attr(not(ci_arti_nightly), allow(unknown_lints))]
+#![warn(missing_docs)]
+#![warn(noop_method_call)]
+#![warn(unreachable_pub)]
+#![warn(clippy::all)]
+#![deny(clippy::await_holding_lock)]
+#![deny(clippy::cargo_common_metadata)]
+#![deny(clippy::cast_lossless)]
+#![deny(clippy::checked_conversions)]
+#![warn(clippy::cognitive_complexity)]
+#![deny(clippy::debug_assert_with_mut_call)]
+#![deny(clippy::exhaustive_enums)]
+#![deny(clippy::exhaustive_structs)]
+#![deny(clippy::expl_impl_clone_on_copy)]
+#![deny(clippy::fallible_impl_from)]
+#![deny(clippy::implicit_clone)]
+#![deny(clippy::large_stack_arrays)]
+#![warn(clippy::manual_ok_or)]
+#![deny(clippy::missing_docs_in_private_items)]
+#![warn(clippy::needless_borrow)]
+#![warn(clippy::needless_pass_by_value)]
+#![warn(clippy::option_option)]
+#![deny(clippy::print_stderr)]
+#![deny(clippy::print_stdout)]
+#![warn(clippy::rc_buffer)]
+#![deny(clippy::ref_option_ref)]
+#![warn(clippy::semicolon_if_nothing_returned)]
+#![warn(clippy::trait_duplication_in_bounds)]
+#![deny(clippy::unchecked_duration_subtraction)]
+#![deny(clippy::unnecessary_wraps)]
+#![warn(clippy::unseparated_literal_suffix)]
+#![deny(clippy::unwrap_used)]
+#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
+#![allow(clippy::uninlined_format_args)]
+#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
+#![allow(clippy::result_large_err)] // temporary workaround for arti#587
+#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
+//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
+#![allow(clippy::blocks_in_conditions)] // TODO #1176
+
+// Internal supporting modules
+#[macro_use]
+mod drop_bomb;
+mod drop_reentrancy;
+mod internal_prelude;
+#[macro_use]
+mod refcount;
+mod utils;
+
+// Modules with public items
+mod config;
+mod error;
+pub mod mtracker;
+
+//---------- re-exports at the crate root ----------
+
+pub use config::{Config, ConfigBuilder};
+pub use error::{Error, MemoryReclaimedError, StartupError};
+pub use mtracker::MemoryQuotaTracker;
+
+/// `Result` whose `Err` is [`tor_memtrack::Error`](Error)
+pub type Result<T> = std::result::Result<T, Error>;