1use crate::{
2 AllocationBootstrap, AllocationDeclaration, AllocationHistory, AllocationLedger,
3 AllocationPolicy, AllocationSlotDescriptor, DeclarationSnapshot, StableCellLedgerError,
4 StableCellLedgerRecord, StableKey, ValidatedAllocations,
5 registry::{
6 StaticMemoryDeclaration, StaticMemoryDeclarationError, StaticMemoryRangeDeclaration,
7 seal_static_memory_registry, static_memory_declarations, static_memory_range_declarations,
8 },
9 slot::{
10 IC_MEMORY_AUTHORITY_OWNER, IC_MEMORY_AUTHORITY_PURPOSE, IC_MEMORY_LEDGER_LABEL,
11 IC_MEMORY_LEDGER_STABLE_KEY, MEMORY_MANAGER_LEDGER_ID, MemoryManagerAuthorityRecord,
12 MemoryManagerIdRange, MemoryManagerRangeAuthority, MemoryManagerRangeAuthorityError,
13 MemoryManagerRangeMode, MemoryManagerSlotError,
14 },
15};
16use ic_stable_structures::{
17 Cell, DefaultMemoryImpl,
18 memory_manager::{MemoryId, MemoryManager, VirtualMemory},
19};
20use std::{
21 cell::RefCell,
22 collections::BTreeMap,
23 convert::Infallible,
24 sync::{
25 Mutex,
26 atomic::{AtomicBool, Ordering},
27 },
28};
29
30type DefaultLedgerCell = Cell<StableCellLedgerRecord, VirtualMemory<DefaultMemoryImpl>>;
31
32thread_local! {
33 static DEFAULT_MEMORY_MANAGER: MemoryManager<DefaultMemoryImpl> =
34 MemoryManager::init(DefaultMemoryImpl::default());
35 static DEFAULT_LEDGER_CELL: RefCell<Option<DefaultLedgerCell>> = const {
36 RefCell::new(None)
37 };
38}
39
40static EAGER_INIT_HOOKS: Mutex<Vec<fn()>> = Mutex::new(Vec::new());
41static VALIDATED_ALLOCATIONS: Mutex<Option<ValidatedAllocations>> = Mutex::new(None);
42static BOOTSTRAPPED: AtomicBool = AtomicBool::new(false);
43
44#[derive(Debug, thiserror::Error)]
49pub enum RuntimeBootstrapError<P> {
50 #[error(transparent)]
52 Registry(#[from] StaticMemoryDeclarationError),
53 #[error(transparent)]
55 Range(#[from] MemoryManagerRangeAuthorityError),
56 #[error(transparent)]
58 LedgerIntegrity(#[from] crate::LedgerIntegrityError),
59 #[error(transparent)]
61 LedgerCommit(#[from] crate::LedgerCommitError),
62 #[error(transparent)]
64 StableCellLedger(#[from] StableCellLedgerError),
65 #[error(transparent)]
67 Validation(#[from] crate::AllocationValidationError<RuntimePolicyError<P>>),
68 #[error(transparent)]
70 Staging(#[from] crate::AllocationStageError),
71 #[error("ic-memory runtime lock poisoned")]
73 RuntimeLockPoisoned,
74}
75
76#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
81pub enum RuntimeOpenError {
82 #[error("ic-memory runtime has not completed bootstrap validation")]
84 NotBootstrapped,
85 #[error("ic-memory runtime lock poisoned")]
87 RuntimeLockPoisoned,
88 #[error(transparent)]
90 StableKey(#[from] crate::StableKeyError),
91 #[error("stable key '{0}' was not validated by ic-memory runtime bootstrap")]
93 StableKeyNotValidated(String),
94 #[error(transparent)]
96 MemoryManagerSlot(#[from] MemoryManagerSlotError),
97 #[error(
99 "stable key '{stable_key}' is validated for MemoryManager ID {validated_id}, not requested ID {requested_id}"
100 )]
101 MemoryIdMismatch {
102 stable_key: String,
104 validated_id: u8,
106 requested_id: u8,
108 },
109}
110
111#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
116pub enum RuntimePolicyError<P> {
117 #[error(transparent)]
119 Range(#[from] MemoryManagerRangeAuthorityError),
120 #[error("runtime declaration metadata is missing for stable key '{0}'")]
122 MissingDeclarationMetadata(String),
123 #[error("stable key '{stable_key}' is reserved to authority '{expected_authority}'")]
125 ReservedStableKeyAuthority {
126 stable_key: String,
128 expected_authority: &'static str,
130 },
131 #[error(transparent)]
133 Custom(P),
134}
135
136pub fn defer_eager_init(f: fn()) {
138 assert!(
139 !is_default_memory_manager_bootstrapped(),
140 "ic-memory eager-init registration attempted after runtime bootstrap"
141 );
142 EAGER_INIT_HOOKS
143 .lock()
144 .expect("ic-memory eager-init queue poisoned")
145 .push(f);
146}
147
148#[must_use]
150pub fn is_default_memory_manager_bootstrapped() -> bool {
151 BOOTSTRAPPED.load(Ordering::SeqCst)
152}
153
154pub fn validated_allocations() -> Result<ValidatedAllocations, RuntimeOpenError> {
156 if !is_default_memory_manager_bootstrapped() {
157 return Err(RuntimeOpenError::NotBootstrapped);
158 }
159 VALIDATED_ALLOCATIONS
160 .lock()
161 .map_err(|_| RuntimeOpenError::RuntimeLockPoisoned)?
162 .clone()
163 .ok_or(RuntimeOpenError::NotBootstrapped)
164}
165
166pub fn bootstrap_default_memory_manager()
168-> Result<ValidatedAllocations, RuntimeBootstrapError<Infallible>> {
169 bootstrap_default_memory_manager_with_policy(&NoopPolicy)
170}
171
172pub fn bootstrap_default_memory_manager_with_policy<P: AllocationPolicy>(
187 policy: &P,
188) -> Result<ValidatedAllocations, RuntimeBootstrapError<P::Error>> {
189 if let Ok(validated) = validated_allocations() {
190 return Ok(validated);
191 }
192
193 run_eager_init_hooks();
194
195 let registered_declarations = static_memory_declarations()?;
196 let registered_ranges = static_memory_range_declarations()?;
197 let user_ranges_registered = !registered_ranges.is_empty();
198 let declaration_metadata = declaration_metadata(®istered_declarations);
199 let range_authority = range_authority(registered_ranges)?;
200 let snapshot = declaration_snapshot(registered_declarations)?;
201 seal_static_memory_registry()?;
202 let policy = RuntimeMemoryManagerPolicy {
203 range_authority,
204 user_ranges_registered,
205 declaration_metadata,
206 custom_policy: policy,
207 };
208 let genesis = AllocationLedger::new(0, AllocationHistory::default())?;
209
210 let validated = with_default_ledger_cell(
211 |cell| -> Result<ValidatedAllocations, RuntimeBootstrapError<P::Error>> {
212 let mut record = cell.get().clone();
213 let mut bootstrap = AllocationBootstrap::new(record.store_mut());
214 let commit = bootstrap
215 .initialize_validate_and_commit(&genesis, snapshot, &policy, None)
216 .map_err(runtime_bootstrap_error_from_bootstrap)?;
217 cell.set(record);
218 Ok(commit.validated)
219 },
220 )?;
221
222 publish_validated_allocations(validated.clone())?;
223 BOOTSTRAPPED.store(true, Ordering::SeqCst);
224 Ok(validated)
225}
226
227pub fn open_default_memory_manager_memory(
229 stable_key: &str,
230 id: u8,
231) -> Result<VirtualMemory<DefaultMemoryImpl>, RuntimeOpenError> {
232 let key = StableKey::parse(stable_key)?;
233 let validated = validated_allocations()?;
234 let slot = validated
235 .slot_for(&key)
236 .ok_or_else(|| RuntimeOpenError::StableKeyNotValidated(stable_key.to_string()))?;
237 let validated_id = slot.memory_manager_id()?;
238 if validated_id != id {
239 return Err(RuntimeOpenError::MemoryIdMismatch {
240 stable_key: stable_key.to_string(),
241 validated_id,
242 requested_id: id,
243 });
244 }
245 Ok(default_memory_manager_memory(id))
246}
247
248fn run_eager_init_hooks() {
249 let hooks = {
250 let mut hooks = EAGER_INIT_HOOKS
251 .lock()
252 .expect("ic-memory eager-init queue poisoned");
253 std::mem::take(&mut *hooks)
254 };
255
256 for hook in hooks {
257 hook();
258 }
259}
260
261fn with_default_ledger_cell<P, T>(
262 op: impl FnOnce(&mut DefaultLedgerCell) -> Result<T, RuntimeBootstrapError<P>>,
263) -> Result<T, RuntimeBootstrapError<P>> {
264 DEFAULT_LEDGER_CELL.with(|cell| {
265 let mut cell = cell.borrow_mut();
266 if cell.is_none() {
267 let memory = default_memory_manager_memory(MEMORY_MANAGER_LEDGER_ID);
268 crate::validate_stable_cell_ledger_memory(&memory)?;
269 *cell = Some(Cell::init(memory, StableCellLedgerRecord::default()));
270 }
271 op(cell.as_mut().expect("default ledger cell initialized"))
272 })
273}
274
275fn default_memory_manager_memory(id: u8) -> VirtualMemory<DefaultMemoryImpl> {
276 DEFAULT_MEMORY_MANAGER.with(|manager| manager.get(MemoryId::new(id)))
277}
278
279fn publish_validated_allocations<P>(
280 validated: ValidatedAllocations,
281) -> Result<(), RuntimeBootstrapError<P>> {
282 *VALIDATED_ALLOCATIONS
283 .lock()
284 .map_err(|_| RuntimeBootstrapError::RuntimeLockPoisoned)? = Some(validated);
285 Ok(())
286}
287
288fn declaration_snapshot(
289 registrations: Vec<StaticMemoryDeclaration>,
290) -> Result<DeclarationSnapshot, StaticMemoryDeclarationError> {
291 let mut declarations = Vec::with_capacity(registrations.len() + 1);
292 declarations.push(internal_ledger_declaration()?);
293 declarations.extend(
294 registrations
295 .into_iter()
296 .map(StaticMemoryDeclaration::into_declaration),
297 );
298 DeclarationSnapshot::new(declarations).map_err(StaticMemoryDeclarationError::Declaration)
299}
300
301fn declaration_metadata(registrations: &[StaticMemoryDeclaration]) -> BTreeMap<String, String> {
302 let mut metadata = BTreeMap::new();
303 metadata.insert(
304 IC_MEMORY_LEDGER_STABLE_KEY.to_string(),
305 IC_MEMORY_AUTHORITY_OWNER.to_string(),
306 );
307 for registration in registrations {
308 metadata.insert(
309 registration.declaration().stable_key().as_str().to_string(),
310 registration.declaring_crate().to_string(),
311 );
312 }
313 metadata
314}
315
316fn range_authority(
317 registrations: Vec<StaticMemoryRangeDeclaration>,
318) -> Result<MemoryManagerRangeAuthority, MemoryManagerRangeAuthorityError> {
319 let mut records = Vec::with_capacity(registrations.len() + 1);
320 records.push(internal_ledger_range()?);
321 records.extend(
322 registrations
323 .into_iter()
324 .map(StaticMemoryRangeDeclaration::into_record),
325 );
326 MemoryManagerRangeAuthority::from_records(records)
327}
328
329fn internal_ledger_declaration() -> Result<AllocationDeclaration, crate::DeclarationSnapshotError> {
330 AllocationDeclaration::memory_manager(
331 IC_MEMORY_LEDGER_STABLE_KEY,
332 MEMORY_MANAGER_LEDGER_ID,
333 IC_MEMORY_LEDGER_LABEL,
334 )
335}
336
337fn internal_ledger_range() -> Result<MemoryManagerAuthorityRecord, MemoryManagerRangeAuthorityError>
338{
339 MemoryManagerAuthorityRecord::new(
340 MemoryManagerIdRange::new(
341 MEMORY_MANAGER_LEDGER_ID,
342 crate::MEMORY_MANAGER_GOVERNANCE_MAX_ID,
343 )?,
344 IC_MEMORY_AUTHORITY_OWNER,
345 MemoryManagerRangeMode::Reserved,
346 Some(IC_MEMORY_AUTHORITY_PURPOSE.to_string()),
347 )
348}
349
350fn runtime_bootstrap_error_from_bootstrap<P>(
351 err: crate::BootstrapError<RuntimePolicyError<P>>,
352) -> RuntimeBootstrapError<P> {
353 match err {
354 crate::BootstrapError::Ledger(err) => RuntimeBootstrapError::LedgerCommit(err),
355 crate::BootstrapError::Validation(err) => RuntimeBootstrapError::Validation(err),
356 crate::BootstrapError::Staging(err) => RuntimeBootstrapError::Staging(err),
357 }
358}
359
360struct RuntimeMemoryManagerPolicy<'a, P> {
361 range_authority: MemoryManagerRangeAuthority,
362 user_ranges_registered: bool,
363 declaration_metadata: BTreeMap<String, String>,
364 custom_policy: &'a P,
365}
366
367impl<P: AllocationPolicy> AllocationPolicy for RuntimeMemoryManagerPolicy<'_, P> {
368 type Error = RuntimePolicyError<P::Error>;
369
370 fn validate_key(&self, key: &StableKey) -> Result<(), Self::Error> {
371 let declaring_crate = self.declaring_crate(key)?;
372 if crate::is_ic_memory_stable_key(key.as_str())
373 && declaring_crate != IC_MEMORY_AUTHORITY_OWNER
374 {
375 return Err(RuntimePolicyError::ReservedStableKeyAuthority {
376 stable_key: key.as_str().to_string(),
377 expected_authority: IC_MEMORY_AUTHORITY_OWNER,
378 });
379 }
380 self.custom_policy
381 .validate_key(key)
382 .map_err(RuntimePolicyError::Custom)
383 }
384
385 fn validate_slot(
386 &self,
387 key: &StableKey,
388 slot: &AllocationSlotDescriptor,
389 ) -> Result<(), Self::Error> {
390 self.validate_runtime_range(key, slot)?;
391 self.custom_policy
392 .validate_slot(key, slot)
393 .map_err(RuntimePolicyError::Custom)
394 }
395
396 fn validate_reserved_slot(
397 &self,
398 key: &StableKey,
399 slot: &AllocationSlotDescriptor,
400 ) -> Result<(), Self::Error> {
401 self.validate_runtime_range(key, slot)?;
402 self.custom_policy
403 .validate_reserved_slot(key, slot)
404 .map_err(RuntimePolicyError::Custom)
405 }
406}
407
408impl<P: AllocationPolicy> RuntimeMemoryManagerPolicy<'_, P> {
409 fn declaring_crate(&self, key: &StableKey) -> Result<&str, RuntimePolicyError<P::Error>> {
410 self.declaration_metadata
411 .get(key.as_str())
412 .map(String::as_str)
413 .ok_or_else(|| RuntimePolicyError::MissingDeclarationMetadata(key.as_str().to_string()))
414 }
415
416 fn validate_runtime_range(
417 &self,
418 key: &StableKey,
419 slot: &AllocationSlotDescriptor,
420 ) -> Result<(), RuntimePolicyError<P::Error>> {
421 let declaring_crate = self.declaring_crate(key)?;
422 if declaring_crate == IC_MEMORY_AUTHORITY_OWNER || self.user_ranges_registered {
428 self.range_authority
429 .validate_slot_authority(slot, declaring_crate)?;
430 return Ok(());
431 }
432
433 let id = slot
434 .memory_manager_id()
435 .map_err(MemoryManagerRangeAuthorityError::Slot)?;
436 if self
437 .range_authority
438 .authority_for_id(id)
439 .map_err(RuntimePolicyError::Range)?
440 .is_some()
441 {
442 self.range_authority
443 .validate_slot_authority(slot, declaring_crate)?;
444 }
445 Ok(())
446 }
447}
448
449struct NoopPolicy;
450
451impl AllocationPolicy for NoopPolicy {
452 type Error = Infallible;
453
454 fn validate_key(&self, _key: &StableKey) -> Result<(), Self::Error> {
455 Ok(())
456 }
457
458 fn validate_slot(
459 &self,
460 _key: &StableKey,
461 _slot: &AllocationSlotDescriptor,
462 ) -> Result<(), Self::Error> {
463 Ok(())
464 }
465
466 fn validate_reserved_slot(
467 &self,
468 _key: &StableKey,
469 _slot: &AllocationSlotDescriptor,
470 ) -> Result<(), Self::Error> {
471 Ok(())
472 }
473}
474
475#[cfg(test)]
476pub(crate) fn reset_for_tests() {
477 crate::registry::reset_static_memory_declarations_for_tests();
478 EAGER_INIT_HOOKS
479 .lock()
480 .expect("ic-memory eager-init queue poisoned")
481 .clear();
482 *VALIDATED_ALLOCATIONS
483 .lock()
484 .expect("ic-memory runtime validation state poisoned") = None;
485 BOOTSTRAPPED.store(false, Ordering::SeqCst);
486 DEFAULT_LEDGER_CELL.with_borrow_mut(|cell| {
487 *cell = None;
488 });
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494 use crate::registry::{
495 TEST_REGISTRY_LOCK, register_static_memory_manager_declaration,
496 register_static_memory_manager_range,
497 };
498 use std::sync::atomic::{AtomicBool, Ordering};
499
500 static EAGER_INIT_RAN: AtomicBool = AtomicBool::new(false);
501
502 fn register_crate_a() {
503 register_static_memory_manager_range(
504 100,
505 109,
506 "crate_a",
507 MemoryManagerRangeMode::Reserved,
508 None,
509 )
510 .expect("crate A range");
511 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
512 .expect("crate A memory");
513 }
514
515 fn register_crate_b() {
516 register_static_memory_manager_range(
517 110,
518 119,
519 "crate_b",
520 MemoryManagerRangeMode::Reserved,
521 None,
522 )
523 .expect("crate B range");
524 register_static_memory_manager_declaration(110, "crate_b", "orders", "crate_b.orders.v1")
525 .expect("crate B memory");
526 }
527
528 fn mark_eager_init() {
529 EAGER_INIT_RAN.store(true, Ordering::SeqCst);
530 register_static_memory_manager_declaration(101, "crate_a", "audit", "crate_a.audit.v1")
531 .expect("eager-init declaration");
532 }
533
534 #[test]
535 fn multi_crate_declarations_compose_into_one_bootstrap() {
536 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
537 reset_for_tests();
538 register_crate_a();
539 register_crate_b();
540
541 let validated = bootstrap_default_memory_manager().expect("bootstrap");
542
543 assert_eq!(validated.declarations().len(), 3);
544 assert!(
545 validated
546 .declarations()
547 .iter()
548 .any(|declaration| declaration.stable_key().as_str() == "crate_a.users.v1")
549 );
550 assert!(
551 validated
552 .declarations()
553 .iter()
554 .any(|declaration| declaration.stable_key().as_str() == "crate_b.orders.v1")
555 );
556 }
557
558 #[test]
559 fn conflicting_ranges_fail() {
560 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
561 reset_for_tests();
562 register_static_memory_manager_range(
563 100,
564 110,
565 "crate_a",
566 MemoryManagerRangeMode::Reserved,
567 None,
568 )
569 .expect("crate A range");
570 register_static_memory_manager_range(
571 105,
572 119,
573 "crate_b",
574 MemoryManagerRangeMode::Reserved,
575 None,
576 )
577 .expect("crate B range");
578
579 let err = bootstrap_default_memory_manager().expect_err("overlap must fail");
580 assert!(matches!(
581 err,
582 RuntimeBootstrapError::Range(
583 MemoryManagerRangeAuthorityError::OverlappingRanges { .. }
584 )
585 ));
586 }
587
588 #[test]
589 fn duplicate_stable_keys_fail() {
590 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
591 reset_for_tests();
592 register_static_memory_manager_declaration(100, "crate_a", "users", "app.users.v1")
593 .expect("first declaration");
594 register_static_memory_manager_declaration(101, "crate_b", "users", "app.users.v1")
595 .expect("second declaration");
596
597 let err = bootstrap_default_memory_manager().expect_err("duplicate key must fail");
598 assert!(matches!(
599 err,
600 RuntimeBootstrapError::Registry(StaticMemoryDeclarationError::Declaration(
601 crate::DeclarationSnapshotError::DuplicateStableKey(_)
602 ))
603 ));
604 }
605
606 #[test]
607 fn duplicate_memory_manager_ids_fail() {
608 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
609 reset_for_tests();
610 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
611 .expect("first declaration");
612 register_static_memory_manager_declaration(100, "crate_b", "orders", "crate_b.orders.v1")
613 .expect("second declaration");
614
615 let err = bootstrap_default_memory_manager().expect_err("duplicate slot must fail");
616 assert!(matches!(
617 err,
618 RuntimeBootstrapError::Registry(StaticMemoryDeclarationError::Declaration(
619 crate::DeclarationSnapshotError::DuplicateSlot(_)
620 ))
621 ));
622 }
623
624 #[test]
625 fn out_of_range_memory_declaration_fails_when_ranges_are_declared() {
626 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
627 reset_for_tests();
628 register_static_memory_manager_range(
629 100,
630 109,
631 "crate_a",
632 MemoryManagerRangeMode::Reserved,
633 None,
634 )
635 .expect("crate A range");
636 register_static_memory_manager_declaration(120, "crate_a", "users", "crate_a.users.v1")
637 .expect("out-of-range declaration");
638
639 let err = bootstrap_default_memory_manager().expect_err("out of range must fail");
640 assert!(matches!(
641 err,
642 RuntimeBootstrapError::Validation(crate::AllocationValidationError::Policy(
643 RuntimePolicyError::Range(MemoryManagerRangeAuthorityError::UnclaimedId {
644 id: 120
645 })
646 ))
647 ));
648 }
649
650 #[test]
651 fn late_registration_after_bootstrap_fails() {
652 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
653 reset_for_tests();
654 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
655 .expect("declaration");
656 bootstrap_default_memory_manager().expect("bootstrap");
657
658 let err = register_static_memory_manager_declaration(
659 101,
660 "crate_a",
661 "orders",
662 "crate_a.orders.v1",
663 )
664 .expect_err("late registration must fail");
665 assert_eq!(err, StaticMemoryDeclarationError::RegistrySealed);
666 }
667
668 #[test]
669 fn late_eager_init_registration_after_bootstrap_fails() {
670 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
671 reset_for_tests();
672 register_static_memory_manager_declaration(100, "crate_a", "users", "crate_a.users.v1")
673 .expect("declaration");
674 bootstrap_default_memory_manager().expect("bootstrap");
675
676 let err = std::panic::catch_unwind(|| defer_eager_init(mark_eager_init))
677 .expect_err("late eager-init registration must fail");
678
679 let message = err
680 .downcast_ref::<String>()
681 .map(String::as_str)
682 .or_else(|| err.downcast_ref::<&str>().copied())
683 .expect("panic message");
684 assert!(message.contains("after runtime bootstrap"));
685 }
686
687 #[test]
688 fn eager_init_runs_before_snapshot_seal() {
689 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
690 reset_for_tests();
691 EAGER_INIT_RAN.store(false, Ordering::SeqCst);
692 register_static_memory_manager_range(
693 100,
694 109,
695 "crate_a",
696 MemoryManagerRangeMode::Reserved,
697 None,
698 )
699 .expect("crate A range");
700 defer_eager_init(mark_eager_init);
701
702 let validated = bootstrap_default_memory_manager().expect("bootstrap");
703
704 assert!(EAGER_INIT_RAN.load(Ordering::SeqCst));
705 assert!(
706 validated
707 .declarations()
708 .iter()
709 .any(|declaration| declaration.stable_key().as_str() == "crate_a.audit.v1")
710 );
711 }
712
713 #[test]
714 fn direct_user_can_bootstrap_and_open_without_canic() {
715 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
716 reset_for_tests();
717 register_static_memory_manager_range(
718 120,
719 129,
720 "icydb",
721 MemoryManagerRangeMode::Reserved,
722 None,
723 )
724 .expect("icydb range");
725 register_static_memory_manager_declaration(120, "icydb", "users", "icydb.users.data.v1")
726 .expect("icydb declaration");
727
728 bootstrap_default_memory_manager().expect("bootstrap");
729 open_default_memory_manager_memory("icydb.users.data.v1", 120).expect("open memory");
730 }
731}