Skip to content

feat(rust): add second example using non-op internal extension, expand README #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 8, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 41 additions & 7 deletions rust-demo/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,32 @@
# rust graceful shutdown demo

This folder contains a simple rust function with [CloudWatch Lambda Insight](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-insights.html) enabled. CloudWatch Lambda Insight is
## Generating graceful shutdown signals

In order for Lambda to support graceful shutdown, at least one extension must be registered for your function.
This folder contains two examples demonstrating this. One uses an external extension, and one uses Rust `lambda-runtime` crate's
[`spawn_graceful_shutdown_handler() helper`] function,
which is backed by an internal extension.

For more information on the difference between the two, see [these Lambda Extensions API docs](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-extensions-api.html).

### Internal extension

The simplest way to enable shutdown signals in a `Rust` lambda is via [`spawn_graceful_shutdown_handler() helper`] function in the `lambda-runtime`. Under the hood, this registers an internal extension from wtihin your handler process.

The registered extension is a dummy no-op extension that doesn't subscribe to any events. This is very lightweight since it spawns a `tokio` task that keeps open a long-running connection with the Lambda execution environment, that never receives data. It therefore is essentially never woken and just occupies a small amount of heap memory.

The helper also accepts a callback that includes the logic to fire on `SIGTERM` or `SIGINT`, and generates the boilerplate to react to those signals for us.

You can also manually implement your own internal extension registration, if you want an internal extension that has
useful functionality. For instance, see this example of an internal extension that flushes telemetry: [ref](https://github.com/awslabs/aws-lambda-rust-runtime/blob/main/examples/extension-internal-flush). In that case, you could still use the helper, or you could also directly spawn signal handlers as demonstrated in the [Signal handling in the function](#signal-handling-in-the-function).

### External extension

Alternately, you can receive shutdown signals if an external extension is registered with the runtime. An external extension runs as a separate process alongside your function's process. This does not require code changes in your function handler (besides signal handling logic), but it might add additional overhead if you don't actually need an external extension.

The external extension example assumes that the [CloudWatch Lambda Insight](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-insights.html) is enabled. CloudWatch Lambda Insight is a
monitoring and troubleshooting solution for serverless application. Its agent is an external extension. Any external
extension will work. We use Lambda Insight extension simply because it is readily available.
extension will work. We use Lambda Insight extension simply because it is readily available and useful. Note that this may incurs additional billing fees.

*It is recommended to use the latest [Lambda Insights extension](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights-extension-versions.html)*
```yaml
@@ -14,8 +38,10 @@ extension will work. We use Lambda Insight extension simply because it is readil
- CloudWatchLambdaInsightsExecutionRolePolicy
```

In the function, a simple signal handler is added. It will be executed when the lambda runtime receives
a `SIGTERM`、`SIGINT` signal. You can also add more signal types yourself.
## Signal handling in the function

Inside our external extension example, or inside the [`spawn_graceful_shutdown_handler() helper`], a simple signal handler is added. It will be executed when the lambda runtime receives a `SIGTERM`、`SIGINT` signal. You can customize the logic that will fire when one of the signals is received.


```rust
// Handle SIGTERM signal:
@@ -28,19 +54,24 @@ tokio::spawn(async move {
_sigint = sigint.recv() => {
println!("[runtime] SIGINT received");
println!("[runtime] Graceful shutdown in progress ...");
// additional logic
println!("[runtime] Graceful shutdown completed");
std::process::exit(0);
},
_sigterm = sigterm.recv()=> {
println!("[runtime] SIGTERM received");
println!("[runtime] Graceful shutdown in progress ...");
// additional logic
println!("[runtime] Graceful shutdown completed");
std::process::exit(0);
},
}
});
```
Use the following AWS SAM CLI commands to build and deploy this demo.

## Deploy and Test

Use the following AWS SAM CLI commands from within one of the two examples' subdirectories to build and deploy this demo.

```bash
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/building-rust.html#building-rust-prerequisites
@@ -54,7 +85,7 @@ Take note of the output value of `RustHelloWorldApi`. Use curl to invoke the api
curl "replace this with value of RustHelloWorldApi"
```

Waite for several minutes, check the function's log messages in CloudWatch. If you see a log line containing "SIGTERM
Wait for several minutes, check the function's log messages in CloudWatch. If you see a log line containing "SIGTERM
received", it works!

for example:
@@ -92,4 +123,7 @@ is an experimental package. It is subject to change and intended only for evalua
- [Building Lambda functions with Rust](https://docs.aws.amazon.com/lambda/latest/dg/lambda-rust.html)
- [AWS SAM Documentation](https://docs.aws.amazon.com/serverless-application-model/)
- [Building Rust Lambda functions with Cargo Lambda](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/building-rust.html)
- [cargo-lambda](https://www.cargo-lambda.info/)
- [cargo-lambda](https://www.cargo-lambda.info/)


[`spawn_graceful_shutdown_handler() helper`]: https://docs.rs/lambda_runtime/latest/lambda_runtime/fn.spawn_graceful_shutdown_handler.html
16 changes: 0 additions & 16 deletions rust-demo/rust_app/Cargo.toml

This file was deleted.

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions rust-demo/rust_app_external_extension/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "ru-test"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lambda_runtime = "0.14"
serde = "1.0.136"
tokio = { version = "1", features = ["full"] }
aws_lambda_events = { version = "0.16", default-features = false, features = ["apigw", "alb"] }
serde_json = "1.0.108"
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::HashMap;

use aws_lambda_events::apigw::ApiGatewayProxyRequest;
use lambda_runtime::{Error, LambdaEvent, run, service_fn};
use lambda_runtime::{run, service_fn, tracing, Error, LambdaEvent};
use serde::{Deserialize, Serialize};
use serde_json::json;
use tokio::signal::unix::{signal, SignalKind};
@@ -17,8 +17,9 @@ struct Request {}
/// to be serialized into json. The runtime pays no attention
/// to the contents of the response payload.
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct Response {
statusCode: i32,
status_code: i32,
body: String,
}

@@ -30,15 +31,22 @@ struct Response {
async fn function_handler(event: LambdaEvent<ApiGatewayProxyRequest>) -> Result<Response, Error> {
// Prepare the response payload
let mut payload = HashMap::new();
let source_ip = &*(event.payload.request_context.identity.source_ip.unwrap().to_string());
let source_ip = &*(event
.payload
.request_context
.identity
.source_ip
.unwrap()
.to_string());
payload.insert("message", "hello rust");
payload.insert("source ip", source_ip);
payload.insert("architecture", std::env::consts::ARCH);
payload.insert("operating system", std::env::consts::OS);
tracing::info!("returning payload: {payload:#?}");
// Prepare the response
let body_content = json!(payload).to_string();
let resp = Response {
statusCode: 200,
status_code: 200,
body: body_content,
};

@@ -48,13 +56,7 @@ async fn function_handler(event: LambdaEvent<ApiGatewayProxyRequest>) -> Result<

#[tokio::main]
async fn main() -> Result<(), Error> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
// disable printing the name of the module in every log line.
.with_target(false)
// disabling time is handy because CloudWatch will add the ingestion time.
.without_time()
.init();
tracing::init_default_subscriber();

// Handle SIGTERM signal:
// https://tokio.rs/tokio/topics/shutdown
@@ -66,17 +68,19 @@ async fn main() -> Result<(), Error> {
_sigint = sigint.recv() => {
println!("[runtime] SIGINT received");
println!("[runtime] Graceful shutdown in progress ...");
println!("executing graceful shutdown handler logic");
println!("[runtime] Graceful shutdown completed");
std::process::exit(0);
},
_sigterm = sigterm.recv()=> {
println!("[runtime] SIGTERM received");
println!("[runtime] Graceful shutdown in progress ...");
println!("executing graceful shutdown handler logic");
println!("[runtime] Graceful shutdown completed");
std::process::exit(0);
},
}
});

run(service_fn(function_handler)).await
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
rust-graceful-shutdown-demo
rust-graceful-shutdown-demo-external-extension
lambda graceful shutdown-demo(rust edition)
lambda graceful shutdown-demo(rust edition with external extension)
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
@@ -17,8 +17,8 @@ Resources:
Metadata:
BuildMethod: rust-cargolambda # More info about Cargo Lambda: https://github.com/cargo-lambda/cargo-lambda
Properties:
FunctionName: graceful-shutdown-rust
CodeUri: ./rust_app # Points to dir of Cargo.toml
FunctionName: graceful-shutdown-rust-external-extension
CodeUri: . # Points to dir of Cargo.toml
Handler: bootstrap
Runtime: provided.al2023
Architectures:
1,291 changes: 1,291 additions & 0 deletions rust-demo/rust_app_internal_extension_from_helper/Cargo.lock

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions rust-demo/rust_app_internal_extension_from_helper/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "ru-test"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lambda_runtime = { version = "0.14", features = ["graceful-shutdown", "tracing"] }
serde = "1.0.136"
tokio = { version = "1", features = ["full"] }
aws_lambda_events = { version = "0.16", default-features = false, features = ["apigw"] }
serde_json = "1.0.108"
68 changes: 68 additions & 0 deletions rust-demo/rust_app_internal_extension_from_helper/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use std::collections::HashMap;

use aws_lambda_events::apigw::ApiGatewayProxyRequest;
use lambda_runtime::{
run, service_fn, spawn_graceful_shutdown_handler, tracing, Error, LambdaEvent,
};
use serde::{Deserialize, Serialize};
use serde_json::json;

/// This is a made-up example. Requests come into the runtime as unicode
/// strings in json format, which can map to any structure that implements `serde::Deserialize`
/// The runtime pays no attention to the contents of the request payload.
#[derive(Deserialize)]
struct Request {}

/// This is a made-up example of what a response structure may look like.
/// There is no restriction on what it can be. The runtime requires responses
/// to be serialized into json. The runtime pays no attention
/// to the contents of the response payload.
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct Response {
status_code: i32,
body: String,
}

/// This is the main body for the function.
/// Write your code inside it.
/// There are some code example in the following URLs:
/// - https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples
/// - https://github.com/aws-samples/serverless-rust-demo/
async fn function_handler(event: LambdaEvent<ApiGatewayProxyRequest>) -> Result<Response, Error> {
// Prepare the response payload
let mut payload = HashMap::new();
let source_ip = &*(event
.payload
.request_context
.identity
.source_ip
.unwrap()
.to_string());
payload.insert("message", "hello rust");
payload.insert("source ip", source_ip);
payload.insert("architecture", std::env::consts::ARCH);
payload.insert("operating system", std::env::consts::OS);
// Prepare the response
let body_content = json!(payload).to_string();
let resp = Response {
status_code: 200,
body: body_content,
};
tracing::info!("returning payload: {payload:#?}");

// Return `Response` (it will be serialized to JSON automatically by the runtime)
Ok(resp)
}

#[tokio::main]
async fn main() -> Result<(), Error> {
tracing::init_default_subscriber();

spawn_graceful_shutdown_handler(|| async move {
eprintln!("my custom shutdown logic");
})
.await;

run(service_fn(function_handler)).await
}
44 changes: 44 additions & 0 deletions rust-demo/rust_app_internal_extension_from_helper/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
rust-graceful-shutdown-demo-internal-extension
lambda graceful shutdown-demo(rust edition with internal extension)
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 3
MemorySize: 128

Resources:
RustHelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Metadata:
BuildMethod: rust-cargolambda # More info about Cargo Lambda: https://github.com/cargo-lambda/cargo-lambda
Properties:
FunctionName: graceful-shutdown-rust-internal-extension
CodeUri: . # Points to dir of Cargo.toml
Handler: bootstrap
Runtime: provided.al2023
Architectures:
- arm64
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /hello
Method: get
Outputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
RustHelloWorldApi:
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
RustHelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt RustHelloWorldFunction.Arn
RustHelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt RustHelloWorldFunctionRole.Arn