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<T, E>(
19 operation: impl FnOnce(&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_ref()
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_default_runtime_mut<T, E>(
40 operation: impl FnOnce(&mut MemoryRuntime<DefaultMemoryImpl>) -> Result<T, E>,
41) -> Result<T, E>
42where
43 E: From<RuntimeStateError>,
44{
45 match DEFAULT_RUNTIME.try_with(|runtime| {
46 let mut runtime = runtime
47 .try_borrow_mut()
48 .map_err(|_| E::from(RuntimeStateError::ReentrantAccess))?;
49 let runtime = runtime
50 .get_or_insert_with(|| MemoryRuntime::new(DefaultMemoryImpl::default()))
51 .as_mut()
52 .map_err(|error| E::from(RuntimeStateError::Construction(*error)))?;
53 operation(runtime)
54 }) {
55 Ok(result) => result,
56 Err(_) => Err(E::from(RuntimeStateError::Unavailable)),
57 }
58}
59
60fn with_existing_default_runtime<T, E>(
63 operation: impl FnOnce(Option<&MemoryRuntime<DefaultMemoryImpl>>) -> Result<T, E>,
64) -> Result<T, E>
65where
66 E: From<RuntimeStateError>,
67{
68 DEFAULT_RUNTIME
69 .try_with(|runtime| {
70 let runtime = runtime
71 .try_borrow()
72 .map_err(|_| E::from(RuntimeStateError::ReentrantAccess))?;
73 let existing = runtime
74 .as_ref()
75 .map(|runtime| {
76 runtime
77 .as_ref()
78 .map_err(|error| E::from(RuntimeStateError::Construction(*error)))
79 })
80 .transpose()?;
81 operation(existing)
82 })
83 .map_err(|_| E::from(RuntimeStateError::Unavailable))?
84}
85
86pub fn is_default_memory_manager_bootstrapped() -> Result<bool, RuntimeStateError> {
92 with_existing_default_runtime(|runtime| Ok(runtime.is_some_and(MemoryRuntime::is_bootstrapped)))
93}
94
95pub fn committed_allocations() -> Result<CommittedAllocations, RuntimeOpenError> {
101 with_existing_default_runtime(|runtime| {
102 runtime
103 .ok_or(RuntimeOpenError::NotBootstrapped)?
104 .committed_allocations()
105 .cloned()
106 })
107}
108
109pub fn default_memory_manager_memory_id(stable_key: &str) -> Result<u8, RuntimeOpenError> {
112 with_existing_default_runtime(|runtime| {
113 runtime
114 .ok_or(RuntimeOpenError::NotBootstrapped)?
115 .memory_id(stable_key)
116 })
117}
118
119pub fn verify_default_memory_manager_authority(
122 requirements: &crate::SealedDeclarationSnapshot,
123 authority: &str,
124) -> Result<(), super::RuntimeAdoptionError> {
125 with_existing_default_runtime(|runtime| {
126 runtime
127 .ok_or(RuntimeOpenError::NotBootstrapped)?
128 .verify_authority(requirements, authority)
129 })
130}
131
132pub fn bootstrap_default_memory_manager()
134-> Result<CommittedAllocations, RuntimeBootstrapError<Infallible>> {
135 bootstrap_default_memory_manager_with_policy(&GenericRangePolicy)
136}
137
138pub fn bootstrap_default_memory_manager_with_policy<P: RuntimeBootstrapPolicy>(
145 policy: &P,
146) -> Result<CommittedAllocations, RuntimeBootstrapError<P::Error>> {
147 let declarations = sealed_declaration_snapshot()?;
148 with_default_runtime_mut(|runtime| runtime.bootstrap(&declarations, policy).cloned())
149}
150
151pub fn open_default_memory_manager_memory(
154 stable_key: &str,
155 id: u8,
156) -> Result<RuntimeMemory<DefaultMemoryImpl>, RuntimeOpenError> {
157 with_existing_default_runtime(|runtime| {
158 runtime
159 .ok_or(RuntimeOpenError::NotBootstrapped)?
160 .open_memory(stable_key, id)
161 })
162}
163
164pub fn open_default_memory_manager_memory_by_key(
167 stable_key: &str,
168) -> Result<RuntimeMemory<DefaultMemoryImpl>, RuntimeOpenError> {
169 with_existing_default_runtime(|runtime| {
170 runtime
171 .ok_or(RuntimeOpenError::NotBootstrapped)?
172 .open_memory_by_key(stable_key)
173 })
174}
175
176pub fn default_memory_manager_diagnostic_export() -> Result<DiagnosticExport, RuntimeDiagnosticError>
178{
179 with_default_runtime(MemoryRuntime::diagnostic_export)
180}
181
182pub fn default_memory_manager_commit_recovery_diagnostic()
184-> Result<CommitStoreDiagnostic, RuntimeDiagnosticError> {
185 with_default_runtime(MemoryRuntime::commit_recovery_diagnostic)
186}
187
188pub fn default_memory_manager_doctor_report()
190-> Result<MemoryRuntimeDoctorReport, RuntimeDiagnosticError> {
191 default_memory_manager_doctor_report_with_policy(&GenericRangePolicy)
192}
193
194pub 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 let declarations = sealed_declaration_snapshot()?;
203 with_default_runtime(|runtime| Ok(runtime.doctor_report(&declarations, policy)))
204}
205
206#[cfg(test)]
207pub(super) fn with_default_runtime_borrowed(
208 operation: impl FnOnce() -> Result<(), RuntimeStateError>,
209) -> Result<(), RuntimeStateError> {
210 DEFAULT_RUNTIME.with(|runtime| {
211 let _borrow = runtime.borrow_mut();
212 operation()
213 })
214}
215
216pub fn default_memory_manager_memory_allocations()
222-> Result<super::MemoryAllocations, RuntimeDiagnosticError> {
223 with_existing_default_runtime(|runtime| {
224 runtime
225 .ok_or(RuntimeDiagnosticError::NotBootstrapped)?
226 .memory_allocations()
227 })
228}
229
230pub fn default_memory_manager_memory_allocation_summary()
235-> Result<super::MemoryAllocationSummary, RuntimeDiagnosticError> {
236 with_existing_default_runtime(|runtime| {
237 runtime
238 .ok_or(RuntimeDiagnosticError::NotBootstrapped)?
239 .memory_allocation_summary()
240 })
241}
242
243pub fn bootstrap_default_memory_manager_with_config<P: RuntimeBootstrapPolicy>(
252 config: super::MemoryManagerConfig,
253 policy: &P,
254) -> Result<CommittedAllocations, RuntimeBootstrapError<P::Error>> {
255 DEFAULT_RUNTIME
256 .try_with(|runtime| {
257 let mut runtime = runtime
258 .try_borrow_mut()
259 .map_err(|_| RuntimeStateError::ReentrantAccess)?;
260 let runtime = runtime
261 .get_or_insert_with(|| {
262 MemoryRuntime::new_with_config(DefaultMemoryImpl::default(), config)
263 })
264 .as_mut()
265 .map_err(|error| RuntimeStateError::Construction(*error))?;
266 super::check_bucket_size(runtime.bucket_size_pages, config)
267 .map_err(RuntimeStateError::Construction)?;
268 let declarations = sealed_declaration_snapshot()?;
269 runtime.bootstrap(&declarations, policy).cloned()
270 })
271 .map_err(|_| RuntimeStateError::Unavailable)?
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
279 fn observations_leave_an_absent_runtime_absent() {
280 std::thread::spawn(|| {
281 for _ in 0..2 {
282 assert!(!is_default_memory_manager_bootstrapped().unwrap());
283 assert_eq!(
284 committed_allocations(),
285 Err(RuntimeOpenError::NotBootstrapped)
286 );
287 assert!(matches!(
288 default_memory_manager_memory_allocations(),
289 Err(RuntimeDiagnosticError::NotBootstrapped)
290 ));
291 DEFAULT_RUNTIME.with(|runtime| assert!(runtime.borrow().is_none()));
292 }
293 })
294 .join()
295 .unwrap();
296 }
297
298 #[test]
299 fn observations_preserve_unbootstrapped_configuration() {
300 std::thread::spawn(|| {
301 let config = super::super::MemoryManagerConfig::new(16).unwrap();
302 DEFAULT_RUNTIME.with(|runtime| {
303 *runtime.borrow_mut() = Some(MemoryRuntime::new_with_config(
304 DefaultMemoryImpl::default(),
305 config,
306 ));
307 });
308 let before = default_memory_manager_memory_allocations().unwrap();
309 assert!(!is_default_memory_manager_bootstrapped().unwrap());
310 assert_eq!(
311 committed_allocations(),
312 Err(RuntimeOpenError::NotBootstrapped)
313 );
314 assert_eq!(before.bucket_size_pages, 16);
315 assert_eq!(default_memory_manager_memory_allocations().unwrap(), before);
316 })
317 .join()
318 .unwrap();
319 }
320
321 #[test]
322 fn observations_preserve_cached_construction_failure() {
323 std::thread::spawn(|| {
324 let error = RuntimeConstructionError::ForeignMemory {
325 observed_magic: *b"BAD",
326 };
327 DEFAULT_RUNTIME.with(|runtime| *runtime.borrow_mut() = Some(Err(error)));
328 assert_eq!(
329 is_default_memory_manager_bootstrapped(),
330 Err(RuntimeStateError::Construction(error))
331 );
332 assert_eq!(
333 committed_allocations(),
334 Err(RuntimeOpenError::State(RuntimeStateError::Construction(
335 error
336 )))
337 );
338 assert!(matches!(
339 default_memory_manager_memory_allocations(),
340 Err(RuntimeDiagnosticError::State(RuntimeStateError::Construction(cause)))
341 if cause == error
342 ));
343 for result in [
344 open_default_memory_manager_memory_by_key("app.rows.v1").err(),
345 open_default_memory_manager_memory("app.rows.v1", 100).err(),
346 default_memory_manager_memory_id("app.rows.v1").err(),
347 ] {
348 assert_eq!(result, Some(RuntimeOpenError::State(RuntimeStateError::Construction(error))));
349 }
350 let requirements = crate::SealedDeclarationSnapshot::new(&[], &[], &[]).unwrap();
351 assert_eq!(verify_default_memory_manager_authority(&requirements, "app"), Err(super::super::RuntimeAdoptionError::Open(RuntimeOpenError::State(RuntimeStateError::Construction(error)))));
352 assert!(matches!(default_memory_manager_memory_allocation_summary(), Err(RuntimeDiagnosticError::State(RuntimeStateError::Construction(cause))) if cause == error));
353 DEFAULT_RUNTIME.with(|runtime| {
354 assert!(matches!(runtime.borrow().as_ref(), Some(Err(cause)) if *cause == error));
355 });
356 })
357 .join()
358 .unwrap();
359 }
360 #[test]
361 #[cfg(not(target_arch = "wasm32"))]
362 fn configured_default_prepares_once_and_warm_library_adoption_only_opens() {
363 use crate::registry::{TEST_REGISTRY_LOCK, reset_static_memory_declarations_for_tests};
364 use ic_stable_structures::Memory;
365 let _guard = TEST_REGISTRY_LOCK.lock().unwrap();
366 reset_static_memory_declarations_for_tests();
367 crate::register_static_memory_manager_range(
368 100,
369 110,
370 "app",
371 crate::MemoryManagerRangeMode::Allowed,
372 None,
373 )
374 .unwrap();
375 crate::register_memory_request(
376 crate::MemoryRequest::new(
377 "app",
378 "app.main.control.v1",
379 crate::SchemaMetadata::default(),
380 )
381 .unwrap(),
382 )
383 .unwrap();
384 let backing = super::super::admission_tests::seeded();
385 DEFAULT_RUNTIME
386 .with(|runtime| *runtime.borrow_mut() = Some(MemoryRuntime::new(backing.clone())));
387 let policy = super::super::admission_tests::AdmissionPolicy {
388 discover: true,
389 ..Default::default()
390 };
391 let config = super::super::MemoryManagerConfig::new(1).unwrap();
392 let committed = bootstrap_default_memory_manager_with_config(config, &policy).unwrap();
393 assert_eq!(committed.generation(), 2);
394 let before = backing.borrow().clone();
395 let mut marker = [0; 12];
396 open_default_memory_manager_memory_by_key("app.old.journal.v1")
397 .unwrap()
398 .read(0, &mut marker);
399 assert_eq!(&marker, b"pending/debt");
400 assert_eq!(committed_allocations().unwrap(), committed);
401 assert_eq!(
402 bootstrap_default_memory_manager_with_config(config, &policy).unwrap(),
403 committed
404 );
405 assert!(
406 bootstrap_default_memory_manager_with_config(
407 super::super::MemoryManagerConfig::new(2).unwrap(),
408 &policy
409 )
410 .is_err()
411 );
412 assert_eq!(policy.calls.get(), 1);
413 assert_eq!(*backing.borrow(), before);
414 assert_eq!(
415 default_memory_manager_memory_allocations()
416 .unwrap()
417 .bucket_size_pages,
418 1
419 );
420 DEFAULT_RUNTIME.with(|runtime| *runtime.borrow_mut() = None);
421 reset_static_memory_declarations_for_tests();
422 }
423}