Fix RuntimeApiClientFuture Visibility and Response Handling
Maintainer thường phản hồi trong vòng 11 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 25/100
Hướng nghiên cứu
Bắt đầu với lambda-runtime/src/layers/api_client.rs và xem xét các PR #1091, #1105 và #1109 để hiểu các đánh đổi chưa được giải quyết liên quan đến API và việc xử lý response. Công việc hoàn tất khi một cách tiếp cận được thống nhất, vấn đề về visibility và exhaustiveness của RuntimeApiClientFuture được xử lý, và các response không phải 2xx giữ lại body của chúng và trả về các error result.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Background
The RuntimeApiClientFuture enum in lambda-runtime/src/layers/api_client.rs has several design issues that prevent proper error handling and violate Rust API design best practices. These issues were identified during discussions in PRs #1091 and #1105.
Current Problems
1. Missing #[non_exhaustive] Attribute
The RuntimeApiClientFuture enum is publicly exposed but lacks the #[non_exhaustive] attribute:
pub enum RuntimeApiClientFuture<F> {
First(#[pin] F, Arc<Client>),
Second(#[pin] BoxFuture<'static, Result<http::Response<Incoming>, BoxError>>),
}
This is a semver hazard because:
- Adding new variants becomes a breaking change for downstream users who pattern match on this enum
- The enum is accessible as an associated type on
RuntimeApiClientService, which is publicly visible - Any user building custom runtimes on top of our runtime client could be affected
2. Incorrect Visibility
The enum and its variants are fully public, exposing internal implementation details that were likely not intended to be part of the public API. As noted in PR #1105:
3. Cannot Access Response Body
The current implementation discards the response body for non-2xx responses, making it impossible to read error details from the Lambda Runtime API:
Ok(resp) if !resp.status().is_success() => {
let status = resp.status();
log_or_print!(
tracing: tracing::error!(status = %status, "Lambda Runtime API returned non-200 response"),
fallback: eprintln!("Lambda Runtime API returned non-200 response: status={status}")
);
// We cannot access resp.body() here to log the actual error message!
break Ok(()); // Returns Ok despite error!
}
This is particularly problematic for 410 Gone responses (function timeouts), where we have to add hardcoded messages instead of reading the actual error from the API.
4. Returns Ok(()) for Non-2xx Status Codes
The most counterintuitive behavior is that the client returns Ok(()) even when the Lambda Runtime API returns error status codes:
Ok(resp) if !resp.status().is_success() => {
// ... logging ...
// Return Ok to maintain existing contract - runtime continues despite API errors
break Ok(());
}
This makes error handling confusing. A non-2xx HTTP response should be treated as an error, not success.
Impact Assessment
Based on code search analysis mentioned in PR #1105:
The impact is expected to be minimal:
- Internal machinery doesn't rely on the specific type surface
- Only users who built custom higher-level runtimes would be affected
- Forks of the api client would need updates
Proposed Solutions
Since these changes are breaking, we should bundle them together in a single major version bump. Here are the recommended approaches:
Option 1: Add #[non_exhaustive] and Fix Response Handling (Recommended)
Changes:
- Add
#[non_exhaustive]toRuntimeApiClientFuture - Change the return type to properly represent errors
- Read and log response bodies for non-2xx responses
- Return
Errfor non-2xx status codes instead ofOk
Implementation:
#[non_exhaustive]
pub enum RuntimeApiClientFuture<F> {
First(#[pin] F, Arc<Client>),
Second(#[pin] BoxFuture<'static, Result<http::Response<Incoming>, BoxError>>),
}
impl<F> Future for RuntimeApiClientFuture<F>
where
F: Future<Output = Result<http::Request<Body>, BoxError>>,
{
type Output = Result<(), BoxError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
task::Poll::Ready(loop {
match self.as_mut().project() {
// ... First variant handling ...
RuntimeApiClientFutureProj::Second(fut) => match ready!(fut.poll(cx)) {
Ok(resp) if !resp.status().is_success() => {
let status = resp.status();
// Read the response body to get actual error details
let body = read_body_to_string(resp).await;
log_or_print!(
tracing: tracing::error!(
status = %status,
body = %body,
"Lambda Runtime API returned non-2xx response"
),
fallback: eprintln!(
"Lambda Runtime API returned non-2xx response: status={status}, body={body}"
)
);
// Return Err for non-2xx responses
break Err(format!("Runtime API error: {} - {}", status, body).into());
}
Ok(_) => break Ok(()),
Err(err) => break Err(err),
},
}
})
}
}
Pros:
- Fixes all issues in one go
- Proper error semantics
- Better debugging experience with actual error messages
- Future-proof with
#[non_exhaustive]
Cons:
- Breaking change for anyone matching on the enum
- Breaking change for anyone relying on
Ok(())for non-2xx responses
Option 2: Deprecate and Replace with New Type
Changes:
Option 2: Deprecate and Replace with New Type
Changes:
- Create a new
RuntimeApiClientFutureV2with correct attributes - Create a new
RuntimeApiClientServiceV2that uses the new future type - Deprecate the old
RuntimeApiClientFutureandRuntimeApiClientService - Update internal usage to new types
- Remove old types in next major version
Rationale:
Since RuntimeApiClientFuture is exposed as an associated type on RuntimeApiClientService, we cannot change the future type without also creating a new service type. The service's Future associated type is part of its public API contract.
Implementation:
// Old types - deprecated
#[deprecated(since = "0.x.0", note = "Use RuntimeApiClientServiceV2 instead")]
pub struct RuntimeApiClientService<S> {
inner: S,
client: Arc<Client>,
}
#[deprecated(since = "0.x.0", note = "Use RuntimeApiClientFutureV2 instead")]
pub enum RuntimeApiClientFuture<F> {
First(#[pin] F, Arc<Client>),
Second(#[pin] BoxFuture<'static, Result<http::Response<Incoming>, BoxError>>),
}
// New types - correct design
pub struct RuntimeApiClientServiceV2<S> {
inner: S,
client: Arc<Client>,
}
impl<S> Service<LambdaInvocation> for RuntimeApiClientServiceV2<S>
where
S: Service<LambdaInvocation>,
S::Future: Future<Output = Result<http::Request<Body>, BoxError>>,
S::Error: Into<BoxError>,
{
type Response = ();
type Error = BoxError;
type Future = RuntimeApiClientFutureV2<S::Future>;
// ... implementation ...
}
#[non_exhaustive]
pub enum RuntimeApiClientFutureV2<F> {
First(#[pin] F, Arc<Client>),
Second(#[pin] BoxFuture<'static, Result<http::Response<Incoming>, BoxError>>),
}
Pros:
- Gentler migration path
- Gives users time to update
- Old behavior remains available during deprecation period
Cons:
- Maintains broken behavior longer
- Significantly more code to maintain during transition (duplicate service + future implementations)
- Still requires breaking change eventually
- Users need to update both service and future type references
- More complex migration story
Option 3 Changing the overall behaviour of client returning body.
Another solution proposed by @jlizen is to directly change how the client works at in lambda-rust-api-client. Like the following.
self.client
.request(req)
.map_err(Into::into)
.then(|res| async move {
match res {
Ok(resp) if !resp.status().is_success() => {
let status = resp.status();
match resp.into_body().await {
// convert into a BoxError containing the status and optional body
// which is nicely loggable in the upper layer, or you could also log it here.
// If you need to specifically log ONLY this case rather than other BoxErrors, you could newtype
// a marker type that we downcast the stderror to
Err(BoxError::new(format!("my message with status code + body")))
}
}
_ => res,
}
})
.boxed()
Pros:
- In this case we are only breaking the behaviour of client returning OK instead of ERR. It is a behavioural breaking chane.
- Proper error semantics
- Better debugging experience with actual error messages
Cons:
RuntimeApiClientFutureis not fixed- await at
Clientlevel.
Error Types
Consider creating a dedicated error type for Runtime API errors:
#[derive(Debug, thiserror::Error)]
pub enum RuntimeApiError {
#[error("Runtime API returned {status}: {body}")]
NonSuccessResponse { status: StatusCode, body: String },
#[error("Request build failed: {0}")]
RequestBuildError(#[source] BoxError),
#[error("Network error: {0}")]
NetworkError(#[source] BoxError),
}
References
- PR #1091: https://github.com/aws/aws-lambda-rust-runtime/pull/1091
- PR #1105: https://github.com/aws/aws-lambda-rust-runtime/pull/1105
- PR https://github.com/aws/aws-lambda-rust-runtime/pull/1109
- Current implementation:
lambda-runtime/src/layers/api_client.rs
- Ngôn ngữ chính
- Rust
- Star
- 3.6k
- Fork
- 398
- Merge trung bình
- 7 ngày 4 giờ
- Pull request đã merge (30 ngày)
- 3
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của aws/aws-lambda-rust-runtime
-
[lambda_http] Fallible response body panics during buffered conversion instead of propagating the body errorCó thể đã có người làm @IamPritamAcharya đã nhận 34 ngày trước. Đang mở
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 74/100
aws/aws-lambda-rust-runtime#1165 ·
Maintainer thường phản hồi trong vòng 11 ngày
-
SNS `Timestamp` doesn't round-trip: serialization drops `.000` subseconds, corrupting the signed string-to-signCó thể đã có người làm @DebadityaHait đã nhận 62 ngày trước. Đang mở
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 68/100
aws/aws-lambda-rust-runtime#1161 ·
Maintainer thường phản hồi trong vòng 11 ngày
-
Add Lambda-Runtime-Invocation-Id header support for cross-wiring protectionCó thể làm lại được @darklight3it đã nhận 74 ngày trước và không có pull request nào đang mở. Đang mở
aws/aws-lambda-rust-runtime#1155 · 1 reaction · 1 người được giao ·
Maintainer thường phản hồi trong vòng 11 ngày
-
IoTCoreCustomAuthorizerResponse create invalid payalodCó thể đã có người làm @IamPritamAcharya đã nhận 34 ngày trước. Đang mở
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 48/100
aws/aws-lambda-rust-runtime#1131 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 11 ngày
-
experimental lambda battery packĐang mở
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 30/100
aws/aws-lambda-rust-runtime#1128 · 1 bình luận · 1 reaction ·
Maintainer thường phản hồi trong vòng 11 ngày
Tất cả issue của aws/aws-lambda-rust-runtime
Issue tương tự
-
bug user-priority/P2
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
t8y2/dbx#11718 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
rescript-lang/rescript#8765 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
nautechsystems/nautilus_trader#5287 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
farion1231/cc-switch#8072 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Python 3.15 supportCó thể đã có người làm @amnesiaof đã nhận hôm nay. Đang mởL: python L: python:uv
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
dependabot/dependabot-core#16524 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày