Skip to content

Commit

Permalink
Implemented SnapStart Runtime Hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
mbfreder committed Oct 3, 2023
1 parent d1687e1 commit f4745e0
Show file tree
Hide file tree
Showing 16 changed files with 785 additions and 453 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[workspace]
resolver = "2"
members = [
"lambda-http",
"lambda-integration-tests",
Expand Down
1 change: 1 addition & 0 deletions examples/basic-runtime-hooks/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
19 changes: 19 additions & 0 deletions examples/basic-runtime-hooks/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "basic-runtime-hooks"
version = "0.1.0"
edition = "2021"


# Use cargo-edit(https://github.com/killercup/cargo-edit#installation)
# to manage dependencies.
# Running `cargo add DEPENDENCY_NAME` will
# add the latest version of a dependency to the list,
# and it will keep the alphabetic ordering for you.

[dependencies]
lambda_runtime = { path = "../../lambda-runtime" }
serde = "1.0.136"
tokio = { version = "1", features = ["macros"] }
tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] }
uuid = { version = "1.4.1", features = ["v4"]}
11 changes: 11 additions & 0 deletions examples/basic-runtime-hooks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# AWS Lambda Function example

## Build & Deploy

1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation)
2. Build the function with `cargo lambda build --release`
3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE`

## Build for ARM 64

Build the function with `cargo lambda build --release --arm64`
94 changes: 94 additions & 0 deletions examples/basic-runtime-hooks/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// This example demonstrates use of shared resources such as DB connections
// or local caches that can be initialized at the start of the runtime and
// reused by subsequent lambda handler calls.
// Run it with the following input:
// { "command": "do something" }

use lambda_runtime::{crac::Resource, service_fn, Error, LambdaEvent, Runtime};
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use uuid::Uuid;

/// This is also 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 {
command: String,
}

/// 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)]
struct Response {
req_id: String,
msg: String,
secret: String,
}

struct SharedClient {
name: &'static str,
secret: RefCell<String>,
}

impl SharedClient {
fn new(name: &'static str, secret: String) -> Self {
Self {
name,
secret: RefCell::new(secret),
}
}

fn response(&self, req_id: String, command: String) -> Response {
Response {
req_id,
msg: format!("Command {} executed by {}.", command, self.name),
secret: self.secret.borrow().clone(),
}
}
}

impl Resource for SharedClient {
fn before_checkpoint(&self) -> Result<(), Error> {
// clear the secret before checkpointing
*self.secret.borrow_mut() = String::new();
tracing::info!("in before_checkpoint: secret={:?}", self.secret.borrow());
Ok(())
}
fn after_restore(&self) -> Result<(), Error> {
// regenerate the secret after restoring
let secret = Uuid::new_v4().to_string();
*self.secret.borrow_mut() = secret;
tracing::info!("in after_restore: secret={:?}", self.secret.borrow());
Ok(())
}
}

#[tokio::main]
async fn main() -> Result<(), Error> {
// required to enable CloudWatch error logging by the runtime
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();

let secret = Uuid::new_v4().to_string();
let client = SharedClient::new("Shared Client 1 (perhaps a database)", secret);
let client_ref = &client;
tracing::info!("In main function: secret={:?}", client_ref.secret.borrow());

Runtime::new()
.register(client_ref)
.run(service_fn(move |event: LambdaEvent<Request>| async move {
tracing::info!("In handler function: secret={:?}", client_ref.secret.borrow());
let command = event.payload.command;
Ok::<Response, Error>(client_ref.response(event.context.request_id, command))
}))
.await?;
Ok(())
}
1 change: 1 addition & 0 deletions examples/http-runtime-hooks/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
22 changes: 22 additions & 0 deletions examples/http-runtime-hooks/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "http-runtime-hooks"
version = "0.1.0"
edition = "2021"

# Starting in Rust 1.62 you can use `cargo add` to add dependencies
# to your project.
#
# If you're using an older Rust version,
# download cargo-edit(https://github.com/killercup/cargo-edit#installation)
# to install the `add` subcommand.
#
# Running `cargo add DEPENDENCY_NAME` will
# add the latest version of a dependency to the list,
# and it will keep the alphabetic ordering for you.

[dependencies]
lambda_http = { path = "../../lambda-http" }
tokio = { version = "1", features = ["macros"] }
tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] }
uuid = { version = "1.4.1", features = ["v4"]}
11 changes: 11 additions & 0 deletions examples/http-runtime-hooks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# AWS Lambda Function example

## Build & Deploy

1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation)
2. Build the function with `cargo lambda build --release`
3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE`

## Build for ARM 64

Build the function with `cargo lambda build --release --arm64`
115 changes: 115 additions & 0 deletions examples/http-runtime-hooks/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use lambda_http::{crac, service_fn, Body, Error, IntoResponse, Request, RequestExt, Response, Runtime};
use std::sync::Arc;
use std::sync::RwLock;
use uuid::Uuid;

struct SharedClient {
name: &'static str,
secret: Arc<RwLock<String>>,
}

impl SharedClient {
fn new(name: &'static str, secret: String) -> Self {
Self {
name,
secret: Arc::new(RwLock::new(secret)),
}
}

fn response(&self, req_id: String, first_name: &str) -> String {
format!("{}: Client ({}) invoked by {}.", req_id, self.name, first_name)
}
}

impl crac::Resource for SharedClient {
fn before_checkpoint(&self) -> Result<(), Error> {
// clear the secret before checkpointing
{
let mut write_lock = self.secret.write().unwrap();
*write_lock = String::new();
} // release the write lock

{
tracing::info!("in before_checkpoint: secret={:?}", self.secret.read().unwrap());
} // release the read lock

Ok(())
}
fn after_restore(&self) -> Result<(), Error> {
// regenerate the secret after restoring
{
let mut write_lock = self.secret.write().unwrap();
*write_lock = Uuid::new_v4().to_string();
} // release the write lock

{
tracing::info!("in after_restore: secret={:?}", self.secret.read().unwrap());
} // release the read lock

Ok(())
}
}

#[tokio::main]
async fn main() -> Result<(), Error> {
// required to enable CloudWatch error logging by the runtime
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();

// Create the "client" and a reference to it, so that we can pass this into the handler closure below.
let secret = Uuid::new_v4().to_string();
let shared_client = SharedClient::new("random_client_name_1", secret);
let shared_client_ref = &shared_client;
{
tracing::info!(
"In main function: secret={:?}",
shared_client_ref.secret.read().unwrap()
);
} // release the read lock

// Define a closure here that makes use of the shared client.
let handler_func_closure = move |event: Request| async move {
{
tracing::info!(
"In handler function: secret={:?}",
shared_client_ref.secret.read().unwrap()
);
} // release the read lock

Result::<Response<Body>, Error>::Ok(
match event
.query_string_parameters_ref()
.and_then(|params| params.first("first_name"))
{
Some(first_name) => {
shared_client_ref
.response(
event
.lambda_context_ref()
.map(|ctx| ctx.request_id.clone())
.unwrap_or_default(),
first_name,
)
.into_response()
.await
}
None => Response::builder()
.status(400)
.body("Empty first name".into())
.expect("failed to render response"),
},
)
};

// Pass the closure to the runtime here.
Runtime::new()
.register(shared_client_ref)
.run(service_fn(handler_func_closure))
.await?;
Ok(())
}
Loading

0 comments on commit f4745e0

Please sign in to comment.