1use crate::{
2 AllocationBootstrap, AllocationDeclaration, AllocationHistory, AllocationLedger,
3 AllocationPolicy, AllocationSlotDescriptor, DeclarationSnapshot, DiagnosticExport,
4 DiagnosticMemorySize, LedgerCommitError, StableCellLedgerError, StableCellLedgerRecord,
5 StableKey, ValidatedAllocations, decode_stable_cell_ledger_record, decode_stable_cell_payload,
6 physical::CommitStoreDiagnostic,
7 registry::{
8 StaticMemoryDeclaration, StaticMemoryDeclarationError, StaticMemoryRangeDeclaration,
9 seal_static_memory_registry, static_memory_declarations, static_memory_range_declarations,
10 },
11 slot::{
12 IC_MEMORY_AUTHORITY_OWNER, IC_MEMORY_AUTHORITY_PURPOSE, IC_MEMORY_LEDGER_LABEL,
13 IC_MEMORY_LEDGER_STABLE_KEY, MEMORY_MANAGER_LEDGER_ID, MemoryManagerAuthorityRecord,
14 MemoryManagerIdRange, MemoryManagerRangeAuthority, MemoryManagerRangeAuthorityError,
15 MemoryManagerRangeMode, MemoryManagerSlotError,
16 },
17};
18use ic_stable_structures::{
19 Cell, DefaultMemoryImpl, Memory,
20 memory_manager::{MemoryId, MemoryManager, VirtualMemory},
21};
22use std::{
23 cell::RefCell,
24 collections::BTreeMap,
25 convert::Infallible,
26 sync::{
27 Mutex,
28 atomic::{AtomicBool, Ordering},
29 },
30};
31
32type DefaultLedgerCell = Cell<StableCellLedgerRecord, VirtualMemory<DefaultMemoryImpl>>;
33
34thread_local! {
35 static DEFAULT_MEMORY_MANAGER: MemoryManager<DefaultMemoryImpl> =
36 MemoryManager::init(DefaultMemoryImpl::default());
37 static DEFAULT_LEDGER_CELL: RefCell<Option<DefaultLedgerCell>> = const {
38 RefCell::new(None)
39 };
40}
41
42static EAGER_INIT_HOOKS: Mutex<Vec<fn()>> = Mutex::new(Vec::new());
43static VALIDATED_ALLOCATIONS: Mutex<Option<ValidatedAllocations>> = Mutex::new(None);
44static BOOTSTRAPPED: AtomicBool = AtomicBool::new(false);
45
46#[derive(Debug, thiserror::Error)]
51pub enum RuntimeBootstrapError<P> {
52 #[error(transparent)]
54 Registry(#[from] StaticMemoryDeclarationError),
55 #[error(transparent)]
57 Range(#[from] MemoryManagerRangeAuthorityError),
58 #[error(transparent)]
60 LedgerIntegrity(#[from] crate::LedgerIntegrityError),
61 #[error(transparent)]
63 LedgerCommit(#[from] crate::LedgerCommitError),
64 #[error(transparent)]
66 StableCellLedger(#[from] StableCellLedgerError),
67 #[error(transparent)]
69 Validation(#[from] crate::AllocationValidationError<RuntimePolicyError<P>>),
70 #[error(transparent)]
72 Staging(#[from] crate::AllocationStageError),
73 #[error("ic-memory runtime lock poisoned")]
75 RuntimeLockPoisoned,
76}
77
78#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
83pub enum RuntimeOpenError {
84 #[error("ic-memory runtime has not completed bootstrap validation")]
86 NotBootstrapped,
87 #[error("ic-memory runtime lock poisoned")]
89 RuntimeLockPoisoned,
90 #[error(transparent)]
92 StableKey(#[from] crate::StableKeyError),
93 #[error("stable key '{0}' was not validated by ic-memory runtime bootstrap")]
95 StableKeyNotValidated(String),
96 #[error(transparent)]
98 MemoryManagerSlot(#[from] MemoryManagerSlotError),
99 #[error(
101 "stable key '{stable_key}' is validated for MemoryManager ID {validated_id}, not requested ID {requested_id}"
102 )]
103 MemoryIdMismatch {
104 stable_key: String,
106 validated_id: u8,
108 requested_id: u8,
110 },
111}
112
113#[derive(Debug, thiserror::Error)]
120pub enum RuntimeDiagnosticError {
121 #[error("ic-memory runtime has not completed bootstrap validation")]
123 NotBootstrapped,
124 #[error(transparent)]
126 LedgerCommit(#[from] LedgerCommitError),
127 #[error(transparent)]
129 StableCellLedger(#[from] StableCellLedgerError),
130 #[error(transparent)]
132 MemoryManagerSlot(#[from] MemoryManagerSlotError),
133}
134
135#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
140pub enum RuntimePolicyError<P> {
141 #[error(transparent)]
143 Range(#[from] MemoryManagerRangeAuthorityError),
144 #[error("runtime declaration metadata is missing for stable key '{0}'")]
146 MissingDeclarationMetadata(String),
147 #[error("stable key '{stable_key}' is reserved to authority '{expected_authority}'")]
149 ReservedStableKeyAuthority {
150 stable_key: String,
152 expected_authority: &'static str,
154 },
155 #[error(transparent)]
157 Custom(P),
158}
159
160pub fn defer_eager_init(f: fn()) {
162 assert!(
163 !is_default_memory_manager_bootstrapped(),
164 "ic-memory eager-init registration attempted after runtime bootstrap"
165 );
166 EAGER_INIT_HOOKS
167 .lock()
168 .expect("ic-memory eager-init queue poisoned")
169 .push(f);
170}
171
172#[must_use]
174pub fn is_default_memory_manager_bootstrapped() -> bool {
175 BOOTSTRAPPED.load(Ordering::SeqCst)
176}
177
178pub fn validated_allocations() -> Result<ValidatedAllocations, RuntimeOpenError> {
180 if !is_default_memory_manager_bootstrapped() {
181 return Err(RuntimeOpenError::NotBootstrapped);
182 }
183 VALIDATED_ALLOCATIONS
184 .lock()
185 .map_err(|_| RuntimeOpenError::RuntimeLockPoisoned)?
186 .clone()
187 .ok_or(RuntimeOpenError::NotBootstrapped)
188}
189
190pub fn bootstrap_default_memory_manager()
192-> Result<ValidatedAllocations, RuntimeBootstrapError<Infallible>> {
193 bootstrap_default_memory_manager_with_policy(&NoopPolicy)
194}
195
196pub fn bootstrap_default_memory_manager_with_policy<P: AllocationPolicy>(
211 policy: &P,
212) -> Result<ValidatedAllocations, RuntimeBootstrapError<P::Error>> {
213 if let Ok(validated) = validated_allocations() {
214 return Ok(validated);
215 }
216
217 run_eager_init_hooks();
218
219 let registered_declarations = static_memory_declarations()?;
220 let registered_ranges = static_memory_range_declarations()?;
221 let user_ranges_registered = !registered_ranges.is_empty();
222 let declaration_metadata = declaration_metadata(®istered_declarations);
223 let range_authority = range_authority(registered_ranges)?;
224 let snapshot = declaration_snapshot(registered_declarations)?;
225 seal_static_memory_registry()?;
226 let policy = RuntimeMemoryManagerPolicy {
227 range_authority,
228 user_ranges_registered,
229 declaration_metadata,
230 custom_policy: policy,
231 };
232 let genesis = AllocationLedger::new(0, AllocationHistory::default())?;
233
234 let validated = with_default_ledger_cell(
235 |cell| -> Result<ValidatedAllocations, RuntimeBootstrapError<P::Error>> {
236 let mut record = cell.get().clone();
237 let mut bootstrap = AllocationBootstrap::new(record.store_mut());
238 let commit = bootstrap
239 .initialize_validate_and_commit(&genesis, snapshot, &policy, None)
240 .map_err(runtime_bootstrap_error_from_bootstrap)?;
241 cell.set(record);
242 Ok(commit.validated)
243 },
244 )?;
245
246 publish_validated_allocations(validated.clone())?;
247 BOOTSTRAPPED.store(true, Ordering::SeqCst);
248 Ok(validated)
249}
250
251pub fn open_default_memory_manager_memory(
253 stable_key: &str,
254 id: u8,
255) -> Result<VirtualMemory<DefaultMemoryImpl>, RuntimeOpenError> {
256 let key = StableKey::parse(stable_key)?;
257 let validated = validated_allocations()?;
258 let slot = validated
259 .slot_for(&key)
260 .ok_or_else(|| RuntimeOpenError::StableKeyNotValidated(stable_key.to_string()))?;
261 let validated_id = slot.memory_manager_id()?;
262 if validated_id != id {
263 return Err(RuntimeOpenError::MemoryIdMismatch {
264 stable_key: stable_key.to_string(),
265 validated_id,
266 requested_id: id,
267 });
268 }
269 Ok(default_memory_manager_memory(id))
270}
271
272pub fn default_memory_manager_diagnostic_export() -> Result<DiagnosticExport, RuntimeDiagnosticError>
279{
280 let record = default_ledger_record_for_diagnostics()?;
281 let recovered = record.store().recover()?;
282 let ledger = recovered.ledger();
283 let memory_sizes = default_memory_manager_memory_sizes(ledger)?;
284
285 Ok(
286 DiagnosticExport::from_ledger_with_commit_recovery_and_memory_sizes(
287 ledger,
288 AllocationSlotDescriptor::memory_manager(MEMORY_MANAGER_LEDGER_ID)?,
289 Some(record.store().physical().diagnostic()),
290 memory_sizes,
291 ),
292 )
293}
294
295pub fn default_memory_manager_commit_recovery_diagnostic()
302-> Result<CommitStoreDiagnostic, RuntimeDiagnosticError> {
303 let record = default_ledger_record_from_memory()?;
304 Ok(record.store().physical().diagnostic())
305}
306
307fn run_eager_init_hooks() {
308 let hooks = {
309 let mut hooks = EAGER_INIT_HOOKS
310 .lock()
311 .expect("ic-memory eager-init queue poisoned");
312 std::mem::take(&mut *hooks)
313 };
314
315 for hook in hooks {
316 hook();
317 }
318}
319
320fn with_default_ledger_cell<P, T>(
321 op: impl FnOnce(&mut DefaultLedgerCell) -> Result<T, RuntimeBootstrapError<P>>,
322) -> Result<T, RuntimeBootstrapError<P>> {
323 DEFAULT_LEDGER_CELL.with(|cell| {
324 let mut cell = cell.borrow_mut();
325 if cell.is_none() {
326 let memory = default_memory_manager_memory(MEMORY_MANAGER_LEDGER_ID);
327 crate::validate_stable_cell_ledger_memory(&memory)?;
328 *cell = Some(Cell::init(memory, StableCellLedgerRecord::default()));
329 }
330 op(cell.as_mut().expect("default ledger cell initialized"))
331 })
332}
333
334fn default_memory_manager_memory(id: u8) -> VirtualMemory<DefaultMemoryImpl> {
335 DEFAULT_MEMORY_MANAGER.with(|manager| manager.get(MemoryId::new(id)))
336}
337
338fn default_ledger_record_for_diagnostics() -> Result<StableCellLedgerRecord, RuntimeDiagnosticError>
339{
340 if !is_default_memory_manager_bootstrapped() {
341 return Err(RuntimeDiagnosticError::NotBootstrapped);
342 }
343
344 default_ledger_record_from_memory().map_err(RuntimeDiagnosticError::StableCellLedger)
345}
346
347fn default_ledger_record_from_memory() -> Result<StableCellLedgerRecord, StableCellLedgerError> {
348 let memory = default_memory_manager_memory(MEMORY_MANAGER_LEDGER_ID);
349 if memory.size() == 0 {
350 return Ok(StableCellLedgerRecord::default());
351 }
352
353 let payload = decode_stable_cell_payload(&memory)?;
354 decode_stable_cell_ledger_record(&payload).map_err(StableCellLedgerError::Record)
355}
356
357fn default_memory_manager_memory_sizes(
358 ledger: &AllocationLedger,
359) -> Result<Vec<(AllocationSlotDescriptor, DiagnosticMemorySize)>, RuntimeDiagnosticError> {
360 ledger
361 .allocation_history()
362 .records()
363 .iter()
364 .map(|record| {
365 let id = record.slot().memory_manager_id()?;
366 let memory = default_memory_manager_memory(id);
367 Ok((
368 record.slot().clone(),
369 DiagnosticMemorySize::from_wasm_pages(memory.size()),
370 ))
371 })
372 .collect()
373}
374
375fn publish_validated_allocations<P>(
376 validated: ValidatedAllocations,
377) -> Result<(), RuntimeBootstrapError<P>> {
378 *VALIDATED_ALLOCATIONS
379 .lock()
380 .map_err(|_| RuntimeBootstrapError::RuntimeLockPoisoned)? = Some(validated);
381 Ok(())
382}
383
384fn declaration_snapshot(
385 registrations: Vec<StaticMemoryDeclaration>,
386) -> Result<DeclarationSnapshot, StaticMemoryDeclarationError> {
387 let mut declarations = Vec::with_capacity(registrations.len() + 1);
388 declarations.push(internal_ledger_declaration()?);
389 declarations.extend(
390 registrations
391 .into_iter()
392 .map(StaticMemoryDeclaration::into_declaration),
393 );
394 DeclarationSnapshot::new(declarations).map_err(StaticMemoryDeclarationError::Declaration)
395}
396
397fn declaration_metadata(registrations: &[StaticMemoryDeclaration]) -> BTreeMap<String, String> {
398 let mut metadata = BTreeMap::new();
399 metadata.insert(
400 IC_MEMORY_LEDGER_STABLE_KEY.to_string(),
401 IC_MEMORY_AUTHORITY_OWNER.to_string(),
402 );
403 for registration in registrations {
404 metadata.insert(
405 registration.declaration().stable_key().as_str().to_string(),
406 registration.declaring_crate().to_string(),
407 );
408 }
409 metadata
410}
411
412fn range_authority(
413 registrations: Vec<StaticMemoryRangeDeclaration>,
414) -> Result<MemoryManagerRangeAuthority, MemoryManagerRangeAuthorityError> {
415 let mut records = Vec::with_capacity(registrations.len() + 1);
416 records.push(internal_ledger_range()?);
417 records.extend(
418 registrations
419 .into_iter()
420 .map(StaticMemoryRangeDeclaration::into_record),
421 );
422 MemoryManagerRangeAuthority::from_records(records)
423}
424
425fn internal_ledger_declaration() -> Result<AllocationDeclaration, crate::DeclarationSnapshotError> {
426 AllocationDeclaration::memory_manager(
427 IC_MEMORY_LEDGER_STABLE_KEY,
428 MEMORY_MANAGER_LEDGER_ID,
429 IC_MEMORY_LEDGER_LABEL,
430 )
431}
432
433fn internal_ledger_range() -> Result<MemoryManagerAuthorityRecord, MemoryManagerRangeAuthorityError>
434{
435 MemoryManagerAuthorityRecord::new(
436 MemoryManagerIdRange::new(
437 MEMORY_MANAGER_LEDGER_ID,
438 crate::MEMORY_MANAGER_GOVERNANCE_MAX_ID,
439 )?,
440 IC_MEMORY_AUTHORITY_OWNER,
441 MemoryManagerRangeMode::Reserved,
442 Some(IC_MEMORY_AUTHORITY_PURPOSE.to_string()),
443 )
444}
445
446fn runtime_bootstrap_error_from_bootstrap<P>(
447 err: crate::BootstrapError<RuntimePolicyError<P>>,
448) -> RuntimeBootstrapError<P> {
449 match err {
450 crate::BootstrapError::Ledger(err) => RuntimeBootstrapError::LedgerCommit(err),
451 crate::BootstrapError::Validation(err) => RuntimeBootstrapError::Validation(err),
452 crate::BootstrapError::Staging(err) => RuntimeBootstrapError::Staging(err),
453 }
454}
455
456struct RuntimeMemoryManagerPolicy<'a, P> {
457 range_authority: MemoryManagerRangeAuthority,
458 user_ranges_registered: bool,
459 declaration_metadata: BTreeMap<String, String>,
460 custom_policy: &'a P,
461}
462
463impl<P: AllocationPolicy> AllocationPolicy for RuntimeMemoryManagerPolicy<'_, P> {
464 type Error = RuntimePolicyError<P::Error>;
465
466 fn validate_key(&self, key: &StableKey) -> Result<(), Self::Error> {
467 let declaring_crate = self.declaring_crate(key)?;
468 if crate::is_ic_memory_stable_key(key.as_str())
469 && declaring_crate != IC_MEMORY_AUTHORITY_OWNER
470 {
471 return Err(RuntimePolicyError::ReservedStableKeyAuthority {
472 stable_key: key.as_str().to_string(),
473 expected_authority: IC_MEMORY_AUTHORITY_OWNER,
474 });
475 }
476 self.custom_policy
477 .validate_key(key)
478 .map_err(RuntimePolicyError::Custom)
479 }
480
481 fn validate_slot(
482 &self,
483 key: &StableKey,
484 slot: &AllocationSlotDescriptor,
485 ) -> Result<(), Self::Error> {
486 self.validate_runtime_range(key, slot)?;
487 self.custom_policy
488 .validate_slot(key, slot)
489 .map_err(RuntimePolicyError::Custom)
490 }
491
492 fn validate_reserved_slot(
493 &self,
494 key: &StableKey,
495 slot: &AllocationSlotDescriptor,
496 ) -> Result<(), Self::Error> {
497 self.validate_runtime_range(key, slot)?;
498 self.custom_policy
499 .validate_reserved_slot(key, slot)
500 .map_err(RuntimePolicyError::Custom)
501 }
502}
503
504impl<P: AllocationPolicy> RuntimeMemoryManagerPolicy<'_, P> {
505 fn declaring_crate(&self, key: &StableKey) -> Result<&str, RuntimePolicyError<P::Error>> {
506 self.declaration_metadata
507 .get(key.as_str())
508 .map(String::as_str)
509 .ok_or_else(|| RuntimePolicyError::MissingDeclarationMetadata(key.as_str().to_string()))
510 }
511
512 fn validate_runtime_range(
513 &self,
514 key: &StableKey,
515 slot: &AllocationSlotDescriptor,
516 ) -> Result<(), RuntimePolicyError<P::Error>> {
517 let declaring_crate = self.declaring_crate(key)?;
518 if declaring_crate == IC_MEMORY_AUTHORITY_OWNER || self.user_ranges_registered {
524 self.range_authority
525 .validate_slot_authority(slot, declaring_crate)?;
526 return Ok(());
527 }
528
529 let id = slot
530 .memory_manager_id()
531 .map_err(MemoryManagerRangeAuthorityError::Slot)?;
532 if self
533 .range_authority
534 .authority_for_id(id)
535 .map_err(RuntimePolicyError::Range)?
536 .is_some()
537 {
538 self.range_authority
539 .validate_slot_authority(slot, declaring_crate)?;
540 }
541 Ok(())
542 }
543}
544
545struct NoopPolicy;
546
547impl AllocationPolicy for NoopPolicy {
548 type Error = Infallible;
549
550 fn validate_key(&self, _key: &StableKey) -> Result<(), Self::Error> {
551 Ok(())
552 }
553
554 fn validate_slot(
555 &self,
556 _key: &StableKey,
557 _slot: &AllocationSlotDescriptor,
558 ) -> Result<(), Self::Error> {
559 Ok(())
560 }
561
562 fn validate_reserved_slot(
563 &self,
564 _key: &StableKey,
565 _slot: &AllocationSlotDescriptor,
566 ) -> Result<(), Self::Error> {
567 Ok(())
568 }
569}
570
571#[cfg(test)]
572pub(crate) fn reset_for_tests() {
573 crate::registry::reset_static_memory_declarations_for_tests();
574 EAGER_INIT_HOOKS
575 .lock()
576 .expect("ic-memory eager-init queue poisoned")
577 .clear();
578 *VALIDATED_ALLOCATIONS
579 .lock()
580 .expect("ic-memory runtime validation state poisoned") = None;
581 BOOTSTRAPPED.store(false, Ordering::SeqCst);
582 DEFAULT_LEDGER_CELL.with_borrow_mut(|cell| {
583 *cell = None;
584 });
585}
586
587#[cfg(test)]
588mod tests {
589 use super::*;
590 use crate::registry::{
591 TEST_REGISTRY_LOCK, register_static_memory_manager_declaration,
592 register_static_memory_manager_range,
593 };
594 use std::sync::atomic::{AtomicBool, Ordering};
595
596 static EAGER_INIT_RAN: AtomicBool = AtomicBool::new(false);
597
598 fn register_crate_a() {
599 register_static_memory_manager_range(
600 100,
601 109,
602 "crate_a",
603 MemoryManagerRangeMode::Reserved,
604 None,
605 )
606 .expect("crate A range");
607 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
608 .expect("crate A memory");
609 }
610
611 fn register_crate_b() {
612 register_static_memory_manager_range(
613 110,
614 119,
615 "crate_b",
616 MemoryManagerRangeMode::Reserved,
617 None,
618 )
619 .expect("crate B range");
620 register_static_memory_manager_declaration(110, "crate_b", "orders", "crate_b.orders.v1")
621 .expect("crate B memory");
622 }
623
624 fn mark_eager_init() {
625 EAGER_INIT_RAN.store(true, Ordering::SeqCst);
626 register_static_memory_manager_declaration(101, "crate_a", "audit", "crate_a.audit.v1")
627 .expect("eager-init declaration");
628 }
629
630 #[test]
631 fn multi_crate_declarations_compose_into_one_bootstrap() {
632 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
633 reset_for_tests();
634 register_crate_a();
635 register_crate_b();
636
637 let validated = bootstrap_default_memory_manager().expect("bootstrap");
638
639 assert_eq!(validated.declarations().len(), 3);
640 assert!(
641 validated
642 .declarations()
643 .iter()
644 .any(|declaration| declaration.stable_key().as_str() == "crate_a.users.v1")
645 );
646 assert!(
647 validated
648 .declarations()
649 .iter()
650 .any(|declaration| declaration.stable_key().as_str() == "crate_b.orders.v1")
651 );
652 }
653
654 #[test]
655 fn conflicting_ranges_fail() {
656 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
657 reset_for_tests();
658 register_static_memory_manager_range(
659 100,
660 110,
661 "crate_a",
662 MemoryManagerRangeMode::Reserved,
663 None,
664 )
665 .expect("crate A range");
666 register_static_memory_manager_range(
667 105,
668 119,
669 "crate_b",
670 MemoryManagerRangeMode::Reserved,
671 None,
672 )
673 .expect("crate B range");
674
675 let err = bootstrap_default_memory_manager().expect_err("overlap must fail");
676 assert!(matches!(
677 err,
678 RuntimeBootstrapError::Range(
679 MemoryManagerRangeAuthorityError::OverlappingRanges { .. }
680 )
681 ));
682 }
683
684 #[test]
685 fn duplicate_stable_keys_fail() {
686 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
687 reset_for_tests();
688 register_static_memory_manager_declaration(100, "crate_a", "users", "app.users.v1")
689 .expect("first declaration");
690 register_static_memory_manager_declaration(101, "crate_b", "users", "app.users.v1")
691 .expect("second declaration");
692
693 let err = bootstrap_default_memory_manager().expect_err("duplicate key must fail");
694 assert!(matches!(
695 err,
696 RuntimeBootstrapError::Registry(StaticMemoryDeclarationError::Declaration(
697 crate::DeclarationSnapshotError::DuplicateStableKey(_)
698 ))
699 ));
700 }
701
702 #[test]
703 fn duplicate_memory_manager_ids_fail() {
704 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
705 reset_for_tests();
706 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
707 .expect("first declaration");
708 register_static_memory_manager_declaration(100, "crate_b", "orders", "crate_b.orders.v1")
709 .expect("second declaration");
710
711 let err = bootstrap_default_memory_manager().expect_err("duplicate slot must fail");
712 assert!(matches!(
713 err,
714 RuntimeBootstrapError::Registry(StaticMemoryDeclarationError::Declaration(
715 crate::DeclarationSnapshotError::DuplicateSlot(_)
716 ))
717 ));
718 }
719
720 #[test]
721 fn out_of_range_memory_declaration_fails_when_ranges_are_declared() {
722 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
723 reset_for_tests();
724 register_static_memory_manager_range(
725 100,
726 109,
727 "crate_a",
728 MemoryManagerRangeMode::Reserved,
729 None,
730 )
731 .expect("crate A range");
732 register_static_memory_manager_declaration(120, "crate_a", "users", "crate_a.users.v1")
733 .expect("out-of-range declaration");
734
735 let err = bootstrap_default_memory_manager().expect_err("out of range must fail");
736 assert!(matches!(
737 err,
738 RuntimeBootstrapError::Validation(crate::AllocationValidationError::Policy(
739 RuntimePolicyError::Range(MemoryManagerRangeAuthorityError::UnclaimedId {
740 id: 120
741 })
742 ))
743 ));
744 }
745
746 #[test]
747 fn late_registration_after_bootstrap_fails() {
748 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
749 reset_for_tests();
750 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
751 .expect("declaration");
752 bootstrap_default_memory_manager().expect("bootstrap");
753
754 let err = register_static_memory_manager_declaration(
755 101,
756 "crate_a",
757 "orders",
758 "crate_a.orders.v1",
759 )
760 .expect_err("late registration must fail");
761 assert_eq!(err, StaticMemoryDeclarationError::RegistrySealed);
762 }
763
764 #[test]
765 fn late_eager_init_registration_after_bootstrap_fails() {
766 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
767 reset_for_tests();
768 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
769 .expect("declaration");
770 bootstrap_default_memory_manager().expect("bootstrap");
771
772 let err = std::panic::catch_unwind(|| defer_eager_init(mark_eager_init))
773 .expect_err("late eager-init registration must fail");
774
775 let message = err
776 .downcast_ref::<String>()
777 .map(String::as_str)
778 .or_else(|| err.downcast_ref::<&str>().copied())
779 .expect("panic message");
780 assert!(message.contains("after runtime bootstrap"));
781 }
782
783 #[test]
784 fn eager_init_runs_before_snapshot_seal() {
785 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
786 reset_for_tests();
787 EAGER_INIT_RAN.store(false, Ordering::SeqCst);
788 register_static_memory_manager_range(
789 100,
790 109,
791 "crate_a",
792 MemoryManagerRangeMode::Reserved,
793 None,
794 )
795 .expect("crate A range");
796 defer_eager_init(mark_eager_init);
797
798 let validated = bootstrap_default_memory_manager().expect("bootstrap");
799
800 assert!(EAGER_INIT_RAN.load(Ordering::SeqCst));
801 assert!(
802 validated
803 .declarations()
804 .iter()
805 .any(|declaration| declaration.stable_key().as_str() == "crate_a.audit.v1")
806 );
807 }
808
809 #[test]
810 fn direct_user_can_bootstrap_and_open_without_canic() {
811 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
812 reset_for_tests();
813 register_static_memory_manager_range(
814 120,
815 129,
816 "icydb",
817 MemoryManagerRangeMode::Reserved,
818 None,
819 )
820 .expect("icydb range");
821 register_static_memory_manager_declaration(120, "icydb", "users", "icydb.users.data.v1")
822 .expect("icydb declaration");
823
824 bootstrap_default_memory_manager().expect("bootstrap");
825 open_default_memory_manager_memory("icydb.users.data.v1", 120).expect("open memory");
826 }
827
828 #[test]
829 fn diagnostic_export_reports_default_memory_manager_sizes() {
830 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
831 reset_for_tests();
832 register_static_memory_manager_range(
833 130,
834 139,
835 "diagnostics",
836 MemoryManagerRangeMode::Reserved,
837 None,
838 )
839 .expect("diagnostics range");
840 register_static_memory_manager_declaration(
841 130,
842 "diagnostics",
843 "users",
844 "diagnostics.users.v1",
845 )
846 .expect("diagnostics declaration");
847
848 bootstrap_default_memory_manager().expect("bootstrap");
849 let memory =
850 open_default_memory_manager_memory("diagnostics.users.v1", 130).expect("open memory");
851 let old_size = memory.size();
852 memory.grow(2);
853
854 let export = default_memory_manager_diagnostic_export().expect("diagnostic export");
855 let recovery =
856 default_memory_manager_commit_recovery_diagnostic().expect("recovery diagnostic");
857 let record = export
858 .records
859 .iter()
860 .find(|record| record.allocation.stable_key().as_str() == "diagnostics.users.v1")
861 .expect("diagnostic allocation");
862
863 assert_eq!(
864 recovery.authoritative_generation,
865 Some(export.current_generation)
866 );
867 assert_eq!(
868 record.memory_size,
869 Some(DiagnosticMemorySize::from_wasm_pages(old_size + 2))
870 );
871 }
872}