#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "memquota"), allow(unused))]
//! ## Intended behaviour
//!
//! 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` holds `Weak`.
//! 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| ||
//! || || | ||
//! \/* \/ | ||
//! | ||
//! Account *===========> MemoryTracker ------------------' ||
//! ||
//! ^ ||
//! | \/
//! |
//! `-------------------------------------------------* Participation
//!
//!
//!
//! accountholder which is also directly the Participant ==============\
//! (impl IsParticipant) ||
//! ^ ||
//! || | ||
//! || | ||
//! || global |Weak ||
//! || || | ||
//! \/ \/ | ||
//! ||
//! Account *===========> MemoryTracker ||
//! ||
//! ^ ||
//! | \/
//! |
//! `-------------------------------------------------* Participation
//!
//! ```
// @@ begin lint list maintained by maint/add_warning @@
#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
#![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
//!
#![allow(clippy::blocks_in_conditions)] // TODO #1176
// Internal supporting modules
#[macro_use]
mod drop_bomb;
#[macro_use]
mod refcount;
mod drop_reentrancy;
mod if_enabled;
mod internal_prelude;
mod utils;
// Modules with public items
mod config;
mod error;
pub mod memory_cost;
pub mod mq_queue;
pub mod mtracker;
/// For trait sealing
mod private {
/// Inaccessible trait
pub trait Sealed {}
}
//---------- re-exports at the crate root ----------
pub use config::{Config, ConfigBuilder};
pub use error::{Error, MemoryReclaimedError, StartupError};
pub use if_enabled::EnabledToken;
pub use memory_cost::HasMemoryCost;
pub use mtracker::MemoryQuotaTracker;
/// `Result` whose `Err` is [`tor_memtrack::Error`](Error)
pub type Result = std::result::Result;