1use candid::Principal;
2use pocket_ic::PocketIc;
3use std::{
4 panic::{AssertUnwindSafe, catch_unwind, resume_unwind},
5 sync::{Mutex, MutexGuard},
6};
7
8use super::{
9 CanisterSnapshotTarget, ControllerSnapshotError, ControllerSnapshots,
10 PocketIcCapturedSnapshotExt, PocketIcSnapshotExt, SnapshotRestoreFunding, transport,
11};
12
13pub struct CachedPocketIcBaseline<T> {
18 pocket_ic: PocketIc,
19 snapshots: ControllerSnapshots,
20 metadata: T,
21}
22
23pub struct CachedPocketIcBaselineGuard<'a, T> {
28 guard: MutexGuard<'a, Option<CachedPocketIcBaseline<T>>>,
29}
30
31enum CachedBaselineRestoreFailure {
32 DeadInstanceTransport,
33 Panic(Box<dyn std::any::Any + Send>),
34}
35
36fn acquire_cached_pocket_ic_baseline<T, F>(
38 slot: &'static Mutex<Option<CachedPocketIcBaseline<T>>>,
39 build: F,
40) -> (CachedPocketIcBaselineGuard<'static, T>, bool)
41where
42 F: FnOnce() -> CachedPocketIcBaseline<T>,
43{
44 let mut guard = slot
45 .lock()
46 .unwrap_or_else(std::sync::PoisonError::into_inner);
47 let cache_hit = guard.is_some();
48
49 if !cache_hit {
50 *guard = Some(build());
51 }
52
53 (CachedPocketIcBaselineGuard { guard }, cache_hit)
54}
55
56pub fn restore_or_rebuild_cached_pocket_ic_baseline<T, B, R>(
67 slot: &'static Mutex<Option<CachedPocketIcBaseline<T>>>,
68 build: B,
69 restore: R,
70) -> (CachedPocketIcBaselineGuard<'static, T>, bool)
71where
72 B: Fn() -> CachedPocketIcBaseline<T>,
73 R: Fn(&CachedPocketIcBaseline<T>),
74{
75 let (baseline, cache_hit) = acquire_cached_pocket_ic_baseline(slot, &build);
76 if !cache_hit {
77 return (baseline, false);
78 }
79
80 match try_restore_cached_pocket_ic_baseline(
81 baseline
82 .guard
83 .as_ref()
84 .expect("cached PocketIC baseline must exist"),
85 restore,
86 ) {
87 Ok(()) => return (baseline, true),
88 Err(CachedBaselineRestoreFailure::DeadInstanceTransport) => {}
89 Err(CachedBaselineRestoreFailure::Panic(payload)) => {
90 resume_unwind(payload);
91 }
92 }
93
94 drop(baseline);
95 drop_stale_cached_pocket_ic_baseline(slot);
96
97 let (rebuilt, _cache_hit) = acquire_cached_pocket_ic_baseline(slot, build);
98 (rebuilt, false)
99}
100
101fn try_restore_cached_pocket_ic_baseline<T, R>(
104 baseline: &CachedPocketIcBaseline<T>,
105 restore: R,
106) -> Result<(), CachedBaselineRestoreFailure>
107where
108 R: Fn(&CachedPocketIcBaseline<T>),
109{
110 match catch_unwind(AssertUnwindSafe(|| restore(baseline))) {
111 Ok(()) => Ok(()),
112 Err(payload) => {
113 if transport::panic_is_dead_instance_transport(payload.as_ref()) {
114 Err(CachedBaselineRestoreFailure::DeadInstanceTransport)
115 } else {
116 Err(CachedBaselineRestoreFailure::Panic(payload))
117 }
118 }
119 }
120}
121
122fn drop_stale_cached_pocket_ic_baseline<T>(
125 slot: &'static Mutex<Option<CachedPocketIcBaseline<T>>>,
126) {
127 let stale = {
128 let mut slot = slot
129 .lock()
130 .unwrap_or_else(std::sync::PoisonError::into_inner);
131 slot.take()
132 };
133
134 if let Some(stale) = stale {
135 let _ = catch_unwind(AssertUnwindSafe(|| {
136 drop(stale);
137 }));
138 }
139}
140
141impl<T> CachedPocketIcBaselineGuard<'_, T> {
142 #[must_use]
144 pub fn pocket_ic(&self) -> &PocketIc {
145 self.guard
146 .as_ref()
147 .expect("cached PocketIC baseline must exist")
148 .pocket_ic()
149 }
150
151 #[must_use]
153 pub fn metadata(&self) -> &T {
154 self.guard
155 .as_ref()
156 .expect("cached PocketIC baseline must exist")
157 .metadata()
158 }
159
160 #[must_use]
162 pub fn metadata_mut(&mut self) -> &mut T {
163 self.guard
164 .as_mut()
165 .expect("cached PocketIC baseline must exist")
166 .metadata_mut()
167 }
168
169 pub fn restore(&self, controller_id: Principal) -> Result<(), ControllerSnapshotError> {
171 self.guard
172 .as_ref()
173 .expect("cached PocketIC baseline must exist")
174 .restore(controller_id)
175 }
176
177 pub fn restore_with_funding(
179 &self,
180 controller_id: Principal,
181 funding: SnapshotRestoreFunding,
182 ) -> Result<(), ControllerSnapshotError> {
183 self.guard
184 .as_ref()
185 .expect("cached PocketIC baseline must exist")
186 .restore_with_funding(controller_id, funding)
187 }
188
189 pub fn restore_with_captured_senders(&self) -> Result<(), ControllerSnapshotError> {
191 self.guard
192 .as_ref()
193 .expect("cached PocketIC baseline must exist")
194 .restore_with_captured_senders()
195 }
196
197 pub fn restore_with_captured_senders_and_funding(
199 &self,
200 funding: SnapshotRestoreFunding,
201 ) -> Result<(), ControllerSnapshotError> {
202 self.guard
203 .as_ref()
204 .expect("cached PocketIC baseline must exist")
205 .restore_with_captured_senders_and_funding(funding)
206 }
207}
208
209impl<T> CachedPocketIcBaseline<T> {
210 pub fn capture<I>(
215 pocket_ic: PocketIc,
216 controller_id: Principal,
217 canister_ids: I,
218 metadata: T,
219 ) -> Result<Self, ControllerSnapshotError>
220 where
221 I: IntoIterator<Item = Principal>,
222 {
223 let snapshots = pocket_ic.capture_controller_snapshots(controller_id, canister_ids)?;
224
225 Ok(Self {
226 pocket_ic,
227 snapshots,
228 metadata,
229 })
230 }
231
232 pub fn capture_with_senders<I>(
236 pocket_ic: PocketIc,
237 targets: I,
238 metadata: T,
239 ) -> Result<Self, ControllerSnapshotError>
240 where
241 I: IntoIterator<Item = CanisterSnapshotTarget>,
242 {
243 let snapshots = pocket_ic.capture_snapshots_with_senders(targets)?;
244
245 Ok(Self {
246 pocket_ic,
247 snapshots,
248 metadata,
249 })
250 }
251
252 pub fn restore(&self, controller_id: Principal) -> Result<(), ControllerSnapshotError> {
254 self.pocket_ic
255 .restore_controller_snapshots(controller_id, &self.snapshots)
256 }
257
258 pub fn restore_with_funding(
260 &self,
261 controller_id: Principal,
262 funding: SnapshotRestoreFunding,
263 ) -> Result<(), ControllerSnapshotError> {
264 self.pocket_ic.restore_controller_snapshots_with_funding(
265 controller_id,
266 &self.snapshots,
267 funding,
268 )
269 }
270
271 pub fn restore_with_captured_senders(&self) -> Result<(), ControllerSnapshotError> {
273 self.pocket_ic
274 .restore_snapshots_with_captured_senders(&self.snapshots)
275 }
276
277 pub fn restore_with_captured_senders_and_funding(
279 &self,
280 funding: SnapshotRestoreFunding,
281 ) -> Result<(), ControllerSnapshotError> {
282 self.pocket_ic
283 .restore_snapshots_with_captured_senders_and_funding(&self.snapshots, funding)
284 }
285
286 #[must_use]
288 pub const fn pocket_ic(&self) -> &PocketIc {
289 &self.pocket_ic
290 }
291
292 #[must_use]
294 pub fn snapshot_count(&self) -> usize {
295 self.snapshots.len()
296 }
297
298 pub fn snapshot_canister_ids(&self) -> impl Iterator<Item = Principal> + '_ {
300 self.snapshots.canister_ids()
301 }
302
303 #[must_use]
305 pub const fn metadata(&self) -> &T {
306 &self.metadata
307 }
308
309 #[must_use]
311 pub const fn metadata_mut(&mut self) -> &mut T {
312 &mut self.metadata
313 }
314}