Skip to content

Commit 53637e7

Browse files
authored
Remove function config allocations per invocation. (#732)
Every invocation clones the function config. This allocates memory in the heap for no reason. This change removes those extra allocations by wrapping the config into an Arc and sharing that between invocations. Signed-off-by: David Calavera <[email protected]>
1 parent d3e365c commit 53637e7

File tree

4 files changed

+69
-43
lines changed

4 files changed

+69
-43
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[workspace]
2+
resolver = "2"
23
members = [
34
"lambda-http",
45
"lambda-integration-tests",

lambda-runtime/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ hyper = { version = "0.14.20", features = [
3232
"server",
3333
] }
3434
futures = "0.3"
35-
serde = { version = "1", features = ["derive"] }
35+
serde = { version = "1", features = ["derive", "rc"] }
3636
serde_json = "^1"
3737
bytes = "1.0"
3838
http = "0.2"

lambda-runtime/src/lib.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use std::{
2323
future::Future,
2424
marker::PhantomData,
2525
panic,
26+
sync::Arc,
2627
};
2728
use tokio::io::{AsyncRead, AsyncWrite};
2829
use tokio_stream::{Stream, StreamExt};
@@ -58,6 +59,8 @@ pub struct Config {
5859
pub log_group: String,
5960
}
6061

62+
type RefConfig = Arc<Config>;
63+
6164
impl Config {
6265
/// Attempts to read configuration from environment variables.
6366
pub fn from_env() -> Result<Self, Error> {
@@ -86,7 +89,7 @@ where
8689

8790
struct Runtime<C: Service<http::Uri> = HttpConnector> {
8891
client: Client<C>,
89-
config: Config,
92+
config: RefConfig,
9093
}
9194

9295
impl<C> Runtime<C>
@@ -127,8 +130,7 @@ where
127130
continue;
128131
}
129132

130-
let ctx: Context = Context::try_from(parts.headers)?;
131-
let ctx: Context = ctx.with_config(&self.config);
133+
let ctx: Context = Context::try_from((self.config.clone(), parts.headers))?;
132134
let request_id = &ctx.request_id.clone();
133135

134136
let request_span = match &ctx.xray_trace_id {
@@ -263,7 +265,10 @@ where
263265
trace!("Loading config from env");
264266
let config = Config::from_env()?;
265267
let client = Client::builder().build().expect("Unable to create a runtime client");
266-
let runtime = Runtime { client, config };
268+
let runtime = Runtime {
269+
client,
270+
config: Arc::new(config),
271+
};
267272

268273
let client = &runtime.client;
269274
let incoming = incoming(client);
@@ -294,15 +299,15 @@ mod endpoint_tests {
294299
},
295300
simulated,
296301
types::Diagnostic,
297-
Error, Runtime,
302+
Config, Error, Runtime,
298303
};
299304
use futures::future::BoxFuture;
300305
use http::{uri::PathAndQuery, HeaderValue, Method, Request, Response, StatusCode, Uri};
301306
use hyper::{server::conn::Http, service::service_fn, Body};
302307
use lambda_runtime_api_client::Client;
303308
use serde_json::json;
304309
use simulated::DuplexStreamWrapper;
305-
use std::{convert::TryFrom, env, marker::PhantomData};
310+
use std::{convert::TryFrom, env, marker::PhantomData, sync::Arc};
306311
use tokio::{
307312
io::{self, AsyncRead, AsyncWrite},
308313
select,
@@ -531,9 +536,12 @@ mod endpoint_tests {
531536
if env::var("AWS_LAMBDA_LOG_GROUP_NAME").is_err() {
532537
env::set_var("AWS_LAMBDA_LOG_GROUP_NAME", "test_log");
533538
}
534-
let config = crate::Config::from_env().expect("Failed to read env vars");
539+
let config = Config::from_env().expect("Failed to read env vars");
535540

536-
let runtime = Runtime { client, config };
541+
let runtime = Runtime {
542+
client,
543+
config: Arc::new(config),
544+
};
537545
let client = &runtime.client;
538546
let incoming = incoming(client).take(1);
539547
runtime.run(incoming, f).await?;
@@ -568,13 +576,13 @@ mod endpoint_tests {
568576

569577
let f = crate::service_fn(func);
570578

571-
let config = crate::Config {
579+
let config = Arc::new(Config {
572580
function_name: "test_fn".to_string(),
573581
memory: 128,
574582
version: "1".to_string(),
575583
log_stream: "test_stream".to_string(),
576584
log_group: "test_log".to_string(),
577-
};
585+
});
578586

579587
let runtime = Runtime { client, config };
580588
let client = &runtime.client;

lambda-runtime/src/types.rs

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{Config, Error};
1+
use crate::{Error, RefConfig};
22
use base64::prelude::*;
33
use bytes::Bytes;
44
use http::{HeaderMap, HeaderValue, StatusCode};
@@ -97,7 +97,7 @@ pub struct CognitoIdentity {
9797
/// are populated using the [Lambda environment variables](https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html)
9898
/// and [the headers returned by the poll request to the Runtime APIs](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html#runtimes-api-next).
9999
#[non_exhaustive]
100-
#[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)]
100+
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
101101
pub struct Context {
102102
/// The AWS request ID generated by the Lambda service.
103103
pub request_id: String,
@@ -117,12 +117,14 @@ pub struct Context {
117117
/// Lambda function configuration from the local environment variables.
118118
/// Includes information such as the function name, memory allocation,
119119
/// version, and log streams.
120-
pub env_config: Config,
120+
pub env_config: RefConfig,
121121
}
122122

123-
impl TryFrom<HeaderMap> for Context {
123+
impl TryFrom<(RefConfig, HeaderMap)> for Context {
124124
type Error = Error;
125-
fn try_from(headers: HeaderMap) -> Result<Self, Self::Error> {
125+
fn try_from(data: (RefConfig, HeaderMap)) -> Result<Self, Self::Error> {
126+
let env_config = data.0;
127+
let headers = data.1;
126128
let client_context: Option<ClientContext> = if let Some(value) = headers.get("lambda-runtime-client-context") {
127129
serde_json::from_str(value.to_str()?)?
128130
} else {
@@ -158,13 +160,20 @@ impl TryFrom<HeaderMap> for Context {
158160
.map(|v| String::from_utf8_lossy(v.as_bytes()).to_string()),
159161
client_context,
160162
identity,
161-
..Default::default()
163+
env_config,
162164
};
163165

164166
Ok(ctx)
165167
}
166168
}
167169

170+
impl Context {
171+
/// The execution deadline for the current invocation.
172+
pub fn deadline(&self) -> SystemTime {
173+
SystemTime::UNIX_EPOCH + Duration::from_millis(self.deadline)
174+
}
175+
}
176+
168177
/// Incoming Lambda request containing the event payload and context.
169178
#[derive(Clone, Debug)]
170179
pub struct LambdaEvent<T> {
@@ -273,6 +282,8 @@ where
273282
#[cfg(test)]
274283
mod test {
275284
use super::*;
285+
use crate::Config;
286+
use std::sync::Arc;
276287

277288
#[test]
278289
fn round_trip_lambda_error() {
@@ -292,6 +303,8 @@ mod test {
292303

293304
#[test]
294305
fn context_with_expected_values_and_types_resolves() {
306+
let config = Arc::new(Config::default());
307+
295308
let mut headers = HeaderMap::new();
296309
headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id"));
297310
headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123"));
@@ -300,16 +313,18 @@ mod test {
300313
HeaderValue::from_static("arn::myarn"),
301314
);
302315
headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn"));
303-
let tried = Context::try_from(headers);
316+
let tried = Context::try_from((config, headers));
304317
assert!(tried.is_ok());
305318
}
306319

307320
#[test]
308321
fn context_with_certain_missing_headers_still_resolves() {
322+
let config = Arc::new(Config::default());
323+
309324
let mut headers = HeaderMap::new();
310325
headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id"));
311326
headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123"));
312-
let tried = Context::try_from(headers);
327+
let tried = Context::try_from((config, headers));
313328
assert!(tried.is_ok());
314329
}
315330

@@ -338,7 +353,9 @@ mod test {
338353
"lambda-runtime-client-context",
339354
HeaderValue::from_str(&client_context_str).unwrap(),
340355
);
341-
let tried = Context::try_from(headers);
356+
357+
let config = Arc::new(Config::default());
358+
let tried = Context::try_from((config, headers));
342359
assert!(tried.is_ok());
343360
let tried = tried.unwrap();
344361
assert!(tried.client_context.is_some());
@@ -347,17 +364,20 @@ mod test {
347364

348365
#[test]
349366
fn context_with_empty_client_context_resolves() {
367+
let config = Arc::new(Config::default());
350368
let mut headers = HeaderMap::new();
351369
headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id"));
352370
headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123"));
353371
headers.insert("lambda-runtime-client-context", HeaderValue::from_static("{}"));
354-
let tried = Context::try_from(headers);
372+
let tried = Context::try_from((config, headers));
355373
assert!(tried.is_ok());
356374
assert!(tried.unwrap().client_context.is_some());
357375
}
358376

359377
#[test]
360378
fn context_with_identity_resolves() {
379+
let config = Arc::new(Config::default());
380+
361381
let cognito_identity = CognitoIdentity {
362382
identity_id: String::new(),
363383
identity_pool_id: String::new(),
@@ -370,7 +390,7 @@ mod test {
370390
"lambda-runtime-cognito-identity",
371391
HeaderValue::from_str(&cognito_identity_str).unwrap(),
372392
);
373-
let tried = Context::try_from(headers);
393+
let tried = Context::try_from((config, headers));
374394
assert!(tried.is_ok());
375395
let tried = tried.unwrap();
376396
assert!(tried.identity.is_some());
@@ -379,6 +399,8 @@ mod test {
379399

380400
#[test]
381401
fn context_with_bad_deadline_type_is_err() {
402+
let config = Arc::new(Config::default());
403+
382404
let mut headers = HeaderMap::new();
383405
headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id"));
384406
headers.insert(
@@ -390,86 +412,81 @@ mod test {
390412
HeaderValue::from_static("arn::myarn"),
391413
);
392414
headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn"));
393-
let tried = Context::try_from(headers);
415+
let tried = Context::try_from((config, headers));
394416
assert!(tried.is_err());
395417
}
396418

397419
#[test]
398420
fn context_with_bad_client_context_is_err() {
421+
let config = Arc::new(Config::default());
422+
399423
let mut headers = HeaderMap::new();
400424
headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id"));
401425
headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123"));
402426
headers.insert(
403427
"lambda-runtime-client-context",
404428
HeaderValue::from_static("BAD-Type,not JSON"),
405429
);
406-
let tried = Context::try_from(headers);
430+
let tried = Context::try_from((config, headers));
407431
assert!(tried.is_err());
408432
}
409433

410434
#[test]
411435
fn context_with_empty_identity_is_err() {
436+
let config = Arc::new(Config::default());
437+
412438
let mut headers = HeaderMap::new();
413439
headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id"));
414440
headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123"));
415441
headers.insert("lambda-runtime-cognito-identity", HeaderValue::from_static("{}"));
416-
let tried = Context::try_from(headers);
442+
let tried = Context::try_from((config, headers));
417443
assert!(tried.is_err());
418444
}
419445

420446
#[test]
421447
fn context_with_bad_identity_is_err() {
448+
let config = Arc::new(Config::default());
449+
422450
let mut headers = HeaderMap::new();
423451
headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id"));
424452
headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123"));
425453
headers.insert(
426454
"lambda-runtime-cognito-identity",
427455
HeaderValue::from_static("BAD-Type,not JSON"),
428456
);
429-
let tried = Context::try_from(headers);
457+
let tried = Context::try_from((config, headers));
430458
assert!(tried.is_err());
431459
}
432460

433461
#[test]
434462
#[should_panic]
435463
#[allow(unused_must_use)]
436464
fn context_with_missing_request_id_should_panic() {
465+
let config = Arc::new(Config::default());
466+
437467
let mut headers = HeaderMap::new();
438468
headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id"));
439469
headers.insert(
440470
"lambda-runtime-invoked-function-arn",
441471
HeaderValue::from_static("arn::myarn"),
442472
);
443473
headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn"));
444-
Context::try_from(headers);
474+
Context::try_from((config, headers));
445475
}
446476

447477
#[test]
448478
#[should_panic]
449479
#[allow(unused_must_use)]
450480
fn context_with_missing_deadline_should_panic() {
481+
let config = Arc::new(Config::default());
482+
451483
let mut headers = HeaderMap::new();
452484
headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123"));
453485
headers.insert(
454486
"lambda-runtime-invoked-function-arn",
455487
HeaderValue::from_static("arn::myarn"),
456488
);
457489
headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn"));
458-
Context::try_from(headers);
459-
}
460-
}
461-
462-
impl Context {
463-
/// Add environment details to the context by setting `env_config`.
464-
pub fn with_config(self, config: &Config) -> Self {
465-
Self {
466-
env_config: config.clone(),
467-
..self
468-
}
469-
}
470-
471-
/// The execution deadline for the current invocation.
472-
pub fn deadline(&self) -> SystemTime {
473-
SystemTime::UNIX_EPOCH + Duration::from_millis(self.deadline)
490+
Context::try_from((config, headers));
474491
}
475492
}

0 commit comments

Comments
 (0)