From a57c0b4d1b8d3460fd034d91c22e01ac608ff57e Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Sun, 24 Jul 2022 12:12:00 +0800 Subject: [PATCH 1/5] support async init for high cold start lambda functions --- Cargo.toml | 2 +- README.md | 20 ++++++++++++++------ src/main.rs | 50 ++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 51 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8b6ae56f..cb7712f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ lambda-extension = "0.6.0" lambda_http = "0.6.0" log = "0.4.14" reqwest = { version = "0.11", default-features = false, features = ["blocking", "json"] } -tokio = { version = "1.20.0", features = ["macros", "io-util", "sync", "rt-multi-thread"] } +tokio = { version = "1.20.0", features = ["macros", "io-util", "sync", "rt-multi-thread", "time"] } tokio-retry = "0.3" [[bin]] diff --git a/README.md b/README.md index 60e8ef27..5e9b6779 100644 --- a/README.md +++ b/README.md @@ -67,12 +67,20 @@ After passing readiness check, Lambda Web Adapter will start Lambda Runtime and The readiness check port/path and traffic port can be configured using environment variables. These environment variables can be defined either within docker file or as Lambda function configuration. -|Environment Variable| Description | Default | -|--------------------|------------------------------------------------------------|---------| -|PORT | traffic port | "8080" | -|READINESS_CHECK_PORT| readiness check port, default to the fraffic port | PORT | -|READINESS_CHECK_PATH| readiness check path | "/" | -|REMOVE_BASE_PATH | (optional) the base path to be removed from request path | None | +| Environment Variable | Description | Default | +|----------------------|------------------------------------------------------------------|---------| +| PORT | traffic port | "8080" | +| READINESS_CHECK_PORT | readiness check port, default to the traffic port | PORT | +| READINESS_CHECK_PATH | readiness check path | "/" | +| ASYNC_INIT | enable asynchronous initialization for high cold start functions | "false" | +| REMOVE_BASE_PATH | (optional) the base path to be removed from request path | None | + +**ASYNC_INIT** Lambda managed runtimes offer up to 10 seconds for lambda function initialization. Duration this period of time, Lambda functions have burst of CPU to accelerate cold start, and it is not billed. +If a lambda function couldn't complete the initialization within 10 seconds, Lambda will stop the function, restart the initialization, and bill the initialization time. +To help functions to use this 10 seconds free initialization time and avoid the restart, Lambda Web Adapter supports asynchronous initialization. +When this feature is enabled, Lambda Web Adapter will performance readiness check up to 9.8 seconds. If the web app is not readiness by then, +Lambda Web Adapter will signal to Lambda service that the init is completed, and continue readiness check in the handler. +This feature is disabled by default. Enabled it by setting environment variable `ASYNC_INIT` to `true`. **REMOVE_BASE_PATH** - The value of this environment variable tells the adapter whether the application is running under a base path. For example, you could have configured your API Gateway to have a /orders/{proxy+} and a /catalog/{proxy+} resource. diff --git a/src/main.rs b/src/main.rs index 25540b9c..ca408c40 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use log::*; use reqwest::{redirect, Client}; use std::time::Duration; use std::{env, future, mem}; +use tokio::time::timeout; use tokio_retry::{strategy::FixedInterval, Retry}; type Error = Box; @@ -17,6 +18,7 @@ struct AdapterOptions { readiness_check_port: String, readiness_check_path: String, base_path: Option, + async_init: bool, } #[tokio::main] @@ -31,6 +33,10 @@ async fn main() -> Result<(), Error> { .unwrap_or_else(|_| env::var("PORT").unwrap_or_else(|_| "8080".to_string())), readiness_check_path: env::var("READINESS_CHECK_PATH").unwrap_or_else(|_| "/".to_string()), base_path: env::var("REMOVE_BASE_PATH").ok(), + async_init: env::var("ASYNC_INIT") + .unwrap_or_else(|_| "false".to_string()) + .parse() + .unwrap_or(false), }; // register as an external extension @@ -43,19 +49,14 @@ async fn main() -> Result<(), Error> { .expect("extension thread error"); }); - // check if the application is ready every 10 milliseconds - Retry::spawn(FixedInterval::from_millis(10), || { - let readiness_check_url = format!( - "http://{}:{}{}", - options.host, options.readiness_check_port, options.readiness_check_path - ); - match reqwest::blocking::get(readiness_check_url) { - Ok(response) if { response.status().is_success() } => future::ready(Ok(())), - _ => future::ready(Err::<(), i32>(-1)), - } - }) - .await - .expect("application server is not ready"); + // check if the application is ready + let is_ready = if options.async_init { + timeout(Duration::from_secs_f32(9.8), check_readiness(options)) + .await + .is_ok() + } else { + check_readiness(options).await + }; // start lambda runtime let http_client = &Client::builder() @@ -64,17 +65,38 @@ async fn main() -> Result<(), Error> { .build() .unwrap(); lambda_http::run(http_handler(|event: Request| async move { - http_proxy_handler(event, http_client, options).await + http_proxy_handler(event, http_client, options, is_ready).await })) .await?; Ok(()) } +async fn check_readiness(options: &AdapterOptions) -> bool { + Retry::spawn(FixedInterval::from_millis(10), || { + let readiness_check_url = format!( + "http://{}:{}{}", + options.host, options.readiness_check_port, options.readiness_check_path + ); + match reqwest::blocking::get(readiness_check_url) { + Ok(response) if { response.status().is_success() } => future::ready(Ok(())), + _ => future::ready(Err::<(), i32>(-1)), + } + }) + .await + .is_ok() +} + async fn http_proxy_handler( event: Request, http_client: &Client, options: &AdapterOptions, + is_app_ready: bool, ) -> Result, Error> { + // continue checking readiness if async_init is configured and the app is not ready + if options.async_init && !is_app_ready { + check_readiness(options).await; + } + let raw_path = event.raw_http_path(); let (parts, body) = event.into_parts(); From 35435ac259f24998a6af322a8948994634dff2bf Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Sun, 24 Jul 2022 12:15:51 +0800 Subject: [PATCH 2/5] support async init for high cold start lambda functions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e9b6779..bb0f9bcb 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ The readiness check port/path and traffic port can be configured using environme | ASYNC_INIT | enable asynchronous initialization for high cold start functions | "false" | | REMOVE_BASE_PATH | (optional) the base path to be removed from request path | None | -**ASYNC_INIT** Lambda managed runtimes offer up to 10 seconds for lambda function initialization. Duration this period of time, Lambda functions have burst of CPU to accelerate cold start, and it is not billed. +**ASYNC_INIT** Lambda managed runtimes offer up to 10 seconds for lambda function initialization. During this period of time, Lambda functions have burst of CPU to accelerate cold start, and it is free. If a lambda function couldn't complete the initialization within 10 seconds, Lambda will stop the function, restart the initialization, and bill the initialization time. To help functions to use this 10 seconds free initialization time and avoid the restart, Lambda Web Adapter supports asynchronous initialization. When this feature is enabled, Lambda Web Adapter will performance readiness check up to 9.8 seconds. If the web app is not readiness by then, From 31ed89709924f47c8fe571bf64af1d9efb1bbf99 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Sun, 24 Jul 2022 13:21:42 +0800 Subject: [PATCH 3/5] support async init for high cold start lambda functions --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index bb0f9bcb..891ff3fc 100644 --- a/README.md +++ b/README.md @@ -67,20 +67,20 @@ After passing readiness check, Lambda Web Adapter will start Lambda Runtime and The readiness check port/path and traffic port can be configured using environment variables. These environment variables can be defined either within docker file or as Lambda function configuration. -| Environment Variable | Description | Default | -|----------------------|------------------------------------------------------------------|---------| -| PORT | traffic port | "8080" | -| READINESS_CHECK_PORT | readiness check port, default to the traffic port | PORT | -| READINESS_CHECK_PATH | readiness check path | "/" | -| ASYNC_INIT | enable asynchronous initialization for high cold start functions | "false" | -| REMOVE_BASE_PATH | (optional) the base path to be removed from request path | None | - -**ASYNC_INIT** Lambda managed runtimes offer up to 10 seconds for lambda function initialization. During this period of time, Lambda functions have burst of CPU to accelerate cold start, and it is free. -If a lambda function couldn't complete the initialization within 10 seconds, Lambda will stop the function, restart the initialization, and bill the initialization time. +| Environment Variable | Description | Default | +|----------------------|----------------------------------------------------------------------|---------| +| PORT | traffic port | "8080" | +| READINESS_CHECK_PORT | readiness check port, default to the traffic port | PORT | +| READINESS_CHECK_PATH | readiness check path | "/" | +| ASYNC_INIT | enable asynchronous initialization for long initialization functions | "false" | +| REMOVE_BASE_PATH | (optional) the base path to be removed from request path | None | + +**ASYNC_INIT** Lambda managed runtimes offer up to 10 seconds for function initialization. During this period of time, Lambda functions have burst of CPU to accelerate initialization, and it is free. +If a lambda function couldn't complete the initialization within 10 seconds, Lambda will restart the function, and bill for the initialization. To help functions to use this 10 seconds free initialization time and avoid the restart, Lambda Web Adapter supports asynchronous initialization. -When this feature is enabled, Lambda Web Adapter will performance readiness check up to 9.8 seconds. If the web app is not readiness by then, -Lambda Web Adapter will signal to Lambda service that the init is completed, and continue readiness check in the handler. -This feature is disabled by default. Enabled it by setting environment variable `ASYNC_INIT` to `true`. +When this feature is enabled, Lambda Web Adapter will performance readiness check up to 9.8 seconds. If the web app is not ready by then, +Lambda Web Adapter signals to Lambda service that the init is completed, and continues readiness check in the handler. +This feature is disabled by default. Enable it by setting environment variable `ASYNC_INIT` to `true`. **REMOVE_BASE_PATH** - The value of this environment variable tells the adapter whether the application is running under a base path. For example, you could have configured your API Gateway to have a /orders/{proxy+} and a /catalog/{proxy+} resource. From 987915cfa5a24754ba9554b252c43972dc26e770 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Sun, 24 Jul 2022 16:18:45 +0800 Subject: [PATCH 4/5] update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 891ff3fc..b85aa8e1 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ The readiness check port/path and traffic port can be configured using environme **ASYNC_INIT** Lambda managed runtimes offer up to 10 seconds for function initialization. During this period of time, Lambda functions have burst of CPU to accelerate initialization, and it is free. If a lambda function couldn't complete the initialization within 10 seconds, Lambda will restart the function, and bill for the initialization. To help functions to use this 10 seconds free initialization time and avoid the restart, Lambda Web Adapter supports asynchronous initialization. -When this feature is enabled, Lambda Web Adapter will performance readiness check up to 9.8 seconds. If the web app is not ready by then, +When this feature is enabled, Lambda Web Adapter performs readiness check up to 9.8 seconds. If the web app is not ready by then, Lambda Web Adapter signals to Lambda service that the init is completed, and continues readiness check in the handler. This feature is disabled by default. Enable it by setting environment variable `ASYNC_INIT` to `true`. From aa2508106af1ae5fa578f91f6427cc82a28f3964 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Mon, 25 Jul 2022 23:46:09 +0800 Subject: [PATCH 5/5] skip readiness check after the first invoke --- src/main.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index ca408c40..36dce1de 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,8 +64,15 @@ async fn main() -> Result<(), Error> { .pool_idle_timeout(Duration::from_secs(4)) .build() .unwrap(); - lambda_http::run(http_handler(|event: Request| async move { - http_proxy_handler(event, http_client, options, is_ready).await + let mut first_invoke = true; + lambda_http::run(http_handler(|event: Request| { + let is_app_ready = if first_invoke { + first_invoke = false; + is_ready + } else { + true // the app must be ready after the first invoke + }; + async move { http_proxy_handler(event, http_client, options, is_app_ready).await } })) .await?; Ok(())