use std::time::Duration;
use crate::PluginError;
use crate::proxy::LambdaProxy;
use crate::timing::{as_ms, next_delay_ms};
const READY_INITIAL: Duration = Duration::from_millis(5);
const READY_MAX: Duration = Duration::from_millis(100);
const THREAD_NAME: &str = "aws-lambda-runtime";
const WATCH_INTERVAL: Duration = Duration::from_secs(1);
const WATCH_LIMIT: u32 = 5;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum ResponseMode {
#[default]
Buffered,
Streaming,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Startup {
Pending,
Complete,
Stopping,
}
#[derive(Debug)]
pub(crate) struct LoopSettings {
pub proxy: LambdaProxy,
pub mode: ResponseMode,
pub readiness_timeout: Option<Duration>,
pub concurrency: u32,
pub runtime_api: String,
}
pub async fn wait_until(
ready: impl Fn() -> bool,
timeout: Duration,
initial: Duration,
max: Duration,
) -> bool {
let timeout_ms = as_ms(timeout);
let mut delay_ms = as_ms(initial).max(1);
let max_ms = as_ms(max).max(delay_ms);
let start = tokio::time::Instant::now();
loop {
if ready() {
return true;
}
let slept_ms = as_ms(start.elapsed());
if slept_ms >= timeout_ms {
return false;
}
let step = delay_ms.min(timeout_ms - slept_ms);
tokio::time::sleep(Duration::from_millis(step)).await;
delay_ms = next_delay_ms(delay_ms, max_ms);
}
}
pub(crate) fn spawn_loop(
settings: LoopSettings,
startup: impl Fn() -> Startup + Send + 'static,
) -> Result<(), PluginError> {
std::thread::Builder::new()
.name(THREAD_NAME.to_owned())
.spawn(move || {
let runtime = match build_runtime(settings.concurrency) {
Ok(runtime) => runtime,
Err(error) => {
tracing::error!(%error, "cannot build the Lambda runtime; exiting");
std::process::exit(1);
}
};
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
runtime.block_on(run_when_ready(settings, startup))
}));
if matches!(outcome, Ok(None)) {
return;
}
tracing::error!(?outcome, "AWS Lambda runtime loop stopped; exiting");
std::process::exit(1);
})
.map_err(PluginError::Runtime)?;
Ok(())
}
#[allow(
clippy::future_not_send,
reason = "the lambda_http loop is not Send; it runs on its own thread"
)]
async fn run_when_ready(
settings: LoopSettings,
startup: impl Fn() -> Startup,
) -> Option<Result<(), lambda_http::Error>> {
let limit = settings.readiness_timeout.unwrap_or(Duration::MAX);
wait_until(
|| startup() != Startup::Pending,
limit,
READY_INITIAL,
READY_MAX,
)
.await;
match startup() {
Startup::Stopping => {
tracing::info!("Autumn shuts down; the Lambda loop does not start");
return None;
}
Startup::Pending => tracing::warn!(
?limit,
"Autumn startup is not complete; the Lambda loop starts now"
),
Startup::Complete => {}
}
if settings.concurrency > 1 {
tokio::spawn(watch_runtime_api(settings.runtime_api));
}
tracing::info!(mode = ?settings.mode, concurrency = settings.concurrency, "AWS Lambda runtime loop runs");
Some(run(settings.proxy, settings.mode).await)
}
fn build_runtime(concurrency: u32) -> std::io::Result<tokio::runtime::Runtime> {
if concurrency <= 1 {
return tokio::runtime::Builder::new_current_thread()
.enable_all()
.build();
}
let cores = std::thread::available_parallelism().map_or(1, std::num::NonZero::get);
let wanted = usize::try_from(concurrency).unwrap_or(usize::MAX);
tokio::runtime::Builder::new_multi_thread()
.worker_threads(cores.min(wanted))
.enable_all()
.build()
}
async fn watch_runtime_api(address: String) {
let mut failures = 0;
loop {
tokio::time::sleep(WATCH_INTERVAL).await;
let connect = tokio::net::TcpStream::connect(address.as_str());
match tokio::time::timeout(WATCH_INTERVAL, connect).await {
Ok(Ok(_)) => failures = 0,
_ => failures += 1,
}
if failures >= WATCH_LIMIT {
tracing::error!(%address, "AWS Lambda Runtime API is not reachable; exiting");
std::process::exit(1);
}
}
}
#[allow(
clippy::future_not_send,
reason = "the lambda_http loop is not Send; it runs on its own thread"
)]
async fn run(proxy: LambdaProxy, mode: ResponseMode) -> Result<(), lambda_http::Error> {
let proxy = proxy.response_mode(mode);
match mode {
ResponseMode::Streaming => lambda_http::run_with_streaming_response_concurrent(proxy).await,
_ => lambda_http::run_concurrent(proxy).await,
}
}