how to create custom context #405
-
I have an expensive task that should not be part of the handler. As that would be running the code again and again for each event call. According to AWS documentation the execution context is the place where these "global" code needs to go so that the output of the code is persistent over events. The docs say that "A new instance of the Context object is passed to each handler invocation". Is there a way create a custom context which saves the global state of the object between calls? |
Beta Was this translation helpful? Give feedback.
Replies: 10 comments
-
A bit of background on Lambda contexts. Each invocation of a Lambda function is provided a new context value which captures a set of introspective information about the function and optionally calling context information thats consistent across all types of event trigger. You can find documentation of how the Rust runtime represents that here. It's typically not a place to store user data because you'll get a new copy on each invocation as a function argument with new information. Globals in Lambda are often used specifically as an optimization for cached initialization. An example may be a function that requires a tokio Runtime in function code. Rather than recreate a new Runtime on every Lambda invocation which can be expensive, you might store a single Runtime in a thread local and share via RefCell to reduce the cost of initializing a new one on each . Here's a rough sketch for what that might look like. thread_local!(static RUNTIME: RefCell<Runtime> = {
RefCell::new(Runtime::new().unwrap())
});
fn main() {
lambda!(handler);
}
fn handler(
event: Value,
ctx: Context,
) -> Result<(), HandlerError> {
RUNTIME.with(|rt| {
rt.borrow_mut()
.block_on(future_work(event))?;
Ok(())
})
} https://docs.rs/lazy_static/1.3.0/lazy_static/ is another crate that you may find helpful. Another option which I haven't myself explored but I think would be interesting is to implement the Handler trait for a custom struct that captures what you may store in global state within its struct fields. Typically you'll be using closures or fn defs to implement your lambda which already have implementations for Below is a rough sketch for what that might look like struct Func {
data: usize
}
impl Handler<Value, usize, HandlerError> for Func {
fn run(&mut self, event: Value, ctx: Context) -> Result<usize, HandlerError> {
self.data += 1;
}
}
fn main() {
lambda!(Func { data: 0 });
} |
Beta Was this translation helpful? Give feedback.
-
Thank you @softprops for the overview, last option looks pretty neat! |
Beta Was this translation helpful? Give feedback.
-
I haven't tried it myself you so I'd be curious to hear how that goes for you |
Beta Was this translation helpful? Give feedback.
-
I gave the last option a try and its working pretty great for me. Don't have any code that I can share just yet but I'll try to put up a demo repo to link to soon. Thank you @softprops for that idea! |
Beta Was this translation helpful? Give feedback.
-
I was wondering if an example for the current /// Functions serving as ALB and API Gateway REST and HTTP API handlers must conform to this type.
///
/// This can be viewed as a `lambda::Handler` constrained to `http` crate `Request` and `Response` types
pub trait Handler: Sized {
/// The type of Error that this Handler will return
type Error;
/// The type of Response this Handler will return
type Response: IntoResponse;
/// The type of Future this Handler will return
type Fut: Future<Output = Result<Self::Response, Self::Error>> + 'static;
/// Function used to execute handler behavior
fn call(&mut self, event: Request, context: Context) -> Self::Fut;
} My attempt so far: impl Handler for MyStructWithContext {
type Error = Error;
type Response = Response<Body>;
type Fut = dyn Future<Output = Result<Self::Response, Self::Error>> + 'static;
fn call(&mut self, event: Request, context: Context) -> Self::Fut {
unimplemented!()
}
} ...results in:
...and I'm trying to fathom exactly what of those types doesn't have a known size. |
Beta Was this translation helpful? Give feedback.
-
I'm not sure if this is exactly what you mean but here's an example of an impl for a Fn type I think |
Beta Was this translation helpful? Give feedback.
-
Thanks for the response. That code sample you provided is what I based my code on. Thanks for reminding about the use of box here too. I think it’s clear that AWS lambda needs to at least provide a hook, or a great example of code initialisation for Rust. Even better, folding over state with a scan-style function would be great, but perhaps that’s another issue. :-) |
Beta Was this translation helpful? Give feedback.
-
The library has changed a little since this question was first asked, but it does work with BoxFuture as follows: use futures::future::BoxFuture;
use lambda_http::{handler, Body, Handler, Request, Response};
use lambda_runtime::{Context, Error};
struct Server {
some_global_setting: String,
}
impl Handler for Server {
type Error = Error;
type Response = Response<Body>;
type Fut = BoxFuture<'static, Result<Response<Body>, Error>>;
fn call(
&self,
request: Request,
context: Context,
) -> BoxFuture<'static, Result<Response<Body>, Error>> {
//some_other_async_function(self.global_settings.clone(), request, context).boxed()
}
}
#[tokio::main]
async fn main() -> Result<(), Error> {
let server = Server {
some_global_setting: "blah".to_string();
};
let wrapped_handler = handler(server);
lambda_runtime::run(wrapped_handler).await?;
Ok(())
} If this is the recommended solution then I can add it to the README |
Beta Was this translation helpful? Give feedback.
-
Am trying to do it a bit differently, and am struggling with lifetime management: pub fn my_handler_fn<F>(f: F, c: Client) -> MyHandlerFn<F> {
MyHandlerFn { f, c }
}
#[derive(Clone, Debug)]
pub struct MyHandlerFn<F> {
f: F,
c: Client
}
impl<F, A, B, Error, Fut> Handler<A, B> for MyHandlerFn<F>
where
F: Fn(A, lambda_runtime::Context, &Client) -> Fut,
Fut: Future<Output = Result<B, Error>>,
Error: Into<Box<dyn std::error::Error + Send + Sync + 'static>> + std::fmt::Display,
{
type Error = Error;
type Fut = Fut;
fn call(&self, req: A, ctx: lambda_runtime::Context) -> Self::Fut {
(self.f)(req, ctx, &self.c)
}
} And then the following: #[tokio::main]
async fn main() -> Result<(), Error> {
let shared_config = aws_config::load_from_env().await;
let client = Client::new(&shared_config);
let func = my_handler_fn(my_handler, client);
lambda_runtime::run(func).await?;
Ok(())
} But I get Anybody know how to resolve this? |
Beta Was this translation helpful? Give feedback.
-
Hey @Jacco ! Could you paste the entire error message? Also, is there any specific reason why you are creating a separate handler function rather than just using the By the way, I've just added this example using a struct that implements the Handler trait for reference. |
Beta Was this translation helpful? Give feedback.
Hey @Jacco !
Could you paste the entire error message?
Also, is there any specific reason why you are creating a separate handler function rather than just using the
call
function in yourMyHandlerFn
struct?By the way, I've just added this example using a struct that implements the Handler trait for reference.