1use crate::{
2 cdk::{
3 structures::{BTreeMap, DefaultMemoryImpl, memory::VirtualMemory},
4 types::Principal,
5 utils::time::now_secs,
6 },
7 eager_static, ic_memory,
8 ids::CanisterRole,
9 memory::impl_storable_unbounded,
10 model::memory::id::root::CANISTER_POOL_ID,
11 types::Cycles,
12};
13use candid::CandidType;
14use serde::{Deserialize, Serialize};
15use std::cell::RefCell;
16
17eager_static! {
22 static CANISTER_POOL: RefCell<BTreeMap<Principal, CanisterPoolEntry, VirtualMemory<DefaultMemoryImpl>>> =
23 RefCell::new(BTreeMap::init(
24 ic_memory!(CanisterPool, CANISTER_POOL_ID),
25 ));
26}
27
28pub type CanisterPoolView = Vec<(Principal, CanisterPoolEntry)>;
33
34#[derive(CandidType, Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
39pub enum CanisterPoolStatus {
40 PendingReset,
41 #[default]
42 Ready,
43 Failed {
44 reason: String,
45 },
46}
47
48impl CanisterPoolStatus {
49 #[must_use]
50 pub const fn is_pending_reset(&self) -> bool {
51 matches!(self, Self::PendingReset)
52 }
53
54 #[must_use]
55 pub const fn is_ready(&self) -> bool {
56 matches!(self, Self::Ready)
57 }
58
59 #[must_use]
60 pub const fn is_failed(&self) -> bool {
61 matches!(self, Self::Failed { .. })
62 }
63}
64
65#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
70pub struct CanisterPoolEntry {
71 pub created_at: u64,
72 pub cycles: Cycles,
73 #[serde(default)]
74 pub status: CanisterPoolStatus,
75 #[serde(default)]
76 pub role: Option<CanisterRole>,
77 #[serde(default)]
78 pub parent: Option<Principal>,
79 #[serde(default)]
80 pub module_hash: Option<Vec<u8>>,
81}
82
83impl_storable_unbounded!(CanisterPoolEntry);
84
85pub struct CanisterPool;
90
91impl CanisterPool {
92 pub fn register(
94 pid: Principal,
95 cycles: Cycles,
96 status: CanisterPoolStatus,
97 role: Option<CanisterRole>,
98 parent: Option<Principal>,
99 module_hash: Option<Vec<u8>>,
100 ) {
101 let entry = CanisterPoolEntry {
102 created_at: now_secs(),
103 cycles,
104 status,
105 role,
106 parent,
107 module_hash,
108 };
109
110 CANISTER_POOL.with_borrow_mut(|map| {
111 map.insert(pid, entry);
112 });
113 }
114
115 #[must_use]
116 pub(crate) fn get(pid: Principal) -> Option<CanisterPoolEntry> {
117 CANISTER_POOL.with_borrow(|map| map.get(&pid))
118 }
119
120 #[must_use]
121 pub(crate) fn update(pid: Principal, entry: CanisterPoolEntry) -> bool {
122 CANISTER_POOL.with_borrow_mut(|map| {
123 if map.contains_key(&pid) {
124 map.insert(pid, entry);
125 true
126 } else {
127 false
128 }
129 })
130 }
131
132 #[must_use]
134 pub(crate) fn pop_ready() -> Option<(Principal, CanisterPoolEntry)> {
135 CANISTER_POOL.with_borrow_mut(|map| {
136 let min_pid = map
137 .iter()
138 .filter(|entry| entry.value().status.is_ready())
139 .min_by_key(|entry| entry.value().created_at)
140 .map(|entry| *entry.key())?;
141 map.remove(&min_pid).map(|entry| (min_pid, entry))
142 })
143 }
144
145 #[must_use]
147 pub(crate) fn contains(pid: &Principal) -> bool {
148 CANISTER_POOL.with_borrow(|map| map.contains_key(pid))
149 }
150
151 #[must_use]
153 pub(crate) fn take(pid: &Principal) -> Option<CanisterPoolEntry> {
154 CANISTER_POOL.with_borrow_mut(|map| map.remove(pid))
155 }
156
157 #[must_use]
159 #[cfg(test)]
160 pub(crate) fn remove(pid: &Principal) -> Option<CanisterPoolEntry> {
161 CANISTER_POOL.with_borrow_mut(|map| map.remove(pid))
162 }
163
164 #[must_use]
166 pub(crate) fn export() -> CanisterPoolView {
167 CANISTER_POOL.with_borrow(BTreeMap::to_vec)
168 }
169
170 #[cfg(test)]
172 pub(crate) fn clear() {
173 CANISTER_POOL.with_borrow_mut(BTreeMap::clear);
174 }
175
176 #[must_use]
178 pub(crate) fn len() -> u64 {
179 CANISTER_POOL.with_borrow(|map| map.len())
180 }
181
182 #[must_use]
184 #[cfg(test)]
185 pub(crate) fn is_empty() -> bool {
186 CANISTER_POOL.with_borrow(|map| map.is_empty())
187 }
188}
189
190#[cfg(test)]
195mod tests {
196 use super::*;
197 use candid::Principal;
198
199 fn pid(n: u8) -> Principal {
200 Principal::self_authenticating(vec![n])
201 }
202
203 #[test]
204 fn register_and_export() {
205 CanisterPool::clear();
206
207 let p1 = pid(1);
208 let p2 = pid(2);
209
210 CanisterPool::register(
211 p1,
212 100u128.into(),
213 CanisterPoolStatus::Ready,
214 None,
215 None,
216 None,
217 );
218 CanisterPool::register(
219 p2,
220 200u128.into(),
221 CanisterPoolStatus::Ready,
222 None,
223 None,
224 None,
225 );
226
227 let view = CanisterPool::export();
228 assert_eq!(view.len(), 2);
229
230 let entry1 = view.iter().find(|(id, _)| *id == p1).unwrap();
231 assert_eq!(entry1.1.cycles, 100u128.into());
232
233 let entry2 = view.iter().find(|(id, _)| *id == p2).unwrap();
234 assert_eq!(entry2.1.cycles, 200u128.into());
235 }
236
237 #[test]
238 fn remove_specific_pid() {
239 CanisterPool::clear();
240
241 let p1 = pid(1);
242 let p2 = pid(2);
243
244 CanisterPool::register(
245 p1,
246 123u128.into(),
247 CanisterPoolStatus::Ready,
248 None,
249 None,
250 None,
251 );
252 CanisterPool::register(
253 p2,
254 456u128.into(),
255 CanisterPoolStatus::Ready,
256 None,
257 None,
258 None,
259 );
260
261 let removed = CanisterPool::remove(&p1).unwrap();
262 assert_eq!(removed.cycles, 123u128.into());
263
264 let view = CanisterPool::export();
266 assert_eq!(view.len(), 1);
267 assert_eq!(view[0].0, p2);
268 }
269
270 #[test]
271 fn clear_resets_pool() {
272 CanisterPool::clear();
273
274 CanisterPool::register(
275 pid(1),
276 10u128.into(),
277 CanisterPoolStatus::Ready,
278 None,
279 None,
280 None,
281 );
282 assert!(!CanisterPool::is_empty());
283
284 CanisterPool::clear();
285 assert!(CanisterPool::is_empty());
286 }
287}