Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### July 17, 2026
`2.12.0`
- Add `Lambda-Runtime-Invocation-Id` header support for cross-wiring protection. The RIC now echoes the invocation ID received from RAPID on `/next` back on `/response` and `/error`, enabling RAPID to detect and reject stale responses from timed-out invocations.

### May 13, 2026
`2.11.0`
- Update aws-lambda-java-serialization dependency to 1.4.1
Expand Down
2 changes: 1 addition & 1 deletion aws-lambda-java-runtime-interface-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-runtime-interface-client</artifactId>
<version>2.11.0</version>
<version>2.12.0</version>
<packaging>jar</packaging>

<name>AWS Lambda Java Runtime Interface Client</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,15 +315,15 @@ private static void startRuntimeLoop(LambdaRequestHandler lambdaRequestHandler,

try {
ByteArrayOutputStream payload = lambdaRequestHandler.call(request);
runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray());
runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray(), request.getInvocationId());
// clear interrupted flag in case if it was set by user's code
Thread.interrupted();
} catch (Throwable t) {
UserFault.filterStackTrace(t);
userFault = UserFault.makeUserFault(t);
shouldExit = exitLoopOnErrors && (t instanceof VirtualMachineError || t instanceof IOError || userFault.fatal);
LambdaError error = createLambdaErrorFromThrowableOrUserFault(t);
runtimeClient.reportInvocationError(request.getId(), error);
runtimeClient.reportInvocationError(request.getId(), error, request.getInvocationId());
} finally {
if (userFault != null) {
lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,17 @@ public interface LambdaRuntimeApiClient {
* Report invocation success
* @param requestId request id
* @param response byte array representing response
* @param invocationId invocation id for cross-wiring protection (may be null)
*/
void reportInvocationSuccess(String requestId, byte[] response) throws IOException;
void reportInvocationSuccess(String requestId, byte[] response, String invocationId) throws IOException;

/**
* Report invocation error
* @param requestId request id
* @param error error to report
* @param invocationId invocation id for cross-wiring protection (may be null)
*/
void reportInvocationError(String requestId, LambdaError error) throws IOException;
void reportInvocationError(String requestId, LambdaError error, String invocationId) throws IOException;

/**
* SnapStart endpoint to report that beforeCheckoint hooks were executed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public LambdaRuntimeApiClientImpl(String hostnameAndPort) {
@Override
public void reportInitError(LambdaError error) throws IOException {
String endpoint = this.baseUrl + "/2018-06-01/runtime/init/error";
reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE);
reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE, null);
}

@Override
Expand Down Expand Up @@ -123,14 +123,15 @@ public InvocationRequest nextInvocationWithExponentialBackoff(LambdaContextLogge
}

@Override
public void reportInvocationSuccess(String requestId, byte[] response) {
NativeClient.postInvocationResponse(requestId.getBytes(UTF_8), response);
public void reportInvocationSuccess(String requestId, byte[] response, String invocationId) {
byte[] invocationIdBytes = invocationId != null ? invocationId.getBytes(UTF_8) : null;
NativeClient.postInvocationResponse(requestId.getBytes(UTF_8), response, invocationIdBytes);
}

@Override
public void reportInvocationError(String requestId, LambdaError error) throws IOException {
public void reportInvocationError(String requestId, LambdaError error, String invocationId) throws IOException {
String endpoint = invocationEndpoint + requestId + "/error";
reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE);
reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE, invocationId);
}

@Override
Expand All @@ -145,13 +146,17 @@ public void restoreNext() throws IOException {
@Override
public void reportRestoreError(LambdaError error) throws IOException {
String endpoint = this.baseUrl + "/2018-06-01/runtime/restore/error";
reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE);
reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE, null);
}

void reportLambdaError(String endpoint, LambdaError error, int maxXrayHeaderSize) throws IOException {
void reportLambdaError(String endpoint, LambdaError error, int maxXrayHeaderSize, String invocationId) throws IOException {
Map<String, String> headers = new HashMap<>();
headers.put(ERROR_TYPE_HEADER, error.errorType.getRapidError());

if (invocationId != null) {
headers.put("Lambda-Runtime-Invocation-Id", invocationId);
}

if (error.xRayErrorCause != null) {
byte[] xRayErrorCauseJson = DtoSerializers.serialize(error.xRayErrorCause);
if (xRayErrorCauseJson != null && xRayErrorCauseJson.length < maxXrayHeaderSize) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ static void init(String awsLambdaRuntimeApi) {

static native InvocationRequest next();

static native void postInvocationResponse(byte[] requestId, byte[] response);
static native void postInvocationResponse(byte[] requestId, byte[] response, byte[] invocationId);

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ public class InvocationRequest {
*/
private String tenantId;

/**
* The invocation ID for cross-wiring protection.
*/
private String invocationId;

private byte[] content;

public String getId() {
Expand Down Expand Up @@ -107,6 +112,14 @@ public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}

public String getInvocationId() {
return invocationId;
}

public void setInvocationId(String invocationId) {
this.invocationId = invocationId;
}

public byte[] getContent() {
return content;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ static jfieldID clientContextField;
static jfieldID cognitoIdentityField;
static jfieldID xrayTraceIdField;
static jfieldID tenantIdField;
static jfieldID invocationIdField;


jint JNI_OnLoad(JavaVM* vm, void* reserved) {
Expand All @@ -43,6 +44,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
clientContextField = env->GetFieldID(invocationRequestClass , "clientContext", "Ljava/lang/String;");
cognitoIdentityField = env->GetFieldID(invocationRequestClass , "cognitoIdentity", "Ljava/lang/String;");
tenantIdField = env->GetFieldID(invocationRequestClass, "tenantId", "Ljava/lang/String;");
invocationIdField = env->GetFieldID(invocationRequestClass, "invocationId", "Ljava/lang/String;");

return JNI_VERSION;
}
Expand Down Expand Up @@ -112,6 +114,10 @@ JNIEXPORT jobject JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_
CHECK_EXCEPTION(env, env->SetObjectField(invocationRequest, tenantIdField, env->NewStringUTF(response.tenant_id.c_str())));
}

if(response.invocation_id != ""){
CHECK_EXCEPTION(env, env->SetObjectField(invocationRequest, invocationIdField, env->NewStringUTF(response.invocation_id.c_str())));
}

bytes = reinterpret_cast<const jbyte*>(response.payload.c_str());
CHECK_EXCEPTION(env, jArray = env->NewByteArray(response.payload.length()));
CHECK_EXCEPTION(env, env->SetByteArrayRegion(jArray, 0, response.payload.length(), bytes));
Expand All @@ -124,7 +130,7 @@ JNIEXPORT jobject JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_
}

JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient_postInvocationResponse
(JNIEnv *env, jobject thisObject, jbyteArray jrequestId, jbyteArray jresponseArray) {
(JNIEnv *env, jobject thisObject, jbyteArray jrequestId, jbyteArray jresponseArray, jbyteArray jinvocationId) {
std::string payload = toNativeString(env, jresponseArray);
if ((env)->ExceptionOccurred()){
return;
Expand All @@ -134,8 +140,16 @@ JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_run
return;
}

std::string invocationId;
if (jinvocationId != nullptr) {
invocationId = toNativeString(env, jinvocationId);
if ((env)->ExceptionOccurred()){
return;
}
}

auto response = aws::lambda_runtime::invocation_response::success(payload, "application/json");
auto outcome = CLIENT->post_success(requestId, response);
auto outcome = CLIENT->post_success(requestId, response, invocationId);
if (!outcome.is_success()) {
std::string errorMessage("Failed to post invocation response.");
throwLambdaRuntimeClientException(env, errorMessage, outcome.get_failure());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ JNIEXPORT jobject JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_
(JNIEnv *, jobject);

JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient_postInvocationResponse
(JNIEnv *, jobject, jbyteArray, jbyteArray);
(JNIEnv *, jobject, jbyteArray, jbyteArray, jbyteArray);

#ifdef __cplusplus
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ struct invocation_request {
*/
std::string tenant_id;

/**
* Invocation ID for cross-wiring protection.
*/
std::string invocation_id;

/**
* The number of milliseconds left before lambda terminates the current execution.
*/
Expand Down Expand Up @@ -154,20 +159,21 @@ class runtime {
/**
* Tells lambda that the function has succeeded.
*/
post_outcome post_success(std::string const& request_id, invocation_response const& handler_response);
post_outcome post_success(std::string const& request_id, invocation_response const& handler_response, std::string const& invocation_id = "");

/**
* Tells lambda that the function has failed.
*/
post_outcome post_failure(std::string const& request_id, invocation_response const& handler_response);
post_outcome post_failure(std::string const& request_id, invocation_response const& handler_response, std::string const& invocation_id = "");

private:
void set_curl_next_options();
void set_curl_post_result_options();
post_outcome do_post(
std::string const& url,
std::string const& request_id,
invocation_response const& handler_response);
invocation_response const& handler_response,
std::string const& invocation_id = "");

private:
std::string const m_user_agent_header;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ static constexpr auto COGNITO_IDENTITY_HEADER = "lambda-runtime-cognito-identity
static constexpr auto DEADLINE_MS_HEADER = "lambda-runtime-deadline-ms";
static constexpr auto FUNCTION_ARN_HEADER = "lambda-runtime-invoked-function-arn";
static constexpr auto TENANT_ID_HEADER = "lambda-runtime-aws-tenant-id";
static constexpr auto INVOCATION_ID_HEADER = "lambda-runtime-invocation-id";
thread_local static CURL* m_curl_handle = curl_easy_init();

enum Endpoints {
Expand Down Expand Up @@ -306,25 +307,30 @@ runtime::next_outcome runtime::get_next()
if (resp.has_header(TENANT_ID_HEADER)) {
req.tenant_id = resp.get_header(TENANT_ID_HEADER);
}

if (resp.has_header(INVOCATION_ID_HEADER)) {
req.invocation_id = resp.get_header(INVOCATION_ID_HEADER);
}
return next_outcome(req);
}

runtime::post_outcome runtime::post_success(std::string const& request_id, invocation_response const& handler_response)
runtime::post_outcome runtime::post_success(std::string const& request_id, invocation_response const& handler_response, std::string const& invocation_id)
{
std::string const url = m_endpoints[Endpoints::RESULT] + request_id + "/response";
return do_post(url, request_id, handler_response);
return do_post(url, request_id, handler_response, invocation_id);
}

runtime::post_outcome runtime::post_failure(std::string const& request_id, invocation_response const& handler_response)
runtime::post_outcome runtime::post_failure(std::string const& request_id, invocation_response const& handler_response, std::string const& invocation_id)
{
std::string const url = m_endpoints[Endpoints::RESULT] + request_id + "/error";
return do_post(url, request_id, handler_response);
return do_post(url, request_id, handler_response, invocation_id);
}

runtime::post_outcome runtime::do_post(
std::string const& url,
std::string const& request_id,
invocation_response const& handler_response)
invocation_response const& handler_response,
std::string const& invocation_id)
{
set_curl_post_result_options();
curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_URL, url.c_str());
Expand All @@ -341,6 +347,9 @@ runtime::post_outcome runtime::do_post(
headers = curl_slist_append(headers, "Expect:");
headers = curl_slist_append(headers, "transfer-encoding:");
headers = curl_slist_append(headers, m_user_agent_header.c_str());
if (!invocation_id.empty()) {
headers = curl_slist_append(headers, ("lambda-runtime-invocation-id: " + invocation_id).c_str());
}
auto const& payload = handler_response.get_payload();
logging::log_debug(
LOG_TAG, "calculating content length... %s", ("content-length: " + std::to_string(payload.length())).c_str());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ void testConcurrentRunWithPlatformThreads() throws Throwable {
AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient);

// Success Reports Must Equal number of tasks that ran successfully.
verify(runtimeClient, times(7)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any());
verify(runtimeClient, times(7)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any(), any());
// Hashmap keys should equal the number of threads (runtime loops).
assertEquals(4, SampleHandler.hashMap.size());
// Hashmap total count should equal all tasks that ran * number of iterations per task
Expand Down Expand Up @@ -280,11 +280,11 @@ void testConcurrentRunWithPlatformThreadsWithFailures() throws Throwable {
verify(lambdaLogger, times(6)).log(anyString(), eq(LogLevel.ERROR));

// Failed invokes should be reported.
verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any());
verify(runtimeClient).reportInvocationError(eq(UserFaultID), any());
verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any(), any());
verify(runtimeClient).reportInvocationError(eq(UserFaultID), any(), any());

// Success Reports Must Equal number of tasks that ran successfully.
verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any());
verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any(), any());

// Hashmap keys should equal the minumum between(number of threads (runtime loops) AND number of tasks that ran successfully).
assertEquals(2, SampleHandler.hashMap.size());
Expand Down Expand Up @@ -326,12 +326,12 @@ void testConcurrentModeLoopDoesNotExitExceptForLambdaRuntimeClientMaxRetriesExce
verify(lambdaLogger, times(4)).log(anyString(), eq(LogLevel.ERROR));

// Failed invokes should be reported.
verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any());
verify(runtimeClient).reportInvocationError(eq(UserFaultID), any());
verify(runtimeClient).reportInvocationError(eq(IOErrorID), any());
verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any(), any());
verify(runtimeClient).reportInvocationError(eq(UserFaultID), any(), any());
verify(runtimeClient).reportInvocationError(eq(IOErrorID), any(), any());

// Success Reports Must Equal number of tasks that ran successfully.
verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any());
verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any(), any());

// Hashmap keys should equal the minumum between(number of threads (runtime loops) AND number of tasks that ran successfully).
assertEquals(1, SampleHandler.hashMap.size());
Expand Down Expand Up @@ -516,12 +516,12 @@ void testSequentialWithFatalUserFaultErrorStopsLoop() throws Throwable {
verify(lambdaLogger, times(2)).log(anyString(), eq(LogLevel.ERROR));

// Failed invokes should be reported.
verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any());
verify(runtimeClient).reportInvocationError(eq(UserFaultID), any());
verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any(), any());
verify(runtimeClient).reportInvocationError(eq(UserFaultID), any(), any());

// Success Reports Must Equal number of tasks that ran successfully. And only 2 Error reports for failImmediatelyRequest and userFaultRequest.
verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any());
verify(runtimeClient, times(2)).reportInvocationError(any(), any());
verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any(), any());
verify(runtimeClient, times(2)).reportInvocationError(any(), any(), any());

// Hashmap keys should equal one as it is not multithreaded.
assertEquals(1, SampleHandler.hashMap.size());
Expand Down Expand Up @@ -562,12 +562,12 @@ void testSequentialWithVirtualMachineErrorStopsLoop() throws Throwable {
verify(lambdaLogger, times(2)).log(anyString(), eq(LogLevel.ERROR));

// Failed invokes should be reported.
verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any());
verify(runtimeClient).reportInvocationError(eq(IOErrorID), any());
verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any(), any());
verify(runtimeClient).reportInvocationError(eq(IOErrorID), any(), any());

// Success Reports Must Equal number of tasks that ran successfully. And only 2 Error reports for failImmediatelyRequest and virtualMachineErrorRequest.
verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any());
verify(runtimeClient, times(2)).reportInvocationError(any(), any());
verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any(), any());
verify(runtimeClient, times(2)).reportInvocationError(any(), any(), any());

// Hashmap keys should equal one as it is not multithreaded.
assertEquals(1, SampleHandler.hashMap.size());
Expand Down
Loading
Loading