use crate::{
InternalError,
access::AccessError,
dto::caller_authority::CallerAuthorityPublication,
ops::{
caller_authority::CallerAuthorityOps,
storage::{StorageOpsError, fleet_activation::FleetActivationOps},
},
workflow::runtime::timer::{TimerError, require_active, retain_owned_once, with_owned_once},
};
use ic_timers::{
DeclarationLifetime, OnceDecision, OnceRegistration, OnceRunResult, TimerCompletion,
TimerIdentity, TimerSchedule, register_once,
};
use std::{cell::RefCell, future::Future, time::Duration};
thread_local! {
static STARTUP: RefCell<Option<OnceRegistration>> = const { RefCell::new(None) };
}
pub fn release(publication: CallerAuthorityPublication) -> Result<(), InternalError> {
FleetActivationOps::release_application_startup(CallerAuthorityOps::publication_from_dto(
publication,
))
.map_err(StorageOpsError::from)?;
Ok(())
}
pub fn require_started() -> Result<(), InternalError> {
FleetActivationOps::require_application_started()
.map_err(AccessError::from)
.map_err(Into::into)
}
pub fn require_initialization_effect() -> Result<(), InternalError> {
FleetActivationOps::application_startup_work()
.map_err(AccessError::from)?
.ok_or_else(InternalError::unavailable)?;
Ok(())
}
pub fn require_endpoint_started(call: crate::ids::EndpointCall) -> Result<(), AccessError> {
use crate::{
model::caller_authority::CallerAdmissionError,
ops::runtime::{env::EnvOps, fleet_activation::FleetActivationRuntimeOps},
};
if EnvOps::is_root()
|| EnvOps::is_fleet_coordinator_runtime()
|| FleetActivationRuntimeOps::is_standalone_local()
{
return Ok(());
}
let role = EnvOps::canister_role()
.map_err(|_| AccessError::from(CallerAdmissionError::AuthorityUnavailable))?;
if role.is_wasm_store()
|| crate::domain::policy::pure::fleet_activation::is_nonroot_infrastructure_endpoint(call)
{
return Ok(());
}
FleetActivationOps::require_application_started().map_err(Into::into)
}
pub fn schedule<F: Future<Output = ()> + 'static>(
hook: fn(Option<Vec<u8>>) -> F,
) -> Result<(), InternalError> {
let pending = FleetActivationOps::application_startup_work()
.map_err(AccessError::from)?
.is_some();
if with_owned_once(&STARTUP, |_| ())?.is_none() {
let claim = register_once(
TimerIdentity::try_new("canic", "application", "initialize")
.map_err(TimerError::from)?,
DeclarationLifetime::Retained,
move |_| run(hook),
)
.map_err(TimerError::from)?;
retain_owned_once(&STARTUP, claim)?;
}
if pending {
require_active()?;
with_owned_once(&STARTUP, |claim| {
claim.ensure_scheduled(TimerSchedule::After(Duration::ZERO))
})?
.ok_or(TimerError::MissingClaim)?
.map_err(TimerError::from)?;
}
Ok(())
}
async fn run<F: Future<Output = ()>>(hook: fn(Option<Vec<u8>>) -> F) -> OnceRunResult {
let work = match FleetActivationOps::application_startup_work() {
Ok(Some(work)) => work,
Ok(None) => return OnceRunResult::new(TimerCompletion::no_work(), OnceDecision::Stop),
Err(_) => {
return OnceRunResult::new(TimerCompletion::invariant_failure(0), OnceDecision::Stop);
}
};
hook(work.arguments).await;
match FleetActivationOps::complete_application_startup(&work.release) {
Ok(()) => OnceRunResult::new(TimerCompletion::success(1), OnceDecision::Stop),
Err(_) => OnceRunResult::new(TimerCompletion::invariant_failure(0), OnceDecision::Stop),
}
}