Skip to content

Commit 048c1e8

Browse files
committed
test: add multiconcurrency testing
1 parent 07de1f5 commit 048c1e8

3 files changed

Lines changed: 138 additions & 1 deletion

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "invocation-id-concurrent"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
lambda_runtime = { path = "../../lambda-runtime", features = ["concurrency-tokio"] }
8+
serde = "1.0.219"
9+
tokio = { version = "1", features = ["macros", "rt", "time"] }
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// This example requires the following input to succeed:
2+
// { "command": "do something" }
3+
4+
use lambda_runtime::{service_fn, tracing, Diagnostic, Error, LambdaEvent};
5+
use serde::{Deserialize, Serialize};
6+
7+
#[derive(Deserialize)]
8+
struct Request {
9+
command: String,
10+
sleep: u32
11+
}
12+
13+
#[derive(Serialize, Debug, PartialEq)]
14+
struct Response {
15+
req_id: String,
16+
inv_id: Option<String>,
17+
}
18+
19+
#[derive(Debug)]
20+
struct HandlerError(String);
21+
22+
impl std::fmt::Display for HandlerError {
23+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24+
write!(f, "{}", self.0)
25+
}
26+
}
27+
28+
impl From<HandlerError> for Diagnostic {
29+
fn from(e: HandlerError) -> Diagnostic {
30+
Diagnostic {
31+
error_type: "HandlerError".into(),
32+
error_message: e.0,
33+
}
34+
}
35+
}
36+
37+
38+
/**
39+
* Cross-wiring protection: duplicate request-id after timeout.
40+
41+
Timeline:
42+
t=0: Invoke A starts, handler sleeps 7s
43+
t=5: A times out (timeout=5s). Batch 1 completes with timeout error.
44+
t=5: Invoke B starts (same request-id), handler sleeps 4s
45+
t=7: A's handler wakes up, posts stale /response/{same-id}
46+
t=9: B's handler wakes up, posts correct /response/{same-id}
47+
48+
With invocation-id: A's stale post at t=7 gets 410 Gone. B responds at t=9 correctly.
49+
Without: A's stale response at t=7 is accepted for B (cross-wired).
50+
*/
51+
52+
#[tokio::main]
53+
async fn main() -> Result<(), Error> {
54+
// required to enable CloudWatch error logging by the runtime
55+
tracing::init_default_subscriber();
56+
let max_concurrency = std::env::var("AWS_LAMBDA_MAX_CONCURRENCY").unwrap_or_else(|_| "not set".to_string());
57+
tracing::info!(AWS_LAMBDA_MAX_CONCURRENCY = %max_concurrency, "starting concurrent handler");
58+
59+
let func = service_fn(my_handler);
60+
if let Err(err) = lambda_runtime::run_concurrent(func).await {
61+
tracing::error!(error = %err, "run error");
62+
return Err(err);
63+
}
64+
Ok(())
65+
}
66+
67+
pub(crate) async fn my_handler(event: LambdaEvent<Request>) -> Result<Response, HandlerError> {
68+
if event.payload.sleep > 0 {
69+
tokio::time::sleep(tokio::time::Duration::from_secs(event.payload.sleep.into())).await;
70+
}
71+
72+
let resp = Response {
73+
req_id: event.context.request_id,
74+
inv_id: event.context.invocation_id,
75+
};
76+
77+
Ok(resp)
78+
}
79+
80+
#[cfg(test)]
81+
mod tests {
82+
use super::*;
83+
use lambda_runtime::{Context, LambdaEvent};
84+
85+
#[tokio::test]
86+
async fn handler_returns_request_and_invocation_ids() {
87+
let mut context = Context::default();
88+
context.request_id = "req-123".to_string();
89+
context.invocation_id = Some("inv-456".to_string());
90+
91+
let payload = Request {
92+
command: "test".to_string(),
93+
sleep: 0,
94+
};
95+
let event = LambdaEvent { payload, context };
96+
let result = my_handler(event).await.unwrap();
97+
98+
assert_eq!(
99+
result,
100+
Response {
101+
req_id: "req-123".to_string(),
102+
inv_id: Some("inv-456".to_string()),
103+
}
104+
);
105+
}
106+
107+
#[tokio::test]
108+
async fn handler_works_without_invocation_id() {
109+
let mut context = Context::default();
110+
context.request_id = "req-789".to_string();
111+
// invocation_id defaults to None
112+
113+
let payload = Request {
114+
command: "test".to_string(),
115+
sleep: 0,
116+
};
117+
let event = LambdaEvent { payload, context };
118+
let result = my_handler(event).await.unwrap();
119+
120+
assert_eq!(
121+
result,
122+
Response {
123+
req_id: "req-789".to_string(),
124+
inv_id: None,
125+
}
126+
);
127+
}
128+
}

scripts/test-rie.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ fi
1818
CONTAINER_PID=$!
1919

2020
echo "Container started. Test with:"
21-
if [ "$EXAMPLE" = "basic-lambda" ] || [ "$EXAMPLE" = "basic-lambda-concurrent" ]; then
21+
if [ "$EXAMPLE" = "basic-lambda" ] || [ "$EXAMPLE" = "basic-lambda-concurrent" ] || [ "$EXAMPLE" = "invocation-id-concurrent" ]; then
2222
echo "curl -XPOST 'http://localhost:9000/2015-03-31/functions/function/invocations' -d '{\"command\": \"test from RIE\"}' -H 'Content-Type: application/json'"
2323
else
2424
echo "For example '$EXAMPLE', check examples/$EXAMPLE/src/main.rs for the expected payload format."

0 commit comments

Comments
 (0)