-
Notifications
You must be signed in to change notification settings - Fork 398
Expand file tree
/
Copy pathmain.rs
More file actions
103 lines (83 loc) · 2.88 KB
/
Copy pathmain.rs
File metadata and controls
103 lines (83 loc) · 2.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// This example requires the following input to succeed:
// { "command": "do something" }
use lambda_runtime::{service_fn, tracing, Diagnostic, Error, LambdaEvent};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct Request {
#[serde(default, rename = "command")]
_command: String,
sleep: u32,
}
#[derive(Serialize, Debug, PartialEq)]
struct Response {
from: String,
}
#[derive(Debug)]
struct HandlerError(String);
impl std::fmt::Display for HandlerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<HandlerError> for Diagnostic {
fn from(e: HandlerError) -> Diagnostic {
Diagnostic {
error_type: "HandlerError".into(),
error_message: e.0,
}
}
}
/**
* Cross-wiring protection: duplicate request-id after timeout.
Timeline:
t=0: Invoke A starts, handler sleeps 7s
t=5: A times out (timeout=5s). Batch 1 completes with timeout error.
t=5: Invoke B starts (same request-id), handler sleeps 4s
t=7: A's handler wakes up, posts stale /response/{same-id}
t=9: B's handler wakes up, posts correct /response/{same-id}
With invocation-id: A's stale post at t=7 gets 410 Gone. B responds at t=9 correctly.
Without: A's stale response at t=7 is accepted for B (cross-wired).
*/
#[tokio::main]
async fn main() -> Result<(), Error> {
// required to enable CloudWatch error logging by the runtime
tracing::init_default_subscriber();
let max_concurrency = std::env::var("AWS_LAMBDA_MAX_CONCURRENCY").unwrap_or_else(|_| "not set".to_string());
tracing::info!(AWS_LAMBDA_MAX_CONCURRENCY = %max_concurrency, "starting concurrent handler");
let func = service_fn(my_handler);
if let Err(err) = lambda_runtime::run_concurrent(func).await {
tracing::error!(error = %err, "run error");
return Err(err);
}
Ok(())
}
pub(crate) async fn my_handler(event: LambdaEvent<Request>) -> Result<Response, HandlerError> {
if event.payload.sleep > 0 {
tokio::time::sleep(tokio::time::Duration::from_secs(event.payload.sleep.into())).await;
}
Ok(Response {
from: event.payload._command,
})
}
#[cfg(test)]
mod tests {
use super::*;
use lambda_runtime::{Context, LambdaEvent};
#[tokio::test]
async fn handler_echoes_marker() {
let event = LambdaEvent {
payload: Request {
_command: "invoke-B".into(),
sleep: 0,
},
context: Context::default(),
};
let result = my_handler(event).await.unwrap();
assert_eq!(result, Response { from: "invoke-B".into() });
}
#[test]
fn request_defaults_missing_command() {
let request: Request = serde_json::from_str(r#"{"sleep": 0}"#).unwrap();
assert_eq!(request._command, "");
}
}