1use super::{
2 MemoryManagerConfig, MemoryRuntime, RuntimeBootstrapError, RuntimeConstructionError,
3 RuntimeDiagnosticError, RuntimeMemory, RuntimeOpenError, RuntimeStateError,
4 policy::GenericAllocationPolicy,
5};
6use crate::{
7 CommittedAllocations, DiagnosticExport, MemoryRuntimeDoctorReport, RuntimeBootstrapPolicy,
8 SealedDeclarationSnapshot, physical::CommitStoreDiagnostic,
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.growth.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 declarations: &SealedDeclarationSnapshot,
125 pool: &crate::MemoryAllocationPool,
126) -> Result<CommittedAllocations, RuntimeBootstrapError<Infallible>> {
127 bootstrap_default_memory_manager_with_policy(declarations, pool, &GenericAllocationPolicy)
128}
129
130pub fn bootstrap_default_memory_manager_with_policy<P: RuntimeBootstrapPolicy>(
137 declarations: &SealedDeclarationSnapshot,
138 pool: &crate::MemoryAllocationPool,
139 policy: &P,
140) -> Result<CommittedAllocations, RuntimeBootstrapError<P::Error>> {
141 with_default_runtime_mut(None, |runtime| {
142 runtime.bootstrap(declarations, pool, policy).cloned()
143 })
144}
145
146pub fn open_default_memory_manager_memory(
149 stable_key: &str,
150) -> Result<RuntimeMemory<DefaultMemoryImpl>, RuntimeOpenError> {
151 with_existing_default_runtime(|runtime| {
152 runtime
153 .ok_or(RuntimeOpenError::NotBootstrapped)?
154 .open_memory(stable_key)
155 })
156}
157
158pub fn default_memory_manager_diagnostic_export() -> Result<DiagnosticExport, RuntimeDiagnosticError>
163{
164 with_existing_default_runtime(|runtime| {
165 runtime
166 .ok_or(RuntimeDiagnosticError::NotBootstrapped)?
167 .diagnostic_export()
168 })
169}
170
171pub fn default_memory_manager_commit_recovery_diagnostic()
176-> Result<CommitStoreDiagnostic, RuntimeDiagnosticError> {
177 with_existing_default_runtime(|runtime| {
178 runtime
179 .ok_or(RuntimeDiagnosticError::NotBootstrapped)?
180 .commit_recovery_diagnostic()
181 })
182}
183
184pub fn default_memory_manager_doctor_report(
189 declarations: &SealedDeclarationSnapshot,
190 pool: &crate::MemoryAllocationPool,
191) -> Result<MemoryRuntimeDoctorReport, RuntimeDiagnosticError> {
192 default_memory_manager_doctor_report_with_policy(declarations, pool, &GenericAllocationPolicy)
193}
194
195pub fn default_memory_manager_doctor_report_with_policy<P>(
201 declarations: &SealedDeclarationSnapshot,
202 pool: &crate::MemoryAllocationPool,
203 policy: &P,
204) -> Result<MemoryRuntimeDoctorReport, RuntimeDiagnosticError>
205where
206 P: RuntimeBootstrapPolicy,
207 P::Error: Display,
208{
209 with_existing_default_runtime(|runtime| {
210 Ok(runtime
211 .ok_or(RuntimeDiagnosticError::NotBootstrapped)?
212 .doctor_report(declarations, pool, policy))
213 })
214}
215
216#[cfg(test)]
217pub(super) fn with_default_runtime_borrowed(
218 operation: impl FnOnce() -> Result<(), RuntimeStateError>,
219) -> Result<(), RuntimeStateError> {
220 DEFAULT_RUNTIME.with(|runtime| {
221 let _borrow = runtime.borrow_mut();
222 operation()
223 })
224}
225
226pub fn default_memory_manager_memory_allocations()
232-> Result<super::MemoryAllocations, RuntimeDiagnosticError> {
233 with_existing_default_runtime(|runtime| {
234 runtime
235 .ok_or(RuntimeDiagnosticError::NotBootstrapped)?
236 .memory_allocations()
237 })
238}
239
240pub fn default_memory_manager_memory_allocation_summary()
245-> Result<super::MemoryAllocationSummary, RuntimeDiagnosticError> {
246 with_existing_default_runtime(|runtime| {
247 runtime
248 .ok_or(RuntimeDiagnosticError::NotBootstrapped)?
249 .memory_allocation_summary()
250 })
251}
252
253pub fn bootstrap_default_memory_manager_with_config<P: RuntimeBootstrapPolicy>(
263 declarations: &SealedDeclarationSnapshot,
264 config: MemoryManagerConfig,
265 pool: &crate::MemoryAllocationPool,
266 policy: &P,
267) -> Result<CommittedAllocations, RuntimeBootstrapError<P::Error>> {
268 with_default_runtime_mut(Some(config), |runtime| {
269 runtime.bootstrap(declarations, pool, policy).cloned()
270 })
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 fn snapshot() -> crate::SealedDeclarationSnapshot {
277 crate::SealedDeclarationSnapshot::new(vec![]).unwrap()
278 }
279 fn pool() -> crate::MemoryAllocationPool {
280 crate::MemoryAllocationPool::new(
281 vec![crate::MemoryAuthority::new("app", "app.").unwrap()],
282 vec![],
283 )
284 .unwrap()
285 }
286
287 fn diagnostic_observations() -> [Result<(), RuntimeDiagnosticError>; 4] {
288 [
289 default_memory_manager_diagnostic_export().map(|_| ()),
290 default_memory_manager_commit_recovery_diagnostic().map(|_| ()),
291 default_memory_manager_doctor_report(&snapshot(), &pool()).map(|_| ()),
292 default_memory_manager_doctor_report_with_policy(
293 &snapshot(),
294 &pool(),
295 &GenericAllocationPolicy,
296 )
297 .map(|_| ()),
298 ]
299 }
300
301 #[test]
302 fn observations_leave_an_absent_runtime_absent() {
303 std::thread::spawn(|| {
304 for _ in 0..2 {
305 assert!(!is_default_memory_manager_bootstrapped().unwrap());
306 assert_eq!(
307 committed_allocations(),
308 Err(RuntimeOpenError::NotBootstrapped)
309 );
310 assert!(matches!(
311 default_memory_manager_memory_allocations(),
312 Err(RuntimeDiagnosticError::NotBootstrapped)
313 ));
314 for result in diagnostic_observations() {
315 assert!(matches!(
316 result,
317 Err(RuntimeDiagnosticError::NotBootstrapped)
318 ));
319 }
320 DEFAULT_RUNTIME.with(|runtime| assert!(runtime.borrow().is_none()));
321 }
322 })
323 .join()
324 .unwrap();
325 }
326
327 #[test]
328 fn observations_preserve_unbootstrapped_configuration() {
329 std::thread::spawn(|| {
330 let config = super::super::MemoryManagerConfig::new(16).unwrap();
331 DEFAULT_RUNTIME.with(|runtime| {
332 *runtime.borrow_mut() = Some(MemoryRuntime::new_with_config(
333 DefaultMemoryImpl::default(),
334 config,
335 ));
336 });
337 let before = default_memory_manager_memory_allocations().unwrap();
338 assert!(!is_default_memory_manager_bootstrapped().unwrap());
339 assert_eq!(
340 committed_allocations(),
341 Err(RuntimeOpenError::NotBootstrapped)
342 );
343 assert_eq!(before.bucket_size_pages, 16);
344 assert!(matches!(
345 default_memory_manager_diagnostic_export(),
346 Err(RuntimeDiagnosticError::NotBootstrapped)
347 ));
348 default_memory_manager_commit_recovery_diagnostic().unwrap();
349 assert!(
350 !default_memory_manager_doctor_report(&snapshot(), &pool())
351 .unwrap()
352 .bootstrapped
353 );
354 assert!(
355 !default_memory_manager_doctor_report_with_policy(
356 &snapshot(),
357 &pool(),
358 &GenericAllocationPolicy
359 )
360 .unwrap()
361 .bootstrapped
362 );
363 assert_eq!(default_memory_manager_memory_allocations().unwrap(), before);
364 })
365 .join()
366 .unwrap();
367 }
368
369 #[test]
370 fn configured_bootstrap_propagates_metadata_growth_refusal() {
371 std::thread::spawn(|| {
372 let error = RuntimeConstructionError::Growth(super::super::RuntimeGrowError::BackingRefused { additional_pages: 1 });
373 DEFAULT_RUNTIME.with(|runtime| *runtime.borrow_mut() = Some(Err(error)));
374 assert!(matches!(
375 bootstrap_default_memory_manager_with_config(&snapshot(), MemoryManagerConfig::new(16).unwrap(), &pool(), &GenericAllocationPolicy),
376 Err(RuntimeBootstrapError::State(RuntimeStateError::Construction(cause))) if cause == error
377 ));
378 assert_eq!(is_default_memory_manager_bootstrapped(), Err(RuntimeStateError::Construction(error)));
379 DEFAULT_RUNTIME.with(|runtime| assert!(matches!(runtime.borrow().as_ref(), Some(Err(cause)) if *cause == error)));
380 }).join().unwrap();
381 }
382
383 #[test]
384 fn observations_preserve_cached_construction_failure() {
385 std::thread::spawn(|| {
386 let error = RuntimeConstructionError::ForeignMemory {
387 observed_magic: *b"BAD",
388 };
389 DEFAULT_RUNTIME.with(|runtime| *runtime.borrow_mut() = Some(Err(error)));
390 for result in diagnostic_observations() {
391 assert!(matches!(result, Err(RuntimeDiagnosticError::State(RuntimeStateError::Construction(cause))) if cause == error));
392 }
393 assert_eq!(
394 is_default_memory_manager_bootstrapped(),
395 Err(RuntimeStateError::Construction(error))
396 );
397 assert_eq!(
398 committed_allocations(),
399 Err(RuntimeOpenError::State(RuntimeStateError::Construction(
400 error
401 )))
402 );
403 assert!(matches!(
404 default_memory_manager_memory_allocations(),
405 Err(RuntimeDiagnosticError::State(RuntimeStateError::Construction(cause)))
406 if cause == error
407 ));
408 for result in [
409 open_default_memory_manager_memory("app.rows.v1").err(),
410 default_memory_manager_memory_id("app.rows.v1").err(),
411 ] {
412 assert_eq!(result, Some(RuntimeOpenError::State(RuntimeStateError::Construction(error))));
413 }
414 let requirements = crate::SealedDeclarationSnapshot::new(vec![]).unwrap();
415 assert_eq!(verify_default_memory_manager_authority(&requirements, "app"), Err(super::super::RuntimeAdoptionError::Open(RuntimeOpenError::State(RuntimeStateError::Construction(error)))));
416 assert!(matches!(default_memory_manager_memory_allocation_summary(), Err(RuntimeDiagnosticError::State(RuntimeStateError::Construction(cause))) if cause == error));
417 DEFAULT_RUNTIME.with(|runtime| {
418 assert!(matches!(runtime.borrow().as_ref(), Some(Err(cause)) if *cause == error));
419 });
420 })
421 .join()
422 .unwrap();
423 }
424
425 #[test]
426 fn diagnostic_observations_preserve_reentrant_access_errors() {
427 with_default_runtime_borrowed(|| {
428 for result in diagnostic_observations() {
429 assert!(matches!(
430 result,
431 Err(RuntimeDiagnosticError::State(
432 RuntimeStateError::ReentrantAccess
433 ))
434 ));
435 }
436 Ok(())
437 })
438 .unwrap();
439 }
440 #[test]
441 #[cfg(not(target_arch = "wasm32"))]
442 fn configured_default_prepares_once_and_warm_library_adoption_only_opens() {
443 use ic_stable_structures::Memory;
444 let declarations = crate::SealedDeclarationSnapshot::new(vec![
445 crate::MemoryRequest::new(
446 "app",
447 "app.main.control.v1",
448 crate::SchemaMetadata::default(),
449 )
450 .unwrap(),
451 ])
452 .unwrap();
453 let backing = super::super::admission_tests::seeded();
454 DEFAULT_RUNTIME
455 .with(|runtime| *runtime.borrow_mut() = Some(MemoryRuntime::new(backing.clone())));
456 let policy = super::super::admission_tests::AdmissionPolicy {
457 discover: true,
458 ..Default::default()
459 };
460 let config = super::super::MemoryManagerConfig::new(1).unwrap();
461 let committed =
462 bootstrap_default_memory_manager_with_config(&declarations, config, &pool(), &policy)
463 .unwrap();
464 assert_eq!(committed.generation(), 2);
465 let before = backing.borrow().clone();
466 let mut marker = [0; 12];
467 open_default_memory_manager_memory("app.old.journal.v1")
468 .unwrap()
469 .read(0, &mut marker);
470 assert_eq!(&marker, b"pending/debt");
471 assert_eq!(committed_allocations().unwrap(), committed);
472 assert_eq!(
473 bootstrap_default_memory_manager_with_config(&declarations, config, &pool(), &policy)
474 .unwrap(),
475 committed
476 );
477 assert!(
478 bootstrap_default_memory_manager_with_config(
479 &declarations,
480 super::super::MemoryManagerConfig::new(2).unwrap(),
481 &pool(),
482 &policy
483 )
484 .is_err()
485 );
486 assert_eq!(policy.calls.get(), 1);
487 assert_eq!(*backing.borrow(), before);
488 assert_eq!(
489 default_memory_manager_memory_allocations()
490 .unwrap()
491 .bucket_size_pages,
492 1
493 );
494 DEFAULT_RUNTIME.with(|runtime| *runtime.borrow_mut() = None);
495 }
496}