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