-
Notifications
You must be signed in to change notification settings - Fork 398
Expand file tree
/
Copy pathmain.rs
More file actions
129 lines (107 loc) · 3.62 KB
/
Copy pathmain.rs
File metadata and controls
129 lines (107 loc) · 3.62 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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// 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(rename = "command")]
_command: String,
sleep: u32,
}
#[derive(Serialize, Debug, PartialEq)]
struct Response {
req_id: String,
inv_id: Option<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;
}
let resp = Response {
req_id: event.context.request_id,
inv_id: event.context.invocation_id,
};
Ok(resp)
}
#[cfg(test)]
mod tests {
use super::*;
use lambda_runtime::{Context, LambdaEvent};
#[tokio::test]
async fn handler_returns_request_and_invocation_ids() {
let mut context = Context::default();
context.request_id = "req-123".to_string();
context.invocation_id = Some("inv-456".to_string());
let payload = Request {
_command: "test".to_string(),
sleep: 0,
};
let event = LambdaEvent { payload, context };
let result = my_handler(event).await.unwrap();
assert_eq!(
result,
Response {
req_id: "req-123".to_string(),
inv_id: Some("inv-456".to_string()),
}
);
}
#[tokio::test]
async fn handler_works_without_invocation_id() {
let mut context = Context::default();
context.request_id = "req-789".to_string();
// invocation_id defaults to None
let payload = Request {
_command: "test".to_string(),
sleep: 0,
};
let event = LambdaEvent { payload, context };
let result = my_handler(event).await.unwrap();
assert_eq!(
result,
Response {
req_id: "req-789".to_string(),
inv_id: None,
}
);
}
}