use std::sync::Arc;
use async_trait::async_trait;
use crate::function::{AppError, ComposableFunction};
use crate::platform::{FunctionOptions, Platform};
use crate::util::app_config_reader::AppConfigReader;
use crate::util::{elastic_queue, managed_cache};
const MAX_SEQ: u32 = 999;
#[async_trait]
pub trait EntryPoint: Send + Sync {
async fn start(&self, args: &[String]) -> Result<(), AppError>;
}
#[derive(Default)]
pub struct AppStarter {
before: Vec<(u32, Arc<dyn EntryPoint>)>,
preloads: Vec<(String, Arc<dyn ComposableFunction>, usize, FunctionOptions)>,
mains: Vec<(u32, Arc<dyn EntryPoint>)>,
}
impl AppStarter {
pub fn new() -> Self {
Self::default()
}
pub fn before_application(mut self, sequence: u32, hook: Arc<dyn EntryPoint>) -> Self {
self.before.push((sequence.min(MAX_SEQ), hook));
self
}
pub fn preload(
self,
route: &str,
function: Arc<dyn ComposableFunction>,
instances: usize,
) -> Self {
self.preload_with_options(route, function, instances, FunctionOptions::default())
}
pub fn preload_with_options(
mut self,
route: &str,
function: Arc<dyn ComposableFunction>,
instances: usize,
options: FunctionOptions,
) -> Self {
self.preloads
.push((route.to_string(), function, instances, options));
self
}
pub fn main_application(mut self, sequence: u32, entry: Arc<dyn EntryPoint>) -> Self {
self.mains.push((sequence.min(MAX_SEQ), entry));
self
}
pub async fn run(self, args: Vec<String>) -> Result<(), AppError> {
crate::util::overrides::load_runtime_args();
let config = AppConfigReader::get_instance();
log::info!(
"Starting application {} (platform-core v{})",
Platform::name(),
env!("CARGO_PKG_VERSION")
);
elastic_queue::start_housekeeping();
managed_cache::start_housekeeping();
let platform = Platform::get_instance();
if !platform.has_route(crate::inbox::TEMPORARY_INBOX) {
if let Err(e) = platform.register_with_options(
crate::inbox::TEMPORARY_INBOX,
Arc::new(crate::inbox::TemporaryInbox),
500,
crate::platform::FunctionOptions {
zero_traced: true,
interceptor: false,
private: true,
},
) {
if !platform.has_route(crate::inbox::TEMPORARY_INBOX) {
return Err(e);
}
}
}
if !platform.has_route(crate::telemetry::DISTRIBUTED_TRACING) {
if let Err(e) = platform.register_private(
crate::telemetry::DISTRIBUTED_TRACING,
std::sync::Arc::new(crate::telemetry::Telemetry::new(&platform)),
1,
) {
if !platform.has_route(crate::telemetry::DISTRIBUTED_TRACING) {
return Err(e);
}
}
}
if !platform.has_route(crate::automation::EVENT_API_SERVICE) {
let event_api_options = crate::platform::FunctionOptions {
zero_traced: false,
interceptor: true,
private: true,
};
if let Err(e) = platform.register_with_options(
crate::automation::EVENT_API_SERVICE,
Arc::new(crate::automation::EventApiService::new(&platform)),
250,
event_api_options,
) {
if !platform.has_route(crate::automation::EVENT_API_SERVICE) {
return Err(e);
}
}
}
if !platform.has_route(crate::actuator::INFO_ACTUATOR) {
use crate::actuator::{ActuatorContext, ActuatorKind, ActuatorServices};
let context = ActuatorContext::new(&platform);
let actuators = [
(crate::actuator::INFO_ACTUATOR, ActuatorKind::Info),
(crate::actuator::ROUTES_ACTUATOR, ActuatorKind::Routes),
(crate::actuator::ENV_ACTUATOR, ActuatorKind::Env),
(crate::actuator::HEALTH_ACTUATOR, ActuatorKind::Health),
(crate::actuator::LIVENESS_ACTUATOR, ActuatorKind::Liveness),
];
let instances = crate::actuator::actuator_instances(config);
for (route, kind) in actuators {
if let Err(e) = platform.register_private(
route,
Arc::new(ActuatorServices::new(kind, context.clone())),
instances,
) {
if !platform.has_route(route) {
return Err(e);
}
}
}
}
if !platform.has_route("no.op") {
let config = AppConfigReader::get_instance();
let no_op_instances = config
.get_property("worker.instances.no.op")
.and_then(|value| value.parse::<usize>().ok())
.unwrap_or(500);
if let Err(e) = platform.register_private(
"no.op",
Arc::new(crate::function::NoOpFunction),
no_op_instances,
) {
if !platform.has_route("no.op") {
return Err(e);
}
}
}
if !platform.has_route(crate::automation::ASYNC_HTTP_REQUEST) {
if let Err(e) = platform.register_with_options(
crate::automation::ASYNC_HTTP_REQUEST,
Arc::new(crate::automation::http_client::AsyncHttpClientService::new(
&platform,
)),
500,
FunctionOptions {
zero_traced: false,
interceptor: true,
private: true,
},
) {
if !platform.has_route(crate::automation::ASYNC_HTTP_REQUEST) {
return Err(e);
}
}
}
let mut before = self.before;
before.sort_by_key(|(sequence, _)| *sequence);
for (sequence, hook) in before {
hook.start(&args).await.map_err(|e| {
AppError::new(
e.status(),
format!(
"BeforeApplication (sequence {sequence}) failed: {}",
e.message()
),
)
})?;
}
for (route, function, instances, options) in self.preloads {
platform.register_with_options(&route, function, instances, options)?;
log::info!(
"{route} with {instances} instance{} started",
if instances == 1 { "" } else { "s" }
);
}
for entry in inventory::iter::<crate::registry::WsServiceEntry> {
if !crate::util::feature::is_required(entry.optional_service, config) {
log::info!(
"Skip optional websocket service /{}/{} (condition: {})",
entry.namespace,
entry.name,
entry.optional_service.unwrap_or_default()
);
continue;
}
if validate_ws_service_name(entry.name) && validate_ws_service_name(entry.namespace) {
crate::automation::ws_server::register_ws_service_with_namespace(
entry.namespace,
entry.name,
entry.factory,
);
} else {
log::error!(
"Unable to load websocket service /{}/{} - not a valid service name",
entry.namespace,
entry.name
);
}
}
let serve_http = config.get_property_or("rest.automation", "false") == "true"
|| crate::automation::ws_server::has_ws_services();
if serve_http {
crate::automation::start_http_server(&platform).await?;
}
if self.mains.is_empty() {
return Err(AppError::new(
400,
"Missing main application - did you forget to add one with main_application()?",
));
}
let mut mains = self.mains;
mains.sort_by_key(|(sequence, _)| *sequence);
for (sequence, entry) in mains {
entry.start(&args).await.map_err(|e| {
AppError::new(
e.status(),
format!(
"MainApplication (sequence {sequence}) failed: {}",
e.message()
),
)
})?;
}
Ok(())
}
}
pub struct AutoStart;
impl AutoStart {
pub fn run() -> Result<(), AppError> {
let runtime = tokio::runtime::Runtime::new()
.map_err(|e| AppError::new(500, format!("Unable to start runtime: {e}")))?;
runtime.block_on(async {
Self::main(std::env::args().collect()).await?;
let config = AppConfigReader::get_instance();
if config.get_property_or("rest.automation", "false") == "true"
|| crate::automation::ws_server::has_ws_services()
{
log::info!("Application running - press Ctrl-C to stop");
let _ = tokio::signal::ctrl_c().await;
} else {
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
}
crate::util::elastic_queue::shutdown_cleanup();
Ok(())
})
}
pub async fn main(args: Vec<String>) -> Result<(), AppError> {
static STARTED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
if STARTED.swap(true, std::sync::atomic::Ordering::SeqCst) {
return Ok(());
}
crate::util::overrides::load_runtime_args();
crate::logging::init();
let config = AppConfigReader::get_instance();
let mut starter = AppStarter::new();
for entry in inventory::iter::<crate::registry::BeforeAppEntry> {
if !crate::util::feature::is_required(entry.optional_service, config) {
log::info!(
"Skip optional before-application (condition: {})",
entry.optional_service.unwrap_or_default()
);
continue;
}
starter = starter.before_application(entry.sequence, (entry.factory)());
}
let preload_overrides = crate::preload_override::preload_override(config);
for entry in inventory::iter::<crate::registry::PreloadEntry> {
if !crate::util::feature::is_required(entry.optional_service, config) {
log::info!(
"Skip optional {} (condition: {})",
entry.route,
entry.optional_service.unwrap_or_default()
);
continue;
}
let instances = entry
.env_instances
.and_then(|key| config.get_property(key))
.and_then(|value| value.parse::<usize>().ok())
.unwrap_or(entry.instances);
let (routes, instances) =
crate::preload_override::apply(&preload_overrides, entry.route, instances);
let function = (entry.factory)();
for route in &routes {
starter = starter.preload_with_options(
route,
function.clone(),
instances,
FunctionOptions {
zero_traced: entry.zero_tracing,
interceptor: entry.interceptor,
private: entry.is_private,
},
);
}
}
for entry in inventory::iter::<crate::registry::MainAppEntry> {
if !crate::util::feature::is_required(entry.optional_service, config) {
log::info!(
"Skip optional main-application (condition: {})",
entry.optional_service.unwrap_or_default()
);
continue;
}
starter = starter.main_application(entry.sequence, (entry.factory)());
}
starter.run(args).await?;
Ok(())
}
}
fn validate_ws_service_name(name: &str) -> bool {
!name.is_empty()
&& name.bytes().all(|b| {
b.is_ascii_lowercase() || b.is_ascii_digit() || matches!(b, b'.' | b'-' | b'_')
})
}