canic_core/workflow/runtime/
mod.rs

1pub mod cycles;
2pub mod log;
3pub mod random;
4
5use crate::{
6    VERSION,
7    cdk::{
8        api::{canister_self, trap},
9        println,
10        types::Principal,
11    },
12    dto::{abi::v1::CanisterInitPayload, env::EnvView, subnet::SubnetIdentity},
13    ids::{CanisterRole, SubnetRole},
14    infra::ic::{Network, build_network},
15    log::Topic,
16    ops::{
17        adapter::directory::{app_directory_from_view, subnet_directory_from_view},
18        runtime::{env::EnvOps, memory::MemoryRegistryOps},
19        storage::{
20            directory::{AppDirectoryOps, SubnetDirectoryOps},
21            registry::SubnetRegistryOps,
22        },
23    },
24    workflow,
25};
26use canic_memory::runtime::init_eager_tls;
27
28///
29/// Runtime
30/// Coordinates periodic background services (timers) for Canic canisters.
31///
32
33pub struct Runtime;
34
35impl Runtime {
36    /// Start timers that should run on all canisters.
37    pub fn start_all() {
38        workflow::runtime::cycles::scheduler::start();
39        workflow::runtime::log::retention::start();
40        workflow::runtime::random::scheduler::start();
41    }
42
43    /// Start timers that should run only on root canisters.
44    pub fn start_all_root() {
45        EnvOps::require_root().unwrap_or_else(|e| fatal("start_all_root", e));
46
47        // start shared timers too
48        Self::start_all();
49
50        // root-only services
51        workflow::pool::scheduler::start();
52    }
53}
54
55//
56// ─────────────────────────────────────────────────────────────
57// Fatal helpers (lifecycle boundary)
58// ─────────────────────────────────────────────────────────────
59//
60
61fn fatal(phase: &str, err: impl std::fmt::Display) -> ! {
62    let msg = format!("canic init failed during {phase}: {err}");
63    println!("[canic] FATAL: {msg}");
64    trap(&msg);
65}
66
67fn init_memory_or_trap(phase: &str) {
68    if let Err(err) = MemoryRegistryOps::init_memory() {
69        fatal(phase, format!("memory init failed: {err}"));
70    }
71}
72
73fn ensure_nonroot_env(canister_role: CanisterRole, mut env: EnvView) -> EnvView {
74    let mut missing = Vec::new();
75
76    if env.prime_root_pid.is_none() {
77        missing.push("prime_root_pid");
78    }
79    if env.subnet_role.is_none() {
80        missing.push("subnet_role");
81    }
82    if env.subnet_pid.is_none() {
83        missing.push("subnet_pid");
84    }
85    if env.root_pid.is_none() {
86        missing.push("root_pid");
87    }
88    if env.canister_role.is_none() {
89        missing.push("canister_role");
90    }
91    if env.parent_pid.is_none() {
92        missing.push("parent_pid");
93    }
94
95    if missing.is_empty() {
96        return env;
97    }
98
99    if build_network() == Some(Network::Ic) {
100        fatal(
101            "nonroot_init",
102            format!("missing env fields on ic: {}", missing.join(", ")),
103        );
104    }
105
106    // local / test fallback defaults
107    let root_pid = Principal::from_slice(&[0xBB; 29]);
108    let subnet_pid = Principal::from_slice(&[0xAA; 29]);
109
110    env.prime_root_pid.get_or_insert(root_pid);
111    env.subnet_role.get_or_insert(SubnetRole::PRIME);
112    env.subnet_pid.get_or_insert(subnet_pid);
113    env.root_pid.get_or_insert(root_pid);
114    env.canister_role.get_or_insert(canister_role);
115    env.parent_pid.get_or_insert(root_pid);
116
117    env
118}
119
120///
121/// init_root_canister
122/// Bootstraps the root canister runtime and environment.
123///
124
125pub fn init_root_canister(identity: SubnetIdentity) {
126    // --- Phase 1: Init base systems ---
127    init_eager_tls();
128    init_memory_or_trap("init_root_canister");
129    crate::log::set_ready();
130
131    // log header
132    println!("");
133    println!("");
134    println!("");
135    crate::log!(
136        Topic::Init,
137        Info,
138        "🔧 --------------------- canic v{VERSION} -----------------------",
139    );
140    crate::log!(Topic::Init, Info, "🏁 init: root ({identity:?})");
141
142    // --- Phase 2: Env registration ---
143    let self_pid = canister_self();
144    EnvOps::set_canister_role(CanisterRole::ROOT);
145    EnvOps::set_root_pid(self_pid);
146
147    match identity {
148        SubnetIdentity::Prime => {
149            EnvOps::set_prime_root_pid(self_pid);
150            EnvOps::set_subnet_role(SubnetRole::PRIME);
151            EnvOps::set_subnet_pid(self_pid);
152        }
153        SubnetIdentity::Standard(params) => {
154            EnvOps::set_prime_root_pid(params.prime_root_pid);
155            EnvOps::set_subnet_role(params.subnet_type);
156            EnvOps::set_subnet_pid(self_pid);
157        }
158        SubnetIdentity::Manual(subnet_pid) => {
159            EnvOps::set_prime_root_pid(self_pid);
160            EnvOps::set_subnet_role(SubnetRole::PRIME);
161            EnvOps::set_subnet_pid(subnet_pid);
162        }
163    }
164
165    SubnetRegistryOps::register_root(self_pid);
166
167    // --- Phase 3: Service startup ---
168    Runtime::start_all_root();
169}
170
171///
172/// post_upgrade_root_canister
173///
174
175pub fn post_upgrade_root_canister() {
176    // --- Phase 1: Init base systems ---
177    init_eager_tls();
178    init_memory_or_trap("post_upgrade_root_canister");
179    crate::log::set_ready();
180    crate::log!(Topic::Init, Info, "🏁 post_upgrade_root_canister");
181
182    // ---  Phase 2 intentionally omitted: post-upgrade does not re-import env or directories.
183
184    // --- Phase 3: Service startup ---
185    Runtime::start_all_root();
186}
187
188///
189/// init_nonroot_canister
190///
191
192pub fn init_nonroot_canister(canister_role: CanisterRole, payload: CanisterInitPayload) {
193    // --- Phase 1: Init base systems ---
194    init_eager_tls();
195    init_memory_or_trap("init_nonroot_canister");
196    crate::log::set_ready();
197    crate::log!(Topic::Init, Info, "🏁 init: {}", canister_role);
198
199    // --- Phase 2: Payload registration ---
200    let env = ensure_nonroot_env(canister_role, payload.env);
201    if let Err(err) = EnvOps::import(env) {
202        fatal("init_nonroot_canister", format!("env import failed: {err}"));
203    }
204
205    AppDirectoryOps::import(app_directory_from_view(payload.app_directory));
206    SubnetDirectoryOps::import(subnet_directory_from_view(payload.subnet_directory));
207
208    // --- Phase 3: Service startup ---
209    Runtime::start_all();
210}
211
212///
213/// post_upgrade_nonroot_canister
214///
215
216pub fn post_upgrade_nonroot_canister(canister_role: CanisterRole) {
217    // --- Phase 1: Init base systems ---
218    init_eager_tls();
219    init_memory_or_trap("post_upgrade_nonroot_canister");
220    crate::log::set_ready();
221    crate::log!(
222        Topic::Init,
223        Info,
224        "🏁 post_upgrade_nonroot_canister: {}",
225        canister_role
226    );
227
228    // ---  Phase 2 intentionally omitted: post-upgrade does not re-import env or directories.
229
230    // --- Phase 3: Service startup ---
231    Runtime::start_all();
232}