#[cfg(test)]
mod tests;
use crate::{
InternalError,
domain::memory::{
MemoryAllocationBinding, MemoryAllocationState, MemoryCommitRecoveryErrorResponse,
},
dto::memory::{
MemoryAllocationEntry, MemoryAllocationPoolEntry, MemoryAllocationRecordEntry,
MemoryAllocationSizeEntry, MemoryAllocationsResponse, MemoryAuthorityEntry,
MemoryCommitRecoveryResponse, MemoryCommitSlotResponse, MemoryExcludedRangeEntry,
MemoryLedgerMemoryEntry, MemoryLedgerResponse,
},
memory::{self, ledger, registry::MemoryRegistryError},
};
use ic_memory::{
AllocationState, CommitRecoveryError, CommitSlotDiagnostic, CommitStoreDiagnostic,
DiagnosticMemorySize, DiagnosticRecord,
};
use thiserror::Error as ThisError;
#[derive(Debug, ThisError)]
pub enum MemoryRegistryOpsError {
#[error(transparent)]
Ledger(#[from] ledger::MemoryLedgerError),
#[error(transparent)]
Registry(#[from] MemoryRegistryError),
#[error(transparent)]
Runtime(#[from] ic_memory::RuntimeBootstrapError<MemoryRegistryError>),
#[error(transparent)]
Diagnostic(#[from] ic_memory::RuntimeDiagnosticError),
#[error(transparent)]
State(#[from] ic_memory::RuntimeStateError),
}
impl From<MemoryRegistryOpsError> for InternalError {
fn from(err: MemoryRegistryOpsError) -> Self {
let code = match err {
MemoryRegistryOpsError::Registry(_)
| MemoryRegistryOpsError::Runtime(_)
| MemoryRegistryOpsError::Ledger(ledger::MemoryLedgerError::Pool(_)) => {
crate::diagnostics::codes::STORAGE_INVALID_STATE
}
MemoryRegistryOpsError::Diagnostic(_)
| MemoryRegistryOpsError::State(_)
| MemoryRegistryOpsError::Ledger(ledger::MemoryLedgerError::Diagnostic(_)) => {
crate::diagnostics::codes::STATE_INVALID
}
};
Self::public(code)
}
}
pub struct MemoryRegistryOps;
impl MemoryRegistryOps {
pub fn allocation_snapshot() -> Result<MemoryAllocationsResponse, InternalError> {
memory_allocations_response(Self::allocation_report()?)
}
pub(crate) fn allocation_report() -> Result<ic_memory::MemoryAllocations, InternalError> {
ic_memory::default_memory_manager_memory_allocations()
.map_err(MemoryRegistryOpsError::from)
.map_err(Into::into)
}
pub(crate) fn allocation_summary() -> Result<ic_memory::MemoryAllocationSummary, InternalError>
{
ic_memory::default_memory_manager_memory_allocation_summary()
.map_err(MemoryRegistryOpsError::from)
.map_err(Into::into)
}
pub(crate) fn init_registry() -> Result<(), InternalError> {
memory::bootstrap_default_memory_manager().map_err(MemoryRegistryOpsError::from)?;
Ok(())
}
pub fn bootstrap_registry() -> Result<(), InternalError> {
Self::init_registry()
}
pub fn is_initialized() -> Result<bool, InternalError> {
crate::memory::runtime::is_memory_bootstrap_ready()
.map_err(MemoryRegistryOpsError::from)
.map_err(Into::into)
}
#[cfg(target_arch = "wasm32")]
pub fn ensure_bootstrap() -> Result<(), InternalError> {
if Self::is_initialized()? {
return Ok(());
}
Self::bootstrap_registry()
}
pub fn ledger_snapshot() -> Result<MemoryLedgerResponse, InternalError> {
let snapshot = ledger::try_snapshot().map_err(MemoryRegistryOpsError::from)?;
let allocation_pool = memory_allocation_pool_response(&snapshot.allocation_pool);
let records: Vec<MemoryAllocationRecordEntry> = snapshot
.export
.records
.into_iter()
.map(memory_allocation_record_response)
.collect();
let memories = memory_ledger_memory_entries(&records);
Ok(MemoryLedgerResponse {
ledger_schema_version: crate::memory::ledger::MEMORY_LEDGER_SCHEMA_VERSION,
physical_format_id: crate::memory::ledger::MEMORY_PHYSICAL_FORMAT_ID,
current_generation: snapshot.export.current_generation,
ledger_memory_manager_id: snapshot.export.ledger_anchor.id(),
commit_recovery: commit_recovery_response(snapshot.export.commit_recovery),
allocation_pool,
memories,
records,
})
}
}
fn memory_allocations_response(
report: ic_memory::MemoryAllocations,
) -> Result<MemoryAllocationsResponse, InternalError> {
let current_generation = report
.current_generation
.ok_or_else(|| InternalError::public(crate::diagnostics::codes::STATE_INVALID))?;
Ok(MemoryAllocationsResponse {
current_generation,
manager_layout_version: report.manager_layout_version,
bucket_size_pages: report.bucket_size_pages,
bucket_size_bytes: report.bucket_size_bytes,
bucket_capacity: report.bucket_capacity,
allocated_buckets: report.allocated_buckets,
remaining_buckets: report.remaining_buckets,
maximum_bucket_bytes: report.maximum_bucket_bytes,
physical_extent: memory_allocation_size_response(report.physical_extent),
virtual_extent: memory_allocation_size_response(report.virtual_extent),
manager_metadata_bytes: report.manager_metadata_bytes,
manager_header_bytes: report.manager_header_bytes,
manager_bucket_table_bytes: report.manager_bucket_table_bytes,
manager_padding_bytes: report.manager_padding_bytes,
allocated_bucket_bytes: report.allocated_bucket_bytes,
bucket_slack_bytes: report.bucket_slack_bytes,
known_binding_bytes: report.known_binding_bytes,
unknown_binding_bytes: report.unknown_binding_bytes,
unmanaged_bytes: report.unmanaged_bytes,
metadata_bytes_read: report.metadata_bytes_read,
memories: report
.memories
.into_iter()
.map(memory_allocation_entry_response)
.collect(),
})
}
fn memory_allocation_entry_response(entry: ic_memory::MemoryAllocation) -> MemoryAllocationEntry {
let binding = match entry.binding {
ic_memory::AllocationBinding::Current { stable_key, owner } => {
MemoryAllocationBinding::Current { stable_key, owner }
}
ic_memory::AllocationBinding::Ledger { stable_key, owner } => {
MemoryAllocationBinding::Ledger { stable_key, owner }
}
ic_memory::AllocationBinding::Unknown => MemoryAllocationBinding::Unknown,
};
MemoryAllocationEntry {
memory_manager_id: entry.memory_manager_id,
binding,
pool_eligible: entry.pool_eligible,
virtual_extent: memory_allocation_size_response(entry.virtual_extent),
allocated_buckets: entry.allocated_buckets,
allocated_bytes: entry.allocated_bytes,
bucket_slack_bytes: entry.bucket_slack_bytes,
payload_bytes: entry.payload_bytes,
}
}
fn commit_recovery_response(
diagnostic: Option<CommitStoreDiagnostic>,
) -> MemoryCommitRecoveryResponse {
let diagnostic = diagnostic.unwrap_or(CommitStoreDiagnostic {
slot0: CommitSlotDiagnostic::Empty,
slot1: CommitSlotDiagnostic::Empty,
recovery: Err(CommitRecoveryError::NoValidGeneration),
});
let (authoritative_generation, recovery_error) = match diagnostic.recovery {
Ok(generation) => (Some(generation), None),
Err(error) => (None, Some(commit_recovery_error_response(error))),
};
MemoryCommitRecoveryResponse {
slot0: commit_slot_response(diagnostic.slot0),
slot1: commit_slot_response(diagnostic.slot1),
authoritative_generation,
recovery_error,
}
}
fn memory_allocation_record_response(record: DiagnosticRecord) -> MemoryAllocationRecordEntry {
let memory_size = record.memory_size.map(memory_allocation_size_response);
let allocation = record.allocation;
let allocation_state = allocation.state();
MemoryAllocationRecordEntry {
memory_manager_id: Some(allocation.slot().id()),
stable_key: allocation.stable_key().as_str().to_string(),
state: memory_allocation_state_response(allocation_state),
memory_size,
schema_version: allocation.schema().schema_version(),
}
}
fn memory_ledger_memory_entries(
records: &[MemoryAllocationRecordEntry],
) -> Vec<MemoryLedgerMemoryEntry> {
records
.iter()
.filter_map(memory_ledger_memory_entry_response)
.collect()
}
fn memory_ledger_memory_entry_response(
record: &MemoryAllocationRecordEntry,
) -> Option<MemoryLedgerMemoryEntry> {
Some(MemoryLedgerMemoryEntry {
memory_manager_id: record.memory_manager_id?,
stable_key: record.stable_key.clone(),
state: record.state,
size: record.memory_size?,
})
}
fn memory_allocation_pool_response(
pool: &ic_memory::MemoryAllocationPool,
) -> MemoryAllocationPoolEntry {
MemoryAllocationPoolEntry {
authorities: pool
.authorities()
.iter()
.map(|grant| MemoryAuthorityEntry {
owner: grant.authority().to_string(),
key_prefix: grant.key_prefix().to_string(),
})
.collect(),
excluded_ranges: pool
.excluded_ranges()
.iter()
.map(|range| MemoryExcludedRangeEntry {
start: range.start(),
end: range.end(),
})
.collect(),
}
}
const fn memory_allocation_size_response(size: DiagnosticMemorySize) -> MemoryAllocationSizeEntry {
MemoryAllocationSizeEntry {
wasm_pages: size.wasm_pages,
bytes: size.bytes,
}
}
const fn memory_allocation_state_response(state: AllocationState) -> MemoryAllocationState {
match state {
AllocationState::Reserved => MemoryAllocationState::Reserved,
AllocationState::Active => MemoryAllocationState::Active,
AllocationState::Retired => MemoryAllocationState::Retired,
}
}
const fn commit_slot_response(slot: CommitSlotDiagnostic) -> MemoryCommitSlotResponse {
match slot {
CommitSlotDiagnostic::Empty => MemoryCommitSlotResponse {
present: false,
generation: None,
valid: false,
},
CommitSlotDiagnostic::Valid { generation } => MemoryCommitSlotResponse {
present: true,
generation: Some(generation),
valid: true,
},
CommitSlotDiagnostic::Invalid { generation } => MemoryCommitSlotResponse {
present: true,
generation: Some(generation),
valid: false,
},
}
}
const fn commit_recovery_error_response(
err: CommitRecoveryError,
) -> MemoryCommitRecoveryErrorResponse {
match err {
CommitRecoveryError::NoValidGeneration => {
MemoryCommitRecoveryErrorResponse::NoValidGeneration
}
CommitRecoveryError::InvalidCommitSlots { .. } => {
MemoryCommitRecoveryErrorResponse::InvalidCommitSlots
}
CommitRecoveryError::AmbiguousGeneration { .. } => {
MemoryCommitRecoveryErrorResponse::AmbiguousGeneration
}
CommitRecoveryError::GenerationOverflow { .. } => {
MemoryCommitRecoveryErrorResponse::GenerationOverflow
}
CommitRecoveryError::UnexpectedGeneration { .. } => {
MemoryCommitRecoveryErrorResponse::UnexpectedGeneration
}
_ => MemoryCommitRecoveryErrorResponse::Unknown,
}
}