1use super::{
2 MemoryManagerConfig, MemoryRuntime, RuntimeBootstrapError, RuntimeConstructionError,
3 RuntimeDiagnosticError, RuntimeMemory, RuntimeOpenError, RuntimeStateError,
4 policy::GenericRangePolicy,
5};
6use crate::{
7 CommittedAllocations, DiagnosticExport, MemoryRuntimeDoctorReport, RuntimeBootstrapPolicy,
8 physical::CommitStoreDiagnostic, registry::sealed_declaration_snapshot,
9};
10use ic_stable_structures::DefaultMemoryImpl;
11use std::{cell::RefCell, convert::Infallible, fmt::Display};
12
13thread_local! {
14 static DEFAULT_RUNTIME:
15 RefCell<Option<Result<MemoryRuntime<DefaultMemoryImpl>, RuntimeConstructionError>>> =
16 const { RefCell::new(None) };
17}
18
19fn with_default_runtime_mut<T, E>(
20 config: Option<MemoryManagerConfig>,
21 operation: impl FnOnce(&mut MemoryRuntime<DefaultMemoryImpl>) -> Result<T, E>,
22) -> Result<T, E>
23where
24 E: From<RuntimeStateError>,
25{
26 match DEFAULT_RUNTIME.try_with(|runtime| {
27 let mut runtime = runtime
28 .try_borrow_mut()
29 .map_err(|_| E::from(RuntimeStateError::ReentrantAccess))?;
30 let runtime = runtime
31 .get_or_insert_with(|| match config {
32 Some(config) => {
33 MemoryRuntime::new_with_config(DefaultMemoryImpl::default(), config)
34 }
35 None => MemoryRuntime::new(DefaultMemoryImpl::default()),
36 })
37 .as_mut()
38 .map_err(|error| E::from(RuntimeStateError::Construction(*error)))?;
39 if let Some(config) = config {
40 super::check_bucket_size(runtime.bucket_size_pages, config)
41 .map_err(|error| E::from(RuntimeStateError::Construction(error)))?;
42 }
43 operation(runtime)
44 }) {
45 Ok(result) => result,
46 Err(_) => Err(E::from(RuntimeStateError::Unavailable)),
47 }
48}
49
50fn with_existing_default_runtime<T, E>(
53 operation: impl FnOnce(Option<&MemoryRuntime<DefaultMemoryImpl>>) -> Result<T, E>,
54) -> Result<T, E>
55where
56 E: From<RuntimeStateError>,
57{
58 DEFAULT_RUNTIME
59 .try_with(|runtime| {
60 let runtime = runtime
61 .try_borrow()
62 .map_err(|_| E::from(RuntimeStateError::ReentrantAccess))?;
63 let existing = runtime
64 .as_ref()
65 .map(|runtime| {
66 runtime
67 .as_ref()
68 .map_err(|error| E::from(RuntimeStateError::Construction(*error)))
69 })
70 .transpose()?;
71 operation(existing)
72 })
73 .map_err(|_| E::from(RuntimeStateError::Unavailable))?
74}
75
76pub fn is_default_memory_manager_bootstrapped() -> Result<bool, RuntimeStateError> {
82 with_existing_default_runtime(|runtime| Ok(runtime.is_some_and(MemoryRuntime::is_bootstrapped)))
83}
84
85pub fn committed_allocations() -> Result<CommittedAllocations, RuntimeOpenError> {
91 with_existing_default_runtime(|runtime| {
92 runtime
93 .ok_or(RuntimeOpenError::NotBootstrapped)?
94 .committed_allocations()
95 .cloned()
96 })
97}
98
99pub fn default_memory_manager_memory_id(stable_key: &str) -> Result<u8, RuntimeOpenError> {
102 with_existing_default_runtime(|runtime| {
103 runtime
104 .ok_or(RuntimeOpenError::NotBootstrapped)?
105 .memory_id(stable_key)
106 })
107}
108
109pub fn verify_default_memory_manager_authority(
112 requirements: &crate::SealedDeclarationSnapshot,
113 authority: &str,
114) -> Result<(), super::RuntimeAdoptionError> {
115 with_existing_default_runtime(|runtime| {
116 runtime
117 .ok_or(RuntimeOpenError::NotBootstrapped)?
118 .verify_authority(requirements, authority)
119 })
120}
121
122pub fn bootstrap_default_memory_manager()
124-> Result<CommittedAllocations, RuntimeBootstrapError<Infallible>> {
125 bootstrap_default_memory_manager_with_policy(&GenericRangePolicy)
126}
127
128pub fn bootstrap_default_memory_manager_with_policy<P: RuntimeBootstrapPolicy>(
135 policy: &P,
136) -> Result<CommittedAllocations, RuntimeBootstrapError<P::Error>> {
137 let declarations = sealed_declaration_snapshot()?;
138 with_default_runtime_mut(None, |runtime| {
139 runtime.bootstrap(&declarations, policy).cloned()
140 })
141}
142
143pub fn open_default_memory_manager_memory(
146 stable_key: &str,
147 id: u8,
148) -> Result<RuntimeMemory<DefaultMemoryImpl>, RuntimeOpenError> {
149 with_existing_default_runtime(|runtime| {
150 runtime
151 .ok_or(RuntimeOpenError::NotBootstrapped)?
152 .open_memory(stable_key, id)
153 })
154}
155
156pub fn open_default_memory_manager_memory_by_key(
159 stable_key: &str,
160) -> Result<RuntimeMemory<DefaultMemoryImpl>, RuntimeOpenError> {
161 with_existing_default_runtime(|runtime| {
162 runtime
163 .ok_or(RuntimeOpenError::NotBootstrapped)?
164 .open_memory_by_key(stable_key)
165 })
166}
167
168pub fn default_memory_manager_diagnostic_export() -> Result<DiagnosticExport, RuntimeDiagnosticError>
173{
174 with_existing_default_runtime(|runtime| {
175 runtime
176 .ok_or(RuntimeDiagnosticError::NotBootstrapped)?
177 .diagnostic_export()
178 })
179}
180
181pub fn default_memory_manager_commit_recovery_diagnostic()
186-> Result<CommitStoreDiagnostic, RuntimeDiagnosticError> {
187 with_existing_default_runtime(|runtime| {
188 runtime
189 .ok_or(RuntimeDiagnosticError::NotBootstrapped)?
190 .commit_recovery_diagnostic()
191 })
192}
193
194pub fn default_memory_manager_doctor_report()
199-> Result<MemoryRuntimeDoctorReport, RuntimeDiagnosticError> {
200 default_memory_manager_doctor_report_with_policy(&GenericRangePolicy)
201}
202
203pub fn default_memory_manager_doctor_report_with_policy<P>(
209 policy: &P,
210) -> Result<MemoryRuntimeDoctorReport, RuntimeDiagnosticError>
211where
212 P: RuntimeBootstrapPolicy,
213 P::Error: Display,
214{
215 with_existing_default_runtime(|runtime| {
218 runtime
219 .ok_or(RuntimeDiagnosticError::NotBootstrapped)
220 .map(|_| ())
221 })?;
222 let declarations = sealed_declaration_snapshot()?;
223 with_existing_default_runtime(|runtime| {
224 Ok(runtime
225 .ok_or(RuntimeDiagnosticError::NotBootstrapped)?
226 .doctor_report(&declarations, policy))
227 })
228}
229
230#[cfg(test)]
231pub(super) fn with_default_runtime_borrowed(
232 operation: impl FnOnce() -> Result<(), RuntimeStateError>,
233) -> Result<(), RuntimeStateError> {
234 DEFAULT_RUNTIME.with(|runtime| {
235 let _borrow = runtime.borrow_mut();
236 operation()
237 })
238}
239
240pub fn default_memory_manager_memory_allocations()
246-> Result<super::MemoryAllocations, RuntimeDiagnosticError> {
247 with_existing_default_runtime(|runtime| {
248 runtime
249 .ok_or(RuntimeDiagnosticError::NotBootstrapped)?
250 .memory_allocations()
251 })
252}
253
254pub fn default_memory_manager_memory_allocation_summary()
259-> Result<super::MemoryAllocationSummary, RuntimeDiagnosticError> {
260 with_existing_default_runtime(|runtime| {
261 runtime
262 .ok_or(RuntimeDiagnosticError::NotBootstrapped)?
263 .memory_allocation_summary()
264 })
265}
266
267pub fn bootstrap_default_memory_manager_with_config<P: RuntimeBootstrapPolicy>(
279 config: MemoryManagerConfig,
280 policy: &P,
281) -> Result<CommittedAllocations, RuntimeBootstrapError<P::Error>> {
282 with_default_runtime_mut(Some(config), |_| Ok::<_, RuntimeStateError>(()))?;
285 let declarations = sealed_declaration_snapshot()?;
286 with_default_runtime_mut(Some(config), |runtime| {
287 runtime.bootstrap(&declarations, policy).cloned()
288 })
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294
295 #[test]
296 fn configured_registration_hooks_can_observe_unbootstrapped_runtime() {
297 use crate::registry::{
298 TEST_REGISTRY_LOCK, defer_eager_init, reset_static_memory_declarations_for_tests,
299 };
300 let _guard = TEST_REGISTRY_LOCK.lock().unwrap();
301 reset_static_memory_declarations_for_tests();
302 defer_eager_init(|| {
303 assert_eq!(is_default_memory_manager_bootstrapped(), Ok(false));
304 assert_eq!(
305 committed_allocations(),
306 Err(RuntimeOpenError::NotBootstrapped)
307 );
308 let summary = default_memory_manager_memory_allocation_summary().unwrap();
309 assert_eq!(summary.bucket_size_pages, 16);
310 assert_eq!(summary.current_generation, None);
311 });
312 std::thread::spawn(|| {
313 let config = super::super::MemoryManagerConfig::new(16).unwrap();
314 bootstrap_default_memory_manager_with_config(config, &GenericRangePolicy).unwrap();
315 assert_eq!(is_default_memory_manager_bootstrapped(), Ok(true));
316 })
317 .join()
318 .unwrap();
319 reset_static_memory_declarations_for_tests();
320 }
321
322 fn diagnostic_observations() -> [Result<(), RuntimeDiagnosticError>; 4] {
323 [
324 default_memory_manager_diagnostic_export().map(|_| ()),
325 default_memory_manager_commit_recovery_diagnostic().map(|_| ()),
326 default_memory_manager_doctor_report().map(|_| ()),
327 default_memory_manager_doctor_report_with_policy(&GenericRangePolicy).map(|_| ()),
328 ]
329 }
330
331 #[test]
332 fn observations_leave_an_absent_runtime_absent() {
333 use crate::registry::{
334 TEST_REGISTRY_LOCK, defer_eager_init, reset_static_memory_declarations_for_tests,
335 };
336 use std::sync::atomic::{AtomicBool, Ordering};
337 static REGISTRATION_RAN: AtomicBool = AtomicBool::new(false);
338 let _guard = TEST_REGISTRY_LOCK.lock().unwrap();
339 reset_static_memory_declarations_for_tests();
340 defer_eager_init(|| REGISTRATION_RAN.store(true, Ordering::SeqCst));
341 std::thread::spawn(|| {
342 for _ in 0..2 {
343 assert!(!is_default_memory_manager_bootstrapped().unwrap());
344 assert_eq!(
345 committed_allocations(),
346 Err(RuntimeOpenError::NotBootstrapped)
347 );
348 assert!(matches!(
349 default_memory_manager_memory_allocations(),
350 Err(RuntimeDiagnosticError::NotBootstrapped)
351 ));
352 for result in diagnostic_observations() {
353 assert!(matches!(
354 result,
355 Err(RuntimeDiagnosticError::NotBootstrapped)
356 ));
357 }
358 DEFAULT_RUNTIME.with(|runtime| assert!(runtime.borrow().is_none()));
359 }
360 })
361 .join()
362 .unwrap();
363 assert!(!REGISTRATION_RAN.load(Ordering::SeqCst));
364 reset_static_memory_declarations_for_tests();
365 }
366
367 #[test]
368 fn observations_preserve_unbootstrapped_configuration() {
369 use crate::registry::{TEST_REGISTRY_LOCK, reset_static_memory_declarations_for_tests};
370 let _guard = TEST_REGISTRY_LOCK.lock().unwrap();
371 reset_static_memory_declarations_for_tests();
372 std::thread::spawn(|| {
373 let config = super::super::MemoryManagerConfig::new(16).unwrap();
374 DEFAULT_RUNTIME.with(|runtime| {
375 *runtime.borrow_mut() = Some(MemoryRuntime::new_with_config(
376 DefaultMemoryImpl::default(),
377 config,
378 ));
379 });
380 let before = default_memory_manager_memory_allocations().unwrap();
381 assert!(!is_default_memory_manager_bootstrapped().unwrap());
382 assert_eq!(
383 committed_allocations(),
384 Err(RuntimeOpenError::NotBootstrapped)
385 );
386 assert_eq!(before.bucket_size_pages, 16);
387 assert!(matches!(
388 default_memory_manager_diagnostic_export(),
389 Err(RuntimeDiagnosticError::NotBootstrapped)
390 ));
391 default_memory_manager_commit_recovery_diagnostic().unwrap();
392 assert!(!default_memory_manager_doctor_report().unwrap().bootstrapped);
393 assert!(
394 !default_memory_manager_doctor_report_with_policy(&GenericRangePolicy)
395 .unwrap()
396 .bootstrapped
397 );
398 assert_eq!(default_memory_manager_memory_allocations().unwrap(), before);
399 })
400 .join()
401 .unwrap();
402 reset_static_memory_declarations_for_tests();
403 }
404
405 #[test]
406 fn observations_preserve_cached_construction_failure() {
407 std::thread::spawn(|| {
408 let error = RuntimeConstructionError::ForeignMemory {
409 observed_magic: *b"BAD",
410 };
411 DEFAULT_RUNTIME.with(|runtime| *runtime.borrow_mut() = Some(Err(error)));
412 for result in diagnostic_observations() {
413 assert!(matches!(result, Err(RuntimeDiagnosticError::State(RuntimeStateError::Construction(cause))) if cause == error));
414 }
415 assert_eq!(
416 is_default_memory_manager_bootstrapped(),
417 Err(RuntimeStateError::Construction(error))
418 );
419 assert_eq!(
420 committed_allocations(),
421 Err(RuntimeOpenError::State(RuntimeStateError::Construction(
422 error
423 )))
424 );
425 assert!(matches!(
426 default_memory_manager_memory_allocations(),
427 Err(RuntimeDiagnosticError::State(RuntimeStateError::Construction(cause)))
428 if cause == error
429 ));
430 for result in [
431 open_default_memory_manager_memory_by_key("app.rows.v1").err(),
432 open_default_memory_manager_memory("app.rows.v1", 100).err(),
433 default_memory_manager_memory_id("app.rows.v1").err(),
434 ] {
435 assert_eq!(result, Some(RuntimeOpenError::State(RuntimeStateError::Construction(error))));
436 }
437 let requirements = crate::SealedDeclarationSnapshot::new(&[], &[], &[]).unwrap();
438 assert_eq!(verify_default_memory_manager_authority(&requirements, "app"), Err(super::super::RuntimeAdoptionError::Open(RuntimeOpenError::State(RuntimeStateError::Construction(error)))));
439 assert!(matches!(default_memory_manager_memory_allocation_summary(), Err(RuntimeDiagnosticError::State(RuntimeStateError::Construction(cause))) if cause == error));
440 DEFAULT_RUNTIME.with(|runtime| {
441 assert!(matches!(runtime.borrow().as_ref(), Some(Err(cause)) if *cause == error));
442 });
443 })
444 .join()
445 .unwrap();
446 }
447
448 #[test]
449 fn diagnostic_observations_preserve_reentrant_access_errors() {
450 with_default_runtime_borrowed(|| {
451 for result in diagnostic_observations() {
452 assert!(matches!(
453 result,
454 Err(RuntimeDiagnosticError::State(
455 RuntimeStateError::ReentrantAccess
456 ))
457 ));
458 }
459 Ok(())
460 })
461 .unwrap();
462 }
463 #[test]
464 #[cfg(not(target_arch = "wasm32"))]
465 fn configured_default_prepares_once_and_warm_library_adoption_only_opens() {
466 use crate::registry::{TEST_REGISTRY_LOCK, reset_static_memory_declarations_for_tests};
467 use ic_stable_structures::Memory;
468 let _guard = TEST_REGISTRY_LOCK.lock().unwrap();
469 reset_static_memory_declarations_for_tests();
470 crate::register_static_memory_manager_range(
471 100,
472 110,
473 "app",
474 crate::MemoryManagerRangeMode::Allowed,
475 None,
476 )
477 .unwrap();
478 crate::register_memory_request(
479 crate::MemoryRequest::new(
480 "app",
481 "app.main.control.v1",
482 crate::SchemaMetadata::default(),
483 )
484 .unwrap(),
485 )
486 .unwrap();
487 let backing = super::super::admission_tests::seeded();
488 DEFAULT_RUNTIME
489 .with(|runtime| *runtime.borrow_mut() = Some(MemoryRuntime::new(backing.clone())));
490 let policy = super::super::admission_tests::AdmissionPolicy {
491 discover: true,
492 ..Default::default()
493 };
494 let config = super::super::MemoryManagerConfig::new(1).unwrap();
495 let committed = bootstrap_default_memory_manager_with_config(config, &policy).unwrap();
496 assert_eq!(committed.generation(), 2);
497 let before = backing.borrow().clone();
498 let mut marker = [0; 12];
499 open_default_memory_manager_memory_by_key("app.old.journal.v1")
500 .unwrap()
501 .read(0, &mut marker);
502 assert_eq!(&marker, b"pending/debt");
503 assert_eq!(committed_allocations().unwrap(), committed);
504 assert_eq!(
505 bootstrap_default_memory_manager_with_config(config, &policy).unwrap(),
506 committed
507 );
508 assert!(
509 bootstrap_default_memory_manager_with_config(
510 super::super::MemoryManagerConfig::new(2).unwrap(),
511 &policy
512 )
513 .is_err()
514 );
515 assert_eq!(policy.calls.get(), 1);
516 assert_eq!(*backing.borrow(), before);
517 assert_eq!(
518 default_memory_manager_memory_allocations()
519 .unwrap()
520 .bucket_size_pages,
521 1
522 );
523 DEFAULT_RUNTIME.with(|runtime| *runtime.borrow_mut() = None);
524 reset_static_memory_declarations_for_tests();
525 }
526}