From 199c9621eaa214720f099e0c9cb9b5496d8b813e Mon Sep 17 00:00:00 2001 From: Maxime David Date: Mon, 24 Mar 2025 14:56:05 +0000 Subject: [PATCH 01/26] misc: merge from public (#80) --- .github/workflows/runtime-interface-client_pr.yml | 8 -------- .github/workflows/samples.yml | 2 +- README.md | 4 ++-- aws-lambda-java-events/README.md | 2 +- aws-lambda-java-events/RELEASE.CHANGELOG.md | 4 ---- aws-lambda-java-events/pom.xml | 2 +- aws-lambda-java-runtime-interface-client/README.md | 4 ++-- .../RELEASE.CHANGELOG.md | 4 ---- aws-lambda-java-runtime-interface-client/pom.xml | 2 +- .../runtime/api/client/PojoSerializerLoaderTest.java | 2 -- .../test/integration/test-handler/pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 2 +- .../fastJson/HelloWorldFunction/pom.xml | 2 +- .../custom-serialization/gson/HelloWorldFunction/pom.xml | 2 +- .../custom-serialization/moshi/HelloWorldFunction/pom.xml | 2 +- .../request-stream-handler/HelloWorldFunction/pom.xml | 2 +- samples/kinesis-firehose-event-handler/pom.xml | 2 +- 17 files changed, 15 insertions(+), 33 deletions(-) diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index 35c6ca06b..cbc5b5ab1 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -58,14 +58,6 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} - - - name: Build and install core dependency locally - working-directory: ./aws-lambda-java-core - run: mvn clean install - - - name: Build and install serialization dependency locally - working-directory: ./aws-lambda-java-serialization - run: mvn clean install -DskipTests - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 2b5e7833f..6d63f423e 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -42,7 +42,7 @@ jobs: custom-serialization: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 # Set up both Java 8 and 21 - name: Set up Java 8 and 21 uses: actions/setup-java@v4 diff --git a/README.md b/README.md index b5153a87f..7f4970949 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ public class SqsHandler implements RequestHandler { com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 ``` @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.6.0 ``` diff --git a/aws-lambda-java-events/README.md b/aws-lambda-java-events/README.md index 43c25d76a..87c61f345 100644 --- a/aws-lambda-java-events/README.md +++ b/aws-lambda-java-events/README.md @@ -74,7 +74,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 ... diff --git a/aws-lambda-java-events/RELEASE.CHANGELOG.md b/aws-lambda-java-events/RELEASE.CHANGELOG.md index a4bcd10a0..6c1769751 100644 --- a/aws-lambda-java-events/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-events/RELEASE.CHANGELOG.md @@ -1,7 +1,3 @@ -### June 17, 2025 -`3.16.0`: -- Add Schema metadata related attributes in KafkaEvent ([#548](https://github.com/aws/aws-lambda-java-libs/pull/548)) - ### January 31, 2025 `3.15.0`: - Fix `CognitoUserPoolPreTokenGenerationEventV2` model ([#519](https://github.com/aws/aws-lambda-java-libs/pull/519)) diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index 925273e9b..f1364e7ab 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events - 3.16.1 + 3.15.0 jar AWS Lambda Java Events Library diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 67a5972d6..368ab710a 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.6.0 @@ -203,7 +203,7 @@ platform-specific JAR by setting the ``. com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.6.0 linux-x86_64 ``` diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index ac073ae85..6a781b270 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,7 +1,3 @@ -### May 21, 2025 -`2.7.0` -- Adding support for multi tenancy ([#540](https://github.com/aws/aws-lambda-java-libs/pull/540)) - ### August 7, 2024 `2.6.0` - Runtime API client improvements: use Lambda-Runtime-Function-Error-Type for reporting errors in format "Runtime." diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index c854fabcd..fde515fda 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.4 + 2.6.0 jar AWS Lambda Java Runtime Interface Client diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java index c2c887973..7c6e9dcb4 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java @@ -7,7 +7,6 @@ import com.amazonaws.services.lambda.runtime.CustomPojoSerializer; import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -32,7 +31,6 @@ class PojoSerializerLoaderTest { @Mock private CustomPojoSerializer mockSerializer; - @AfterEach @BeforeEach void setUp() throws Exception { resetStaticFields(); diff --git a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml index 46d191a74..5304d44b9 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml +++ b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml @@ -15,7 +15,7 @@ com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.4 + 2.6.0 diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index da07133c1..9e3419c69 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -45,7 +45,7 @@ com.amazonaws aws-lambda-java-events - 3.16.1 + 3.15.0 org.junit.jupiter diff --git a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml index 7d3c44246..7325c72a0 100644 --- a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 diff --git a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml index fd4271824..dd3b8e9c5 100644 --- a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 com.google.code.gson diff --git a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml index 2773ef1f1..f23214976 100644 --- a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 diff --git a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml index f6ca21bf7..68e7e81e9 100644 --- a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 com.google.code.gson diff --git a/samples/kinesis-firehose-event-handler/pom.xml b/samples/kinesis-firehose-event-handler/pom.xml index 56c959244..3d03205d3 100644 --- a/samples/kinesis-firehose-event-handler/pom.xml +++ b/samples/kinesis-firehose-event-handler/pom.xml @@ -46,7 +46,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 From a9c7fd1eea83423ed8d5887d31c588b433146be2 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Wed, 7 May 2025 11:00:21 +0100 Subject: [PATCH 02/26] Multi-concurrency mode for the Runtime Interface Client and thread_local C++ Curl Client (#82) Multi Concurrent RIC and Enable Curl Multi Concurrent Requests --- .github/dependabot.yml | 4 + .../lambda/runtime/api/client/AWSLambda.java | 69 +++-- .../api/client/EventHandlerLoader.java | 4 +- .../api/client/PojoSerializerLoader.java | 2 +- .../ReservedRuntimeEnvironmentVariables.java | 13 + .../api/client/util/ConcurrencyConfig.java | 55 ++++ .../include/aws/lambda-runtime/runtime.h | 1 - .../deps/aws-lambda-cpp-0.2.7/src/runtime.cpp | 87 ++++--- .../runtime/api/client/AWSLambdaTest.java | 238 ++++++++++++++++++ .../client/util/ConcurrencyConfigTest.java | 94 +++++++ 10 files changed, 502 insertions(+), 65 deletions(-) create mode 100644 aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java create mode 100644 aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java create mode 100644 aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3722537ae..34d287eec 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,5 +1,9 @@ version: 2 updates: + - package-ecosystem: "maven" + directory: "/aws-lambda-java-runtime-interface" + schedule: + interval: "weekly" - package-ecosystem: "github-actions" directory: "/" diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java index 2eeb14e3d..fdd090077 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java @@ -19,6 +19,7 @@ import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters.LambdaErrorConverter; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters.XRayErrorCauseConverter; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; +import com.amazonaws.services.lambda.runtime.api.client.util.ConcurrencyConfig; import com.amazonaws.services.lambda.runtime.api.client.util.LambdaOutputStream; import com.amazonaws.services.lambda.runtime.api.client.util.UnsafeUtil; import com.amazonaws.services.lambda.runtime.logging.LogFormat; @@ -35,6 +36,8 @@ import java.net.URLClassLoader; import java.security.Security; import java.util.Properties; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; /** * The entrypoint of this class is {@link AWSLambda#startRuntime}. It performs two main tasks: @@ -49,8 +52,8 @@ */ public class AWSLambda { - protected static URLClassLoader customerClassLoader; - + private static URLClassLoader customerClassLoader; + private static final String TRUST_STORE_PROPERTY = "javax.net.ssl.trustStore"; private static final String JAVA_SECURITY_PROPERTIES = "java.security.properties"; @@ -68,9 +71,7 @@ public class AWSLambda { private static final String INIT_TYPE_SNAP_START = "snap-start"; private static final String AWS_LAMBDA_INITIALIZATION_TYPE = System.getenv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_INITIALIZATION_TYPE); - - private static LambdaRuntimeApiClient runtimeClient; - + static { // Override the disabledAlgorithms setting to match configuration for openjdk8-u181. // This is to keep DES ciphers around while we deploying security updates. @@ -137,15 +138,13 @@ private static LambdaRequestHandler findRequestHandler(final String handlerStrin return requestHandler; } - private static LambdaRequestHandler getLambdaRequestHandlerObject(String handler, LambdaContextLogger lambdaLogger) throws ClassNotFoundException, IOException { + private static LambdaRequestHandler getLambdaRequestHandlerObject(String handler, LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient) throws ClassNotFoundException, IOException { UnsafeUtil.disableIllegalAccessWarning(); System.setOut(new PrintStream(new LambdaOutputStream(System.out), false, "UTF-8")); System.setErr(new PrintStream(new LambdaOutputStream(System.err), false, "UTF-8")); setupRuntimeLogger(lambdaLogger); - runtimeClient = new LambdaRuntimeApiClientImpl(LambdaEnvironment.RUNTIME_API); - String taskRoot = System.getProperty("user.dir"); String libRoot = "/opt/java"; // Make system classloader the customer classloader's parent to ensure any aws-lambda-java-core classes @@ -167,13 +166,13 @@ private static LambdaRequestHandler getLambdaRequestHandlerObject(String handler } if (INIT_TYPE_SNAP_START.equals(AWS_LAMBDA_INITIALIZATION_TYPE)) { - onInitComplete(lambdaLogger); + onInitComplete(lambdaLogger, runtimeClient); } return requestHandler; } - public static void setupRuntimeLogger(LambdaLogger lambdaLogger) + private static void setupRuntimeLogger(LambdaLogger lambdaLogger) throws ClassNotFoundException { ReflectUtil.setStaticField( Class.forName("com.amazonaws.services.lambda.runtime.LambdaRuntime"), @@ -213,10 +212,11 @@ private static LogSink createLogSink() { } public static void main(String[] args) throws Throwable { - try (LambdaContextLogger logger = initLogger()) { - LambdaRequestHandler lambdaRequestHandler = getLambdaRequestHandlerObject(args[0], logger); - startRuntimeLoop(lambdaRequestHandler, logger); - + try (LambdaContextLogger lambdaLogger = initLogger()) { + LambdaRuntimeApiClient runtimeClient = new LambdaRuntimeApiClientImpl(LambdaEnvironment.RUNTIME_API); + LambdaRequestHandler lambdaRequestHandler = getLambdaRequestHandlerObject(args[0], lambdaLogger, runtimeClient); + ConcurrencyConfig concurrencyConfig = new ConcurrencyConfig(lambdaLogger); + startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); } catch (IOException | ClassNotFoundException t) { throw new Error(t); } @@ -232,7 +232,38 @@ private static LambdaContextLogger initLogger() { return logger; } - private static void startRuntimeLoop(LambdaRequestHandler requestHandler, LambdaContextLogger lambdaLogger) throws Throwable { + private static void startRuntimeLoopWithExecutor(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, ExecutorService executorService, LambdaRuntimeApiClient runtimeClient) { + executorService.submit(() -> { + try { + startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient); + } catch (Exception e) { + lambdaLogger.log(String.format("Runtime Loop on Thread ID: %s Failed.\n%s", Thread.currentThread().getName(), UserFault.trace(e)), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + } + }); + } + + protected static void startRuntimeLoops(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, ConcurrencyConfig concurrencyConfig, LambdaRuntimeApiClient runtimeClient) throws Exception { + if (concurrencyConfig.isMultiConcurrent()) { + lambdaLogger.log(concurrencyConfig.getConcurrencyConfigMessage(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.INFO : LogLevel.UNDEFINED); + ExecutorService platformThreadExecutor = Executors.newFixedThreadPool(concurrencyConfig.getNumberOfPlatformThreads()); + try { + for (int i = 0; i < concurrencyConfig.getNumberOfPlatformThreads(); i++) { + startRuntimeLoopWithExecutor(lambdaRequestHandler, lambdaLogger, platformThreadExecutor, runtimeClient); + } + } finally { + platformThreadExecutor.shutdown(); + while (true) { + if (platformThreadExecutor.isTerminated()) { + break; + } + } + } + } else { + startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient); + } + } + + private static void startRuntimeLoop(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient) throws Exception { boolean shouldExit = false; while (!shouldExit) { UserFault userFault = null; @@ -245,7 +276,7 @@ private static void startRuntimeLoop(LambdaRequestHandler requestHandler, Lambda ByteArrayOutputStream payload; try { - payload = requestHandler.call(request); + payload = lambdaRequestHandler.call(request); runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray()); // clear interrupted flag in case if it was set by user's code Thread.interrupted(); @@ -275,7 +306,7 @@ private static void startRuntimeLoop(LambdaRequestHandler requestHandler, Lambda } } - static void onInitComplete(final LambdaContextLogger lambdaLogger) throws IOException { + private static void onInitComplete(final LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient) throws IOException { try { Core.getGlobalContext().beforeCheckpoint(null); runtimeClient.restoreNext(); @@ -303,4 +334,8 @@ private static void logExceptionCloudWatch(LambdaContextLogger lambdaLogger, Exc UserFault userFault = UserFault.makeUserFault(exc, true); lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); } + + protected static URLClassLoader getCustomerClassLoader() { + return customerClassLoader; + } } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java index 2876499ec..8fab27fed 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java @@ -116,7 +116,7 @@ private static PojoSerializer getSerializer(Platform platform, Type type if (type instanceof Class) { Class clazz = ((Class) type); if (LambdaEventSerializers.isLambdaSupportedEvent(clazz.getName())) { - return LambdaEventSerializers.serializerFor(clazz, AWSLambda.customerClassLoader); + return LambdaEventSerializers.serializerFor(clazz, AWSLambda.getCustomerClassLoader()); } } // else platform dependent (Android uses GSON but all other platforms use Jackson) @@ -533,7 +533,7 @@ private static LambdaRequestHandler wrapRequestStreamHandler(final RequestStream private void safeAddRequestIdToLog4j(String log4jContextClassName, InvocationRequest request, Class contextMapValueClass) { try { - Class log4jContextClass = ReflectUtil.loadClass(AWSLambda.customerClassLoader, log4jContextClassName); + Class log4jContextClass = ReflectUtil.loadClass(AWSLambda.getCustomerClassLoader(), log4jContextClassName); log4jContextPutMethod = ReflectUtil.loadStaticV2(log4jContextClass, "put", false, String.class, contextMapValueClass); log4jContextPutMethod.call("AWSRequestId", request.getId()); } catch (Exception e) { diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoader.java index daea5911f..da37f7ca7 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoader.java @@ -28,7 +28,7 @@ private static CustomPojoSerializer loadSerializer() return customPojoSerializer; } - ServiceLoader loader = ServiceLoader.load(CustomPojoSerializer.class, AWSLambda.customerClassLoader); + ServiceLoader loader = ServiceLoader.load(CustomPojoSerializer.class, AWSLambda.getCustomerClassLoader()); Iterator serializers = loader.iterator(); if (!serializers.hasNext()) { diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java index 7500a4943..2d32d4048 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java @@ -106,4 +106,17 @@ public interface ReservedRuntimeEnvironmentVariables { * The environment's time zone (UTC). The execution environment uses NTP to synchronize the system clock. */ String TZ = "TZ"; + + /** + * Boolean determining whether the RIC should run multi-concurrent runtime loops. Default value is "false". + * In case it is set to "true", AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY can be used to set the required number of concurrent runtime loops. + */ + String AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC = "AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC"; + + /* + * Used to set the required number of concurrent runtime loops, + * Only used if AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC is set to "true", + * If AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY is not set, the default number of concurrent runtime loops is the number of cores. + */ + String AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY = "AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY"; } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java new file mode 100644 index 000000000..db1f6c218 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java @@ -0,0 +1,55 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package com.amazonaws.services.lambda.runtime.api.client.util; + +import com.amazonaws.services.lambda.runtime.api.client.ReservedRuntimeEnvironmentVariables; +import com.amazonaws.services.lambda.runtime.api.client.UserFault; +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; + +public class ConcurrencyConfig { + private static final int AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT = 1000; + private final int numberOfPlatformThreads; + private final boolean isConcurrencyEnabled; + + public ConcurrencyConfig(LambdaContextLogger logger) { + this(logger, new EnvReader()); + } + + public ConcurrencyConfig(LambdaContextLogger logger, EnvReader envReader) { + this.isConcurrencyEnabled = Boolean.parseBoolean(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)); + int platformThreads = this.isConcurrencyEnabled ? Runtime.getRuntime().availableProcessors() : 0; + + if (this.isConcurrencyEnabled) { + try { + int readNumOfPlatformThreads = Integer.parseUnsignedInt(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)); + if (readNumOfPlatformThreads > 0 && readNumOfPlatformThreads <= AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT) { + platformThreads = readNumOfPlatformThreads; + } else { + throw new IllegalArgumentException(); + } + } catch (Exception e) { + String message = String.format("User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to number of cores as number of platform threads.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY, AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT, UserFault.trace(e)); + logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED); + } + } + + this.numberOfPlatformThreads = platformThreads; + } + + public String getConcurrencyConfigMessage() { + return String.format("Starting %d concurrent function handler threads.", this.numberOfPlatformThreads); + } + + public boolean isMultiConcurrent() { + return this.isConcurrencyEnabled && this.numberOfPlatformThreads >= 2; + } + + public int getNumberOfPlatformThreads() { + return numberOfPlatformThreads; + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h index d7db5f183..c4868c1ba 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h @@ -172,7 +172,6 @@ class runtime { private: std::string const m_user_agent_header; std::array const m_endpoints; - CURL* const m_curl_handle; }; inline std::chrono::milliseconds invocation_request::get_time_remaining() const diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp index eeaf0e7b9..e713da985 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp @@ -40,7 +40,7 @@ static constexpr auto CLIENT_CONTEXT_HEADER = "lambda-runtime-client-context"; 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"; +thread_local static CURL* m_curl_handle = curl_easy_init(); enum Endpoints { INIT, @@ -163,63 +163,62 @@ runtime::runtime(std::string const& endpoint) : runtime(endpoint, "AWS_Lambda_Cp runtime::runtime(std::string const& endpoint, std::string const& user_agent) : m_user_agent_header("User-Agent: " + user_agent), m_endpoints{{endpoint + "/2018-06-01/runtime/init/error", endpoint + "/2018-06-01/runtime/invocation/next", - endpoint + "/2018-06-01/runtime/invocation/"}}, - m_curl_handle(curl_easy_init()) + endpoint + "/2018-06-01/runtime/invocation/"}} { - if (!m_curl_handle) { + if (!lambda_runtime::m_curl_handle) { logging::log_error(LOG_TAG, "Failed to acquire curl easy handle for next."); } } runtime::~runtime() { - curl_easy_cleanup(m_curl_handle); + curl_easy_cleanup(lambda_runtime::m_curl_handle); } void runtime::set_curl_next_options() { // lambda freezes the container when no further tasks are available. The freezing period could be longer than the // request timeout, which causes the following get_next request to fail with a timeout error. - curl_easy_reset(m_curl_handle); - curl_easy_setopt(m_curl_handle, CURLOPT_TIMEOUT, 0L); - curl_easy_setopt(m_curl_handle, CURLOPT_CONNECTTIMEOUT, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_NOSIGNAL, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_TCP_NODELAY, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_reset(lambda_runtime::m_curl_handle); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TIMEOUT, 0L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_CONNECTTIMEOUT, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TCP_NODELAY, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); - curl_easy_setopt(m_curl_handle, CURLOPT_HTTPGET, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_URL, m_endpoints[Endpoints::NEXT].c_str()); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_URL, m_endpoints[Endpoints::NEXT].c_str()); - curl_easy_setopt(m_curl_handle, CURLOPT_WRITEFUNCTION, write_data); - curl_easy_setopt(m_curl_handle, CURLOPT_HEADERFUNCTION, write_header); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEFUNCTION, write_data); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERFUNCTION, write_header); - curl_easy_setopt(m_curl_handle, CURLOPT_PROXY, ""); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_PROXY, ""); #ifndef NDEBUG - curl_easy_setopt(m_curl_handle, CURLOPT_VERBOSE, 1); - curl_easy_setopt(m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_VERBOSE, 1); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback); #endif } void runtime::set_curl_post_result_options() { - curl_easy_reset(m_curl_handle); - curl_easy_setopt(m_curl_handle, CURLOPT_TIMEOUT, 0L); - curl_easy_setopt(m_curl_handle, CURLOPT_CONNECTTIMEOUT, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_NOSIGNAL, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_TCP_NODELAY, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_reset(lambda_runtime::m_curl_handle); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TIMEOUT, 0L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_CONNECTTIMEOUT, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TCP_NODELAY, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); - curl_easy_setopt(m_curl_handle, CURLOPT_POST, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_READFUNCTION, read_data); - curl_easy_setopt(m_curl_handle, CURLOPT_WRITEFUNCTION, write_data); - curl_easy_setopt(m_curl_handle, CURLOPT_HEADERFUNCTION, write_header); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_POST, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_READFUNCTION, read_data); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEFUNCTION, write_data); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERFUNCTION, write_header); - curl_easy_setopt(m_curl_handle, CURLOPT_PROXY, ""); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_PROXY, ""); #ifndef NDEBUG - curl_easy_setopt(m_curl_handle, CURLOPT_VERBOSE, 1); - curl_easy_setopt(m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_VERBOSE, 1); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback); #endif } @@ -227,15 +226,15 @@ runtime::next_outcome runtime::get_next() { http::response resp; set_curl_next_options(); - curl_easy_setopt(m_curl_handle, CURLOPT_WRITEDATA, &resp); - curl_easy_setopt(m_curl_handle, CURLOPT_HEADERDATA, &resp); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEDATA, &resp); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERDATA, &resp); curl_slist* headers = nullptr; headers = curl_slist_append(headers, m_user_agent_header.c_str()); - curl_easy_setopt(m_curl_handle, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTPHEADER, headers); logging::log_debug(LOG_TAG, "Making request to %s", m_endpoints[Endpoints::NEXT].c_str()); - CURLcode curl_code = curl_easy_perform(m_curl_handle); + CURLcode curl_code = curl_easy_perform(lambda_runtime::m_curl_handle); logging::log_debug(LOG_TAG, "Completed request to %s", m_endpoints[Endpoints::NEXT].c_str()); curl_slist_free_all(headers); @@ -247,13 +246,13 @@ runtime::next_outcome runtime::get_next() { long resp_code; - curl_easy_getinfo(m_curl_handle, CURLINFO_RESPONSE_CODE, &resp_code); + curl_easy_getinfo(lambda_runtime::m_curl_handle, CURLINFO_RESPONSE_CODE, &resp_code); resp.set_response_code(static_cast(resp_code)); } { char* content_type = nullptr; - curl_easy_getinfo(m_curl_handle, CURLINFO_CONTENT_TYPE, &content_type); + curl_easy_getinfo(lambda_runtime::m_curl_handle, CURLINFO_CONTENT_TYPE, &content_type); resp.set_content_type(content_type); } @@ -327,7 +326,7 @@ runtime::post_outcome runtime::do_post( invocation_response const& handler_response) { set_curl_post_result_options(); - curl_easy_setopt(m_curl_handle, CURLOPT_URL, url.c_str()); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_URL, url.c_str()); logging::log_info(LOG_TAG, "Making request to %s", url.c_str()); curl_slist* headers = nullptr; @@ -348,11 +347,11 @@ runtime::post_outcome runtime::do_post( std::pair ctx{payload, 0}; aws::http::response resp; - curl_easy_setopt(m_curl_handle, CURLOPT_WRITEDATA, &resp); - curl_easy_setopt(m_curl_handle, CURLOPT_HEADERDATA, &resp); - curl_easy_setopt(m_curl_handle, CURLOPT_READDATA, &ctx); - curl_easy_setopt(m_curl_handle, CURLOPT_HTTPHEADER, headers); - CURLcode curl_code = curl_easy_perform(m_curl_handle); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEDATA, &resp); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERDATA, &resp); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_READDATA, &ctx); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTPHEADER, headers); + CURLcode curl_code = curl_easy_perform(lambda_runtime::m_curl_handle); curl_slist_free_all(headers); if (curl_code != CURLE_OK) { @@ -366,7 +365,7 @@ runtime::post_outcome runtime::do_post( } long http_response_code; - curl_easy_getinfo(m_curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code); + curl_easy_getinfo(lambda_runtime::m_curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code); if (!is_success(aws::http::response_code(http_response_code))) { logging::log_error( diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java new file mode 100644 index 000000000..4412214e9 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java @@ -0,0 +1,238 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package com.amazonaws.services.lambda.runtime.api.client; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +import java.io.ByteArrayOutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClientImpl; +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; +import com.amazonaws.services.lambda.runtime.api.client.util.ConcurrencyConfig; +import com.amazonaws.services.lambda.runtime.api.client.util.EnvReader; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +class AWSLambdaTest { + + private static class SampleHandler implements RequestHandler, String> { + public static final String ADD_ENTRY_TO_MAP_ID_OP_MODE = "ADD_ENTRY_TO_MAP_ID"; + public static final String FAIL_IMMEDIATELY_OP_MODE = "FAIL_IMMEDIATELY"; + + public static final int nOfIterations = 10; + public static final int perIterationDelayMS = 10; + public static Map hashMap = new ConcurrentHashMap(); + public static AtomicInteger globalCounter = new AtomicInteger(); + + public static void resetStaticFields() { + hashMap.clear(); + globalCounter = new AtomicInteger(); + } + + private static void addEntryToMapImplementation(String name) { + int i = 0; + while (i++ < nOfIterations) { + hashMap.put(name, hashMap.getOrDefault(name, 0) + 1); + globalCounter.incrementAndGet(); + try { + Thread.sleep(perIterationDelayMS); + } catch (InterruptedException e) { + } + } + } + + @Override + public String handleRequest(Map event, Context context) { + // Thread.currentThread().getId() instead of Thread.currentThread().getName() when upgrading JAVA + String name = "Thread " + Thread.currentThread().getName(); + String opMode = event.get("id"); + + switch (opMode) { + case ADD_ENTRY_TO_MAP_ID_OP_MODE: + addEntryToMapImplementation(name); + break; + case FAIL_IMMEDIATELY_OP_MODE: + String[] sArr = {}; + return sArr[1]; + default: + break; + } + + return name; + } + } + + @Mock + private LambdaRuntimeApiClientImpl runtimeClient; + + @Mock + private LambdaContextLogger lambdaLogger; + + @Mock + private EnvReader envReader; + + @Mock + private ConcurrencyConfig concurrencyConfig; + + private LambdaRequestHandler lambdaRequestHandler = new LambdaRequestHandler() { + private SampleHandler sHandler = new SampleHandler(); + + @Override + public ByteArrayOutputStream call(InvocationRequest request) throws Error, Exception { + HashMap eventMap = new HashMap(); + eventMap.put("id", request.getId()); + sHandler.handleRequest(eventMap, null); + return new ByteArrayOutputStream(); + } + }; + + private static InvocationRequest getFakeInvocationRequest(String id) { + InvocationRequest request = new InvocationRequest(); + request.setId(id); + request.setDeadlineTimeInMs(Long.MAX_VALUE); + request.setContent("".getBytes()); + return request; + } + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + SampleHandler.resetStaticFields(); + } + + /* + * com.amazonaws.services.lambda.runtime.api.client.util.SampleHandler contains static fields. These fields are expected to be shared if initialization is behaving as expected. + * After execution of the Runtime loops, we should see that the SampleHandler.globalCounter has been acted on by all the threads. + * The concurrent hashmap in SampleHandler.hashMap should also have all the correct count of Threads that ran. + * IMPORTANT: This test fails through only timeout. + */ + @Test + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void testConcurrentRunWithPlatformThreads() throws Throwable { + when(concurrencyConfig.isMultiConcurrent()).thenReturn(true); + when(concurrencyConfig.getNumberOfPlatformThreads()).thenReturn(4); + + InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); + + when(runtimeClient.nextInvocation()) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(null) + .thenReturn(null) + .thenReturn(null) + .thenReturn(null); + + 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()); + // 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 + assertEquals(7 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); + } + + @Test + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void testConcurrentRunWithPlatformThreadsWithFailures() throws Throwable { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(concurrencyConfig.isMultiConcurrent()).thenReturn(true); + when(concurrencyConfig.getNumberOfPlatformThreads()).thenReturn(4); + + InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); + InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); + InvocationRequest userFaultRequest = mock(InvocationRequest.class); + final String UserFaultID = "Injected Fault Request ID"; + when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID); + + when(runtimeClient.nextInvocation()) + .thenReturn(failImmediatelyRequest) + .thenReturn(userFaultRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(null) + .thenReturn(null) + .thenReturn(null); + + AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); + + // One for each of failImmediatelyRequest and userFaultRequest in finally block + three from the uncaught null expections. + verify(lambdaLogger, times(5)).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()); + + // 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()); + + // Hashmap keys should equal the minumum between(number of threads (runtime loops) AND number of tasks that ran successfully). + assertEquals(2, SampleHandler.hashMap.size()); + + // Hashmap total count should equal all tasks that ran * number of iterations per task + assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); + } + + @Test + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void testSequentialWithFailures() throws Throwable { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(concurrencyConfig.isMultiConcurrent()).thenReturn(false); + + InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); + InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); + InvocationRequest userFaultRequest = mock(InvocationRequest.class); + final String UserFaultID = "Injected Fault Request ID"; + when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID); + + when(runtimeClient.nextInvocation()) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(failImmediatelyRequest) + .thenReturn(userFaultRequest); + + AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); + + // One for failImmediatelyRequest and userFaultRequest in finally block. + 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()); + + // 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()); + + // Hashmap keys should equal one as it is not multithreaded. + assertEquals(1, SampleHandler.hashMap.size()); + + // Hashmap total count should equal all tasks that ran * number of iterations per task + assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); + } +} \ No newline at end of file diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java new file mode 100644 index 000000000..bbb43cd14 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java @@ -0,0 +1,94 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package com.amazonaws.services.lambda.runtime.api.client.util; + +import com.amazonaws.services.lambda.runtime.api.client.ReservedRuntimeEnvironmentVariables; +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ConcurrencyConfigTest { + @Mock + private LambdaContextLogger lambdaLogger; + + @Mock + private EnvReader envReader; + + @Test + void testDefaultConfiguration() { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn(null); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads()); + assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent()); + } + + @Test + void testValidPlatformThreadsConfig() { + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("4"); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + assertEquals(4, config.getNumberOfPlatformThreads()); + assertEquals(true, config.isMultiConcurrent()); + } + + @Test + void testInvalidPlatformThreadsConfig() { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("invalid"); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads()); + verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN)); + assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent()); + } + + @Test + void testExceedingPlatformThreadsLimit() { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("1001"); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads()); + verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN)); + assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent()); + } + + @Test + void testGetConcurrencyConfigMessage() { + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("4"); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + String expectedMessage = "Starting 4 concurrent function handler threads."; + assertEquals(expectedMessage, config.getConcurrencyConfigMessage()); + assertEquals(true, config.isMultiConcurrent()); + } + + @Test + void testGetConcurrencyConfigWithNoConcurrency() { + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("false"); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + assertEquals(0, config.getNumberOfPlatformThreads()); + assertEquals(false, config.isMultiConcurrent()); + } +} From 2769f83b79ce0a83a388468c831c77bd79863b18 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Thu, 8 May 2025 13:29:35 +0100 Subject: [PATCH 03/26] Merge from Public Repo (#83) * Merge from Public Repo * Fix Failing Tests --------- Co-authored-by: Mohammed Ehab --- .../workflows/runtime-interface-client_merge_to_main.yml | 6 +----- .../lambda/runtime/api/client/EventHandlerLoader.java | 1 - .../lambda/runtime/api/client/api/LambdaContext.java | 7 ------- .../src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp | 1 + .../runtime/api/client/PojoSerializerLoaderTest.java | 2 ++ .../lambda/runtime/api/client/api/LambdaContextTest.java | 3 +-- .../runtime/api/client/logging/JsonLogFormatterTest.java | 2 -- 7 files changed, 5 insertions(+), 17 deletions(-) diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index 8909d56bf..e07b191e1 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -28,7 +28,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 - name: Set up JDK 1.8 uses: actions/setup-java@v4 @@ -47,10 +47,6 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} - - name: Build and install serialization dependency locally - working-directory: ./aws-lambda-java-serialization - run: mvn clean install -DskipTests - - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client run: make build diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java index 8fab27fed..af146fd93 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java @@ -582,7 +582,6 @@ public ByteArrayOutputStream call(InvocationRequest request) throws Error, Excep LambdaEnvironment.FUNCTION_VERSION, request.getInvokedFunctionArn(), request.getTenantId(), - request.getXrayTraceId(), clientContext ); diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java index 20b77262d..bd1463db6 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java @@ -23,7 +23,6 @@ public class LambdaContext implements Context { private final CognitoIdentity cognitoIdentity; private final ClientContext clientContext; private final String tenantId; - private final String xrayTraceId; private final LambdaLogger logger; public LambdaContext( @@ -37,7 +36,6 @@ public LambdaContext( String functionVersion, String invokedFunctionArn, String tenantId, - String xrayTraceId, ClientContext clientContext ) { this.memoryLimit = memoryLimit; @@ -51,7 +49,6 @@ public LambdaContext( this.functionVersion = functionVersion; this.invokedFunctionArn = invokedFunctionArn; this.tenantId = tenantId; - this.xrayTraceId = xrayTraceId; this.logger = com.amazonaws.services.lambda.runtime.LambdaRuntime.getLogger(); } @@ -101,10 +98,6 @@ public String getTenantId() { return tenantId; } - public String getXrayTraceId() { - return xrayTraceId; - } - public LambdaLogger getLogger() { return logger; } diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp index e713da985..3897d37db 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp @@ -40,6 +40,7 @@ static constexpr auto CLIENT_CONTEXT_HEADER = "lambda-runtime-client-context"; 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"; thread_local static CURL* m_curl_handle = curl_easy_init(); enum Endpoints { diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java index 7c6e9dcb4..4ebcf5d7e 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java @@ -8,6 +8,7 @@ import com.amazonaws.services.lambda.runtime.CustomPojoSerializer; import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -31,6 +32,7 @@ class PojoSerializerLoaderTest { @Mock private CustomPojoSerializer mockSerializer; + @AfterEach @BeforeEach void setUp() throws Exception { resetStaticFields(); diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java index f7da76198..58880be43 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java @@ -19,7 +19,6 @@ public class LambdaContextTest { private static final LambdaClientContext CLIENT_CONTEXT = new LambdaClientContext(); public static final int MEMORY_LIMIT = 128; public static final String TENANT_ID = "tenant-id"; - public static final String X_RAY_TRACE_ID = "x-ray-trace-id"; @Test public void getRemainingTimeInMillis() { @@ -56,6 +55,6 @@ public void getRemainingTimeInMillis_Deadline() throws InterruptedException { private LambdaContext createContextWithDeadline(long deadlineTimeInMs) { return new LambdaContext(MEMORY_LIMIT, deadlineTimeInMs, REQUEST_ID, LOG_GROUP_NAME, LOG_STREAM_NAME, - FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, X_RAY_TRACE_ID, CLIENT_CONTEXT); + FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, CLIENT_CONTEXT); } } diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java index 91ce9d2a3..531e9ca94 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java @@ -30,7 +30,6 @@ void testFormattingWithLambdaContext() { null, "function-arn", null, - null, null ); assertFormatsString("test log", LogLevel.WARN, context); @@ -49,7 +48,6 @@ void testFormattingWithTenantIdInLambdaContext() { null, "function-arn", "tenant-id", - "xray-trace-id", null ); assertFormatsString("test log", LogLevel.WARN, context); From a66ed4bdd0a461f486253c1f9adb9f20f5783f86 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:17:36 +0100 Subject: [PATCH 04/26] Support Multiconcurrent Logging (#86) * Support Multiconcurrent Logging --------- Co-authored-by: Mohammed Ehab --- .../api/client/logging/JsonLogFormatter.java | 16 ++++--- .../api/client/logging/StdOutLogSink.java | 2 +- .../logging/AbstractLambdaLoggerTest.java | 43 ++++++++++++++++++- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java index f463e7ee5..f1051a216 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java @@ -22,7 +22,7 @@ public class JsonLogFormatter implements LogFormatter { withZone(ZoneId.of("UTC")); private final PojoSerializer serializer = GsonFactory.getInstance().getSerializer(StructuredLogMessage.class); - private LambdaContext lambdaContext; + private ThreadLocal lambdaContext = new ThreadLocal<>(); @Override public String format(String message, LogLevel logLevel) { @@ -39,10 +39,12 @@ private StructuredLogMessage createLogMessage(String message, LogLevel logLevel) msg.message = message; msg.level = logLevel; - if (lambdaContext != null) { - msg.AWSRequestId = lambdaContext.getAwsRequestId(); - msg.tenantId = lambdaContext.getTenantId(); + LambdaContext lambdaContextForCurrentThread = lambdaContext.get(); + if (lambdaContextForCurrentThread != null) { + msg.AWSRequestId = lambdaContextForCurrentThread.getAwsRequestId(); + msg.tenantId = lambdaContextForCurrentThread.getTenantId(); } + return msg; } @@ -53,6 +55,10 @@ private StructuredLogMessage createLogMessage(String message, LogLevel logLevel) */ @Override public void setLambdaContext(LambdaContext context) { - this.lambdaContext = context; + if (context == null) { + lambdaContext.remove(); + } else { + lambdaContext.set(context); + } } } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSink.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSink.java index 873e6fde5..90e7d39c2 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSink.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSink.java @@ -15,7 +15,7 @@ public void log(byte[] message) { log(LogLevel.UNDEFINED, LogFormat.TEXT, message); } - public void log(LogLevel logLevel, LogFormat logFormat, byte[] message) { + public synchronized void log(LogLevel logLevel, LogFormat logFormat, byte[] message) { try { System.out.write(message); } catch (IOException e) { diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java index baeb4c242..151e702af 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java @@ -5,10 +5,15 @@ import com.amazonaws.services.lambda.runtime.logging.LogFormat; import org.junit.jupiter.api.Test; +import java.nio.charset.StandardCharsets; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import com.amazonaws.lambda.thirdparty.org.json.JSONObject; import com.amazonaws.services.lambda.runtime.LambdaLogger; +import com.amazonaws.services.lambda.runtime.api.client.api.LambdaContext; import com.amazonaws.services.lambda.runtime.logging.LogLevel; @@ -20,12 +25,12 @@ public TestSink() { } @Override - public void log(byte[] message) { + public synchronized void log(byte[] message) { messages.add(message); } @Override - public void log(LogLevel logLevel, LogFormat logFormat, byte[] message) { + public synchronized void log(LogLevel logLevel, LogFormat logFormat, byte[] message) { messages.add(message); } @@ -62,6 +67,40 @@ public void testLoggingNullValuesWithoutLogLevelInText() { assertEquals("null", new String(sink.getMessages().get(1))); } + // Makes Sure Logging Contexts are thread local. + @Test + public void testMultiConcurrentLoggingWithoutLogLevelInJSON() { + TestSink sink = new TestSink(); + LambdaContextLogger logger = new LambdaContextLogger(sink, LogLevel.INFO, LogFormat.JSON); + + String someMessagePrefix = "Some Message from "; + String reqIDPrefix = "Thread ID as request# "; + + final int nThreads = 5; + ExecutorService es = Executors.newFixedThreadPool(nThreads); + for (int i = 0; i < nThreads; i++) { + es.submit(() -> logger.setLambdaContext(new LambdaContext(Integer.MAX_VALUE, Long.MAX_VALUE, reqIDPrefix + Thread.currentThread().getName(), "", "", "", null, "", "", "", null))); + } + + final int nMessages = 100_000; + for (int i = 0; i < nMessages; i++) { + es.submit(() -> logger.log(someMessagePrefix + Thread.currentThread().getName())); + } + + es.shutdown(); + while (!es.isTerminated()) { + ; + } + + assertEquals(nMessages, sink.getMessages().size()); + for (byte[] message : sink.getMessages()) { + JSONObject parsedLog = new JSONObject(new String(message, StandardCharsets.UTF_8)); + String parsedMessage = parsedLog.getString("message"); + String parsedReqID = parsedLog.getString("AWSRequestId"); + assertEquals(parsedMessage.substring(someMessagePrefix.length()), parsedReqID.substring(reqIDPrefix.length())); + } + } + @Test public void testLoggingNullValuesWithoutLogLevelInJSON() { TestSink sink = new TestSink(); From 1fde59f45783a8bb150b4bd10fe3a4cccb563675 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Wed, 18 Jun 2025 15:24:33 +0100 Subject: [PATCH 05/26] Modified RIC Behavior for Retries and Failure Handling (#84) * Modified RIC Behavior for Retries and Failure Handling + Refactoring --- .../lambda/runtime/api/client/AWSLambda.java | 97 ++++++----- .../ReservedRuntimeEnvironmentVariables.java | 11 +- .../lambda/runtime/api/client/UserFault.java | 2 +- .../runtimeapi/LambdaRuntimeApiClient.java | 6 + .../LambdaRuntimeApiClientImpl.java | 70 ++++++++ ...timeClientMaxRetriesExceededException.java | 15 ++ .../api/client/util/ConcurrencyConfig.java | 30 ++-- .../runtime/api/client/AWSLambdaTest.java | 150 ++++++++++++++++-- .../logging/AbstractLambdaLoggerTest.java | 7 +- .../LambdaRuntimeApiClientImplTest.java | 83 +++++++++- .../client/util/ConcurrencyConfigTest.java | 29 ++-- 11 files changed, 399 insertions(+), 101 deletions(-) create mode 100644 aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClientMaxRetriesExceededException.java diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java index fdd090077..9c6dd78b1 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java @@ -15,6 +15,7 @@ import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaError; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClient; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClientImpl; +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientMaxRetriesExceededException; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.RapidErrorType; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters.LambdaErrorConverter; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters.XRayErrorCauseConverter; @@ -235,7 +236,7 @@ private static LambdaContextLogger initLogger() { private static void startRuntimeLoopWithExecutor(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, ExecutorService executorService, LambdaRuntimeApiClient runtimeClient) { executorService.submit(() -> { try { - startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient); + startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient, false); } catch (Exception e) { lambdaLogger.log(String.format("Runtime Loop on Thread ID: %s Failed.\n%s", Thread.currentThread().getName(), UserFault.trace(e)), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); } @@ -259,49 +260,73 @@ protected static void startRuntimeLoops(LambdaRequestHandler lambdaRequestHandle } } } else { - startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient); + startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient, true); } } - private static void startRuntimeLoop(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient) throws Exception { + private static LambdaError createLambdaErrorFromThrowableOrUserFault(Throwable t) { + if (t instanceof UserFault) { + return new LambdaError( + LambdaErrorConverter.fromUserFault((UserFault) t), + RapidErrorType.BadFunctionCode); + } else { + return new LambdaError( + LambdaErrorConverter.fromThrowable(t), + XRayErrorCauseConverter.fromThrowable(t), + RapidErrorType.UserException); + } + } + + private static void setEnvVarForXrayTraceId(InvocationRequest request) { + if (request.getXrayTraceId() != null) { + System.setProperty(LAMBDA_TRACE_HEADER_PROP, request.getXrayTraceId()); + } else { + System.clearProperty(LAMBDA_TRACE_HEADER_PROP); + } + } + + private static void reportNonLoopTerminatingException(LambdaContextLogger lambdaLogger, Throwable t) { + lambdaLogger.log( + String.format( + "Runtime Loop on Thread ID: %s Faced and Exception. This exception will not stop the runtime loop.\nException:\n%s", + Thread.currentThread().getName(), UserFault.trace(t)), + lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + } + + /* + * In multiconcurrent mode (exitLoopOnErrors = false), The Runtime Loop will not exit unless LambdaRuntimeClientMaxRetriesExceededException is thrown when calling nextInvocationWithExponentialBackoff. + * In normal/sequential mode (exitLoopOnErrors = true), The Runtime Loop will exit if nextInvocation call fails, when UserFault is fatal, or an Error of type VirtualMachineError or IOError is thrown. + */ + private static void startRuntimeLoop(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient, boolean exitLoopOnErrors) throws Exception { boolean shouldExit = false; while (!shouldExit) { - UserFault userFault = null; - InvocationRequest request = runtimeClient.nextInvocation(); - if (request.getXrayTraceId() != null) { - System.setProperty(LAMBDA_TRACE_HEADER_PROP, request.getXrayTraceId()); - } else { - System.clearProperty(LAMBDA_TRACE_HEADER_PROP); - } - - ByteArrayOutputStream payload; try { - payload = lambdaRequestHandler.call(request); - runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray()); - // clear interrupted flag in case if it was set by user's code - Thread.interrupted(); - } catch (UserFault f) { - shouldExit = f.fatal; - userFault = f; - UserFault.filterStackTrace(f); - LambdaError error = new LambdaError( - LambdaErrorConverter.fromUserFault(f), - RapidErrorType.BadFunctionCode); - runtimeClient.reportInvocationError(request.getId(), error); + UserFault userFault = null; + InvocationRequest request = exitLoopOnErrors ? runtimeClient.nextInvocation() : runtimeClient.nextInvocationWithExponentialBackoff(lambdaLogger); + setEnvVarForXrayTraceId(request); + + try { + ByteArrayOutputStream payload = lambdaRequestHandler.call(request); + runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray()); + // 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); + } finally { + if (userFault != null) { + lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + } + } } catch (Throwable t) { - shouldExit = t instanceof VirtualMachineError || t instanceof IOError; - UserFault.filterStackTrace(t); - userFault = UserFault.makeUserFault(t); - - LambdaError error = new LambdaError( - LambdaErrorConverter.fromThrowable(t), - XRayErrorCauseConverter.fromThrowable(t), - RapidErrorType.UserException); - runtimeClient.reportInvocationError(request.getId(), error); - } finally { - if (userFault != null) { - lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + if (exitLoopOnErrors || t instanceof LambdaRuntimeClientMaxRetriesExceededException) { + throw t; } + + reportNonLoopTerminatingException(lambdaLogger, t); } } } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java index 2d32d4048..1de3dca99 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java @@ -107,16 +107,9 @@ public interface ReservedRuntimeEnvironmentVariables { */ String TZ = "TZ"; - /** - * Boolean determining whether the RIC should run multi-concurrent runtime loops. Default value is "false". - * In case it is set to "true", AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY can be used to set the required number of concurrent runtime loops. - */ - String AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC = "AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC"; - /* * Used to set the required number of concurrent runtime loops, - * Only used if AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC is set to "true", - * If AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY is not set, the default number of concurrent runtime loops is the number of cores. + * If AWS_LAMBDA_MAX_CONCURRENCY is not set, the default number of concurrent runtime loops is the number of cores. */ - String AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY = "AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY"; + String AWS_LAMBDA_MAX_CONCURRENCY = "AWS_LAMBDA_MAX_CONCURRENCY"; } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java index c7c5c9ddf..7d8a50347 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java @@ -38,7 +38,7 @@ public UserFault(String msg, String exception, String trace, Boolean fatal) { * No more user code should run after a fault. */ public static UserFault makeUserFault(Throwable t) { - return makeUserFault(t, false); + return t instanceof UserFault ? (UserFault) t : makeUserFault(t, false); } public static UserFault makeUserFault(Throwable t, boolean fatal) { diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java index e2ae0969a..a62aeb9b8 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java @@ -4,6 +4,7 @@ */ package com.amazonaws.services.lambda.runtime.api.client.runtimeapi; +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; import java.io.IOException; @@ -24,6 +25,11 @@ public interface LambdaRuntimeApiClient { */ InvocationRequest nextInvocation() throws IOException; + /** + * Get next invocation with exponential backoff + */ + InvocationRequest nextInvocationWithExponentialBackoff(LambdaContextLogger lambdaLogger) throws Exception; + /** * Report invocation success * @param requestId request id diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java index 65024b98e..caca69aa7 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java @@ -4,7 +4,11 @@ */ package com.amazonaws.services.lambda.runtime.api.client.runtimeapi; +import com.amazonaws.services.lambda.runtime.api.client.UserFault; +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -14,6 +18,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; import static java.net.HttpURLConnection.HTTP_ACCEPTED; import static java.net.HttpURLConnection.HTTP_OK; import static java.nio.charset.StandardCharsets.UTF_8; @@ -30,6 +36,11 @@ public class LambdaRuntimeApiClientImpl implements LambdaRuntimeApiClient { private static final String ERROR_TYPE_HEADER = "Lambda-Runtime-Function-Error-Type"; // 1MiB private static final int XRAY_ERROR_CAUSE_MAX_HEADER_SIZE = 1024 * 1024; + + // ~32 Seconds Max Backoff. + private static final long MAX_BACKOFF_PERIOD_MS = 1024 * 32; + private static final long INITIAL_BACKOFF_PERIOD_MS = 100; + private static final int MAX_NUMBER_OF_RETRIALS = 5; private final String baseUrl; private final String invocationEndpoint; @@ -52,6 +63,65 @@ public InvocationRequest nextInvocation() { return NativeClient.next(); } + /* + * Retry immediately then retry with exponential backoff. + */ + public static T getSupplierResultWithExponentialBackoff(LambdaContextLogger lambdaLogger, long initialDelayMS, long maxBackoffPeriodMS, int maxNumOfAttempts, Supplier supplier, Function exceptionMessageComposer, Exception maxRetriesException) throws Exception { + long delayMS = initialDelayMS; + for (int attempts = 0; attempts < maxNumOfAttempts; attempts++) { + boolean isFirstAttempt = attempts == 0; + boolean isLastAttempt = (attempts + 1) == maxNumOfAttempts; + + // Try and log whichever exceptions happened + try { + return supplier.get(); + } catch (Exception e) { + String logMessage = exceptionMessageComposer.apply(e); + if (!isLastAttempt) { + logMessage += String.format("\nRetrying%s", isFirstAttempt ? "." : String.format(" in %d ms.", delayMS)); + } + + lambdaLogger.log(logMessage, lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + } + + // throw if ran out of attempts. + if (isLastAttempt) { + throw maxRetriesException; + } + + // update the delay duration. + if (!isFirstAttempt) { + try { + Thread.sleep(delayMS); + delayMS = Math.min(delayMS * 2, maxBackoffPeriodMS); + } catch (InterruptedException e) { + Thread.interrupted(); + } + } + } + + // Should Not be reached. + throw new IllegalStateException(); + } + + @Override + public InvocationRequest nextInvocationWithExponentialBackoff(LambdaContextLogger lambdaLogger) throws Exception { + Supplier nextInvocationSupplier = () -> nextInvocation(); + Function exceptionMessageComposer = (e) -> { + return String.format("Runtime Loop on Thread ID: %s Failed to fetch next invocation.\n%s", Thread.currentThread().getName(), UserFault.trace(e)); + }; + + return getSupplierResultWithExponentialBackoff( + lambdaLogger, + INITIAL_BACKOFF_PERIOD_MS, + MAX_BACKOFF_PERIOD_MS, + MAX_NUMBER_OF_RETRIALS, + nextInvocationSupplier, + exceptionMessageComposer, + new LambdaRuntimeClientMaxRetriesExceededException("Get Next Invocation") + ); + } + @Override public void reportInvocationSuccess(String requestId, byte[] response) { NativeClient.postInvocationResponse(requestId.getBytes(UTF_8), response); diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClientMaxRetriesExceededException.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClientMaxRetriesExceededException.java new file mode 100644 index 000000000..467afa25c --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClientMaxRetriesExceededException.java @@ -0,0 +1,15 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +package com.amazonaws.services.lambda.runtime.api.client.runtimeapi; + +public class LambdaRuntimeClientMaxRetriesExceededException extends LambdaRuntimeClientException { + // 429 is possible; however, that is more appropriate when a server is responding to a spamming client that it wants to rate limit. + // In Our case, however, the RIC is a client that is not able to get a response from an upstream server, so 500 is more appropriate. + public LambdaRuntimeClientMaxRetriesExceededException(String operationName) { + super("Maximum Number of retries have been exceed" + (operationName.equals(null) + ? String.format(" for the %s operation.", operationName) + : "."), 500); + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java index db1f6c218..96fbd4b79 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java @@ -12,33 +12,27 @@ import com.amazonaws.services.lambda.runtime.logging.LogLevel; public class ConcurrencyConfig { - private static final int AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT = 1000; + private static final int AWS_LAMBDA_MAX_CONCURRENCY_LIMIT = 1000; private final int numberOfPlatformThreads; - private final boolean isConcurrencyEnabled; public ConcurrencyConfig(LambdaContextLogger logger) { this(logger, new EnvReader()); } public ConcurrencyConfig(LambdaContextLogger logger, EnvReader envReader) { - this.isConcurrencyEnabled = Boolean.parseBoolean(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)); - int platformThreads = this.isConcurrencyEnabled ? Runtime.getRuntime().availableProcessors() : 0; - - if (this.isConcurrencyEnabled) { - try { - int readNumOfPlatformThreads = Integer.parseUnsignedInt(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)); - if (readNumOfPlatformThreads > 0 && readNumOfPlatformThreads <= AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT) { - platformThreads = readNumOfPlatformThreads; - } else { - throw new IllegalArgumentException(); - } - } catch (Exception e) { - String message = String.format("User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to number of cores as number of platform threads.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY, AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT, UserFault.trace(e)); - logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED); + int readNumOfPlatformThreads = 0; + try { + readNumOfPlatformThreads = Integer.parseInt(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)); + if (readNumOfPlatformThreads <= 0 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) { + throw new IllegalArgumentException(); } + } catch (Exception e) { + String message = String.format("User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to no concurrency.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY, AWS_LAMBDA_MAX_CONCURRENCY_LIMIT, UserFault.trace(e)); + logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED); + readNumOfPlatformThreads = 0; } - this.numberOfPlatformThreads = platformThreads; + this.numberOfPlatformThreads = readNumOfPlatformThreads; } public String getConcurrencyConfigMessage() { @@ -46,7 +40,7 @@ public String getConcurrencyConfigMessage() { } public boolean isMultiConcurrent() { - return this.isConcurrencyEnabled && this.numberOfPlatformThreads >= 2; + return this.numberOfPlatformThreads > 1; } public int getNumberOfPlatformThreads() { diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java index 4412214e9..de1f9e73f 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java @@ -12,6 +12,7 @@ import static org.mockito.Mockito.*; import java.io.ByteArrayOutputStream; +import java.io.IOError; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -19,6 +20,7 @@ import java.util.concurrent.atomic.AtomicInteger; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClientImpl; +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientMaxRetriesExceededException; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; import com.amazonaws.services.lambda.runtime.api.client.util.ConcurrencyConfig; import com.amazonaws.services.lambda.runtime.api.client.util.EnvReader; @@ -115,6 +117,8 @@ private static InvocationRequest getFakeInvocationRequest(String id) { return request; } + private static final LambdaRuntimeClientMaxRetriesExceededException fakelambdaRuntimeClientMaxRetriesExceededException = new LambdaRuntimeClientMaxRetriesExceededException("Fake max retries happened"); + @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); @@ -135,7 +139,7 @@ void testConcurrentRunWithPlatformThreads() throws Throwable { InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); - when(runtimeClient.nextInvocation()) + when(runtimeClient.nextInvocationWithExponentialBackoff(lambdaLogger)) .thenReturn(successfullInvocationRequest) .thenReturn(successfullInvocationRequest) .thenReturn(successfullInvocationRequest) @@ -143,10 +147,10 @@ void testConcurrentRunWithPlatformThreads() throws Throwable { .thenReturn(successfullInvocationRequest) .thenReturn(successfullInvocationRequest) .thenReturn(successfullInvocationRequest) - .thenReturn(null) - .thenReturn(null) - .thenReturn(null) - .thenReturn(null); + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException); AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); @@ -171,20 +175,23 @@ void testConcurrentRunWithPlatformThreadsWithFailures() throws Throwable { final String UserFaultID = "Injected Fault Request ID"; when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID); - when(runtimeClient.nextInvocation()) + when(runtimeClient.nextInvocationWithExponentialBackoff(lambdaLogger)) .thenReturn(failImmediatelyRequest) .thenReturn(userFaultRequest) .thenReturn(successfullInvocationRequest) .thenReturn(successfullInvocationRequest) - .thenReturn(null) - .thenReturn(null) - .thenReturn(null); + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException); AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); - // One for each of failImmediatelyRequest and userFaultRequest in finally block + three from the uncaught null expections. - verify(lambdaLogger, times(5)).log(anyString(), eq(LogLevel.ERROR)); - + // One for each of failImmediatelyRequest and userFaultRequest in finally block + // Four for crashing the Four runtime loops in the outermost catch of the runtime loop after the Null responses. + // 2 + 4 = 6 + 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()); @@ -198,24 +205,86 @@ void testConcurrentRunWithPlatformThreadsWithFailures() throws Throwable { // Hashmap total count should equal all tasks that ran * number of iterations per task assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); } + + @Test + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void testConcurrentModeLoopDoesNotExitExceptForLambdaRuntimeClientMaxRetriesExceededException() throws Throwable { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(concurrencyConfig.isMultiConcurrent()).thenReturn(true); + when(concurrencyConfig.getNumberOfPlatformThreads()).thenReturn(1); + + InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); + InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); + + InvocationRequest userFaultRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode. + final String UserFaultID = "Injected Fault Request ID"; + when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID); + + InvocationRequest virtualMachineErrorRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode. + final String IOErrorID = "ioerr1"; + when(virtualMachineErrorRequest.getId()).thenThrow(UserFault.makeUserFault(new IOError(new Throwable()), true)).thenReturn(IOErrorID); + + when(runtimeClient.nextInvocationWithExponentialBackoff(lambdaLogger)) + .thenReturn(failImmediatelyRequest) + .thenReturn(userFaultRequest) + .thenReturn(virtualMachineErrorRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenReturn(successfullInvocationRequest); + + AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); + + // One for each of failImmediatelyRequest, userFaultRequest, and virtualMachineErrorRequest + One for the runtime loop thread crashing. + 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()); + + // 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()); + + // Hashmap keys should equal the minumum between(number of threads (runtime loops) AND number of tasks that ran successfully). + assertEquals(1, SampleHandler.hashMap.size()); + + // Hashmap total count should equal all tasks that ran * number of iterations per task + assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); + } + + + /* + * + * NON-CONCURRENT-MODE TESTS + * + */ @Test @Timeout(value = 1, unit = TimeUnit.MINUTES) - void testSequentialWithFailures() throws Throwable { + void testSequentialWithFatalUserFaultErrorStopsLoop() throws Throwable { when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); when(concurrencyConfig.isMultiConcurrent()).thenReturn(false); InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); - InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); - InvocationRequest userFaultRequest = mock(InvocationRequest.class); + InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); // recoverable error in all modes. + + InvocationRequest userFaultRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode. final String UserFaultID = "Injected Fault Request ID"; when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID); + + InvocationRequest virtualMachineErrorRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode. + final String IOErrorID = "ioerr1"; + when(virtualMachineErrorRequest.getId()).thenThrow(UserFault.makeUserFault(new IOError(new Throwable()), true)).thenReturn(IOErrorID); when(runtimeClient.nextInvocation()) .thenReturn(successfullInvocationRequest) .thenReturn(successfullInvocationRequest) .thenReturn(failImmediatelyRequest) - .thenReturn(userFaultRequest); + .thenReturn(userFaultRequest) + // these two should not be even feltched since userFaultRequest is not recoverable. + .thenReturn(successfullInvocationRequest) + .thenReturn(virtualMachineErrorRequest); AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); @@ -226,8 +295,55 @@ void testSequentialWithFailures() throws Throwable { verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any()); verify(runtimeClient).reportInvocationError(eq(UserFaultID), any()); - // Success Reports Must Equal number of tasks that ran successfully. + // 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()); + + // Hashmap keys should equal one as it is not multithreaded. + assertEquals(1, SampleHandler.hashMap.size()); + + // Hashmap total count should equal all tasks that ran * number of iterations per task + assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); + } + + @Test + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void testSequentialWithVirtualMachineErrorStopsLoop() throws Throwable { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(concurrencyConfig.isMultiConcurrent()).thenReturn(false); + + InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); + InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); // recoverable error in all modes. + + InvocationRequest userFaultRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode. + final String UserFaultID = "Injected Fault Request ID"; + when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID); + + InvocationRequest virtualMachineErrorRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode. + final String IOErrorID = "ioerr1"; + when(virtualMachineErrorRequest.getId()).thenThrow(UserFault.makeUserFault(new IOError(new Throwable()), true)).thenReturn(IOErrorID); + + when(runtimeClient.nextInvocation()) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(failImmediatelyRequest) + .thenReturn(virtualMachineErrorRequest) + // these two should not be even feltched since userFaultRequest is not recoverable. + .thenReturn(successfullInvocationRequest) + .thenReturn(userFaultRequest); + + AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); + + // One for failImmediatelyRequest and userFaultRequest in finally block. + 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()); + + // 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()); // Hashmap keys should equal one as it is not multithreaded. assertEquals(1, SampleHandler.hashMap.size()); diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java index 151e702af..f97602e37 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java @@ -67,7 +67,12 @@ public void testLoggingNullValuesWithoutLogLevelInText() { assertEquals("null", new String(sink.getMessages().get(1))); } - // Makes Sure Logging Contexts are thread local. + /* + * Makes Sure Logging Contexts are thread local. + * We start `setLambdaContext` operations using the **single** shared `logger` object on a fixed thread pool, differentiating them with thread IDs. + * We then start concurrent `log` operations which are scheduled using that fixed pool. + * It is then verified that a given log operation, which logs the thread ID it is running on, used a context that had the same thread ID. + */ @Test public void testMultiConcurrentLoggingWithoutLogLevelInJSON() { TestSink sink = new TestSink(); diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java index 473e2aef3..710c1565e 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java @@ -14,6 +14,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.ErrorRequest; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.StackElement; @@ -22,8 +24,18 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import okhttp3.HttpUrl; import static java.net.HttpURLConnection.HTTP_ACCEPTED; import static java.net.HttpURLConnection.HTTP_OK; @@ -36,6 +48,14 @@ @DisabledOnOs(OS.MAC) public class LambdaRuntimeApiClientImplTest { + @SuppressWarnings("rawtypes") + private final Supplier mockSupplier = mock(Supplier.class); + @SuppressWarnings("rawtypes") + private final Function mockExceptionMessageComposer = mock(Function.class); + private final LambdaContextLogger mockLambdaContextLogger = mock(LambdaContextLogger.class); + private final LambdaRuntimeClientMaxRetriesExceededException retriesExceededException = new LambdaRuntimeClientMaxRetriesExceededException("Testing Invocations"); + final String fakeExceptionMessage = "Something bad"; + MockWebServer mockWebServer; LambdaRuntimeApiClientImpl lambdaRuntimeApiClientImpl; @@ -43,7 +63,7 @@ public class LambdaRuntimeApiClientImplTest { ErrorRequest errorRequest = new ErrorRequest("testErrorMessage", "testErrorType", errorStackStrace); String requestId = "1234"; - + @BeforeEach void setUp() { mockWebServer = new MockWebServer(); @@ -51,6 +71,67 @@ void setUp() { lambdaRuntimeApiClientImpl = new LambdaRuntimeApiClientImpl(hostnamePort); } + @SuppressWarnings("unchecked") + @Test + public void testgetSupplierResultWithExponentialBackoffAllFailing() throws Exception { + + when(mockSupplier.get()).thenThrow(new RuntimeException(new Exception(fakeExceptionMessage))); + when(mockExceptionMessageComposer.apply(any())).thenReturn(fakeExceptionMessage); + + try { + LambdaRuntimeApiClientImpl.getSupplierResultWithExponentialBackoff(mockLambdaContextLogger, 5, 200, 5, mockSupplier, mockExceptionMessageComposer, retriesExceededException); + } catch (LambdaRuntimeClientMaxRetriesExceededException e) { } + + verify(mockSupplier, times(5)).get(); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 5 ms."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 10 ms."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 20 ms."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage), any()); + verify(mockLambdaContextLogger, times(5)).log(anyString(), any()); + } + + @SuppressWarnings("unchecked") + @Test + public void testgetSupplierResultWithExponentialBackoffTwoFailingThenSuccess() throws Exception { + InvocationRequest fakeRequest = new InvocationRequest(); + + when(mockExceptionMessageComposer.apply(any())).thenReturn(fakeExceptionMessage); + + when(mockSupplier.get()) + .thenThrow(new RuntimeException(new Exception(fakeExceptionMessage))) + .thenThrow(new RuntimeException(new Exception(fakeExceptionMessage))) + .thenReturn(fakeRequest); + + InvocationRequest invocationRequest = (InvocationRequest) LambdaRuntimeApiClientImpl.getSupplierResultWithExponentialBackoff(mockLambdaContextLogger, 5, 200, 5, mockSupplier, mockExceptionMessageComposer, retriesExceededException); + + assertEquals(fakeRequest, invocationRequest); + verify(mockSupplier, times(3)).get(); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 5 ms."), any()); + verify(mockLambdaContextLogger, times(2)).log(anyString(), any()); + } + + @SuppressWarnings("unchecked") + @Test + public void testgetSupplierResultWithExponentialBackoffDoesntGoAboveMax() throws Exception { + + when(mockSupplier.get()).thenThrow(new RuntimeException(new Exception(fakeExceptionMessage))); + + when(mockExceptionMessageComposer.apply(any())).thenReturn(fakeExceptionMessage); + + try { + LambdaRuntimeApiClientImpl.getSupplierResultWithExponentialBackoff(mockLambdaContextLogger, 100, 200, 5, mockSupplier, mockExceptionMessageComposer, retriesExceededException); + } catch (LambdaRuntimeClientMaxRetriesExceededException e) { } + + verify(mockSupplier, times(5)).get(); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 100 ms."), any()); + verify(mockLambdaContextLogger, times(2)).log(eq(fakeExceptionMessage + "\nRetrying in 200 ms."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage), any()); + verify(mockLambdaContextLogger, times(5)).log(anyString(), any()); + } + @Test public void reportInitErrorTest() { try { diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java index bbb43cd14..ef1d832ec 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java @@ -30,18 +30,16 @@ class ConcurrencyConfigTest { @Test void testDefaultConfiguration() { when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn(null); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn(null); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); - assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads()); - assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent()); + assertEquals(0, config.getNumberOfPlatformThreads()); + assertEquals(false, config.isMultiConcurrent()); } @Test void testValidPlatformThreadsConfig() { - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("4"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("4"); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); assertEquals(4, config.getNumberOfPlatformThreads()); @@ -51,31 +49,28 @@ void testValidPlatformThreadsConfig() { @Test void testInvalidPlatformThreadsConfig() { when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("invalid"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("invalid"); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); - assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads()); + assertEquals(0, config.getNumberOfPlatformThreads()); verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN)); - assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent()); + assertEquals(false, config.isMultiConcurrent()); } @Test void testExceedingPlatformThreadsLimit() { when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("1001"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("1001"); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); - assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads()); + assertEquals(0, config.getNumberOfPlatformThreads()); verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN)); - assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent()); + assertEquals(false, config.isMultiConcurrent()); } @Test void testGetConcurrencyConfigMessage() { - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("4"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("4"); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); String expectedMessage = "Starting 4 concurrent function handler threads."; @@ -85,8 +80,6 @@ void testGetConcurrencyConfigMessage() { @Test void testGetConcurrencyConfigWithNoConcurrency() { - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("false"); - ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); assertEquals(0, config.getNumberOfPlatformThreads()); assertEquals(false, config.isMultiConcurrent()); From 93221da3c6d2c74d7b69f91a12a99f7eaabc1360 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Thu, 26 Jun 2025 16:52:16 +0100 Subject: [PATCH 06/26] Merge from public repo (#88) Merge from Public to Private Repo --- .github/dependabot.yml | 1 - README.md | 4 ++-- aws-lambda-java-core/RELEASE.CHANGELOG.md | 4 ---- aws-lambda-java-core/pom.xml | 2 +- .../com/amazonaws/services/lambda/runtime/Context.java | 10 ---------- aws-lambda-java-events/README.md | 2 +- aws-lambda-java-events/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-events/pom.xml | 2 +- aws-lambda-java-runtime-interface-client/README.md | 4 ++-- .../RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-runtime-interface-client/pom.xml | 4 ++-- aws-lambda-java-tests/pom.xml | 2 +- .../fastJson/HelloWorldFunction/pom.xml | 2 +- .../gson/HelloWorldFunction/pom.xml | 2 +- .../moshi/HelloWorldFunction/pom.xml | 2 +- .../request-stream-handler/HelloWorldFunction/pom.xml | 2 +- samples/kinesis-firehose-event-handler/pom.xml | 2 +- 17 files changed, 23 insertions(+), 30 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 34d287eec..88f18ea29 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,7 +4,6 @@ updates: directory: "/aws-lambda-java-runtime-interface" schedule: interval: "weekly" - - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/README.md b/README.md index 7f4970949..b5153a87f 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ public class SqsHandler implements RequestHandler { com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 ``` @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 ``` diff --git a/aws-lambda-java-core/RELEASE.CHANGELOG.md b/aws-lambda-java-core/RELEASE.CHANGELOG.md index aebc8ecd9..527e7dd46 100644 --- a/aws-lambda-java-core/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-core/RELEASE.CHANGELOG.md @@ -1,7 +1,3 @@ -### September 3, 2025 -`1.4.0` -- Getter support for x-ray trace ID through the Context object - ### May 26, 2025 `1.3.0` - Adding support for multi tenancy ([#545](https://github.com/aws/aws-lambda-java-libs/pull/545)) diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index cca9d0cdf..77245c9be 100644 --- a/aws-lambda-java-core/pom.xml +++ b/aws-lambda-java-core/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-core - 1.4.0 + 1.3.0 jar AWS Lambda Java Core Library diff --git a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java index ed9311a11..152765df1 100644 --- a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java +++ b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java @@ -109,14 +109,4 @@ public interface Context { default String getTenantId() { return null; } - - /** - * - * Returns the X-Ray trace ID associated with the request. - * - * @return null by default - */ - default String getXrayTraceId() { - return null; - } } diff --git a/aws-lambda-java-events/README.md b/aws-lambda-java-events/README.md index 87c61f345..43c25d76a 100644 --- a/aws-lambda-java-events/README.md +++ b/aws-lambda-java-events/README.md @@ -74,7 +74,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 ... diff --git a/aws-lambda-java-events/RELEASE.CHANGELOG.md b/aws-lambda-java-events/RELEASE.CHANGELOG.md index 6c1769751..a4bcd10a0 100644 --- a/aws-lambda-java-events/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-events/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### June 17, 2025 +`3.16.0`: +- Add Schema metadata related attributes in KafkaEvent ([#548](https://github.com/aws/aws-lambda-java-libs/pull/548)) + ### January 31, 2025 `3.15.0`: - Fix `CognitoUserPoolPreTokenGenerationEventV2` model ([#519](https://github.com/aws/aws-lambda-java-libs/pull/519)) diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index f1364e7ab..8799966be 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 jar AWS Lambda Java Events Library diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 368ab710a..67a5972d6 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 @@ -203,7 +203,7 @@ platform-specific JAR by setting the ``. com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 linux-x86_64 ``` diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index 6a781b270..ac073ae85 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### May 21, 2025 +`2.7.0` +- Adding support for multi tenancy ([#540](https://github.com/aws/aws-lambda-java-libs/pull/540)) + ### August 7, 2024 `2.6.0` - Runtime API client improvements: use Lambda-Runtime-Function-Error-Type for reporting errors in format "Runtime." diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index fde515fda..866520092 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 jar AWS Lambda Java Runtime Interface Client @@ -61,7 +61,7 @@ com.amazonaws aws-lambda-java-core - 1.4.0 + 1.3.0 com.amazonaws diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 9e3419c69..6589ca742 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -45,7 +45,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 org.junit.jupiter diff --git a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml index 7325c72a0..7d3c44246 100644 --- a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 diff --git a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml index dd3b8e9c5..fd4271824 100644 --- a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 com.google.code.gson diff --git a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml index f23214976..2773ef1f1 100644 --- a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 diff --git a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml index 68e7e81e9..f6ca21bf7 100644 --- a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 com.google.code.gson diff --git a/samples/kinesis-firehose-event-handler/pom.xml b/samples/kinesis-firehose-event-handler/pom.xml index 3d03205d3..56c959244 100644 --- a/samples/kinesis-firehose-event-handler/pom.xml +++ b/samples/kinesis-firehose-event-handler/pom.xml @@ -46,7 +46,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 From d7e2a6d188528ec22ade0e2ae8f6cd45fcdb8de5 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Thu, 26 Jun 2025 17:49:55 +0100 Subject: [PATCH 07/26] Version Bump to 2.8 (#89) Co-authored-by: Mohammed Ehab --- README.md | 2 +- aws-lambda-java-runtime-interface-client/README.md | 2 +- aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-runtime-interface-client/pom.xml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b5153a87f..4b22852c1 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.8.0 ``` diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 67a5972d6..512e66fe0 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.8.0 diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index ac073ae85..a030d288b 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### June 26, 2025 +`2.8.0` +- Refactoring + ### May 21, 2025 `2.7.0` - Adding support for multi tenancy ([#540](https://github.com/aws/aws-lambda-java-libs/pull/540)) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 866520092..a29be4279 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.8.0 jar AWS Lambda Java Runtime Interface Client From 2cf626b817042b65016d4c8532259d8c5a819ff2 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Fri, 4 Jul 2025 18:39:45 +0100 Subject: [PATCH 08/26] Only Log Concurrency Warning Message when AWS_LAMBDA_MAX_CONCURRENCY is set (#90) * Only Log Concurrency Warning Message when AWS_LAMBDA_MAX_CONCURRENCY is set * Var name change --------- Co-authored-by: Mohammed Ehab --- .../api/client/util/ConcurrencyConfig.java | 19 ++++++++++++++----- .../client/util/ConcurrencyConfigTest.java | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java index 96fbd4b79..d98ed71c8 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java @@ -22,14 +22,23 @@ public ConcurrencyConfig(LambdaContextLogger logger) { public ConcurrencyConfig(LambdaContextLogger logger, EnvReader envReader) { int readNumOfPlatformThreads = 0; try { - readNumOfPlatformThreads = Integer.parseInt(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)); - if (readNumOfPlatformThreads <= 0 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) { - throw new IllegalArgumentException(); + String readLambdaMaxConcurrencyEnvVar = envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY); + + // Log only if env var is actually set to an invalid value. Otherwise default to no concurrency silently. + if (!(readLambdaMaxConcurrencyEnvVar == null || readLambdaMaxConcurrencyEnvVar.isEmpty())) { + readNumOfPlatformThreads = Integer.parseInt(readLambdaMaxConcurrencyEnvVar); + if (readNumOfPlatformThreads <= 0 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) { + throw new IllegalArgumentException(); + } } } catch (Exception e) { - String message = String.format("User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to no concurrency.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY, AWS_LAMBDA_MAX_CONCURRENCY_LIMIT, UserFault.trace(e)); - logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED); readNumOfPlatformThreads = 0; + String message = String.format( + "User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to no concurrency.", + ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY, + AWS_LAMBDA_MAX_CONCURRENCY_LIMIT, + UserFault.trace(e)); + logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED); } this.numberOfPlatformThreads = readNumOfPlatformThreads; diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java index ef1d832ec..248625e37 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java @@ -29,10 +29,10 @@ class ConcurrencyConfigTest { @Test void testDefaultConfiguration() { - when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn(null); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + verifyNoInteractions(lambdaLogger); assertEquals(0, config.getNumberOfPlatformThreads()); assertEquals(false, config.isMultiConcurrent()); } From e35157b9751492ea65a80716144205b248a500de Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Tue, 8 Jul 2025 10:05:09 +0100 Subject: [PATCH 09/26] Version Bump RIC to 2.8.1 (#91) * Version Bump RIC to 2.8.1 --------- Authored-by: Mohammed Ehab --- README.md | 2 +- aws-lambda-java-runtime-interface-client/README.md | 2 +- aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-runtime-interface-client/pom.xml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4b22852c1..0870ec2b7 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.0 + 2.8.1 ``` diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 512e66fe0..90e2c0bb5 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.0 + 2.8.1 diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index a030d288b..daa8bde90 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### June 26, 2025 +`2.8.1` +- Refactoring + ### June 26, 2025 `2.8.0` - Refactoring diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index a29be4279..92d9d0f40 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.0 + 2.8.1 jar AWS Lambda Java Runtime Interface Client From ddb34451478eb57042fdd332b9caa2afd7a0bdf2 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Wed, 9 Jul 2025 17:08:43 +0100 Subject: [PATCH 10/26] Allow AWS_LAMBDA_MAX_CONCURRENCY to be one and crash the RIC Otherwise and Version Bump to 2.8.2 (#92) * Allow AWS_LAMBDA_MAX_CONCURRENCY to be one and crash the RIC Otherwise. * Version Bump to 2.8.2 --------- Authored-by: Mohammed Ehab --- README.md | 2 +- .../README.md | 2 +- .../RELEASE.CHANGELOG.md | 4 ++ .../pom.xml | 2 +- .../ReservedRuntimeEnvironmentVariables.java | 4 +- .../api/client/util/ConcurrencyConfig.java | 19 ++++---- .../client/util/ConcurrencyConfigTest.java | 47 ++++++++++++++----- 7 files changed, 51 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 0870ec2b7..70d7c7316 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.1 + 2.8.2 ``` diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 90e2c0bb5..e3c68fe8a 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.1 + 2.8.2 diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index daa8bde90..ff3d8bec5 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### June 26, 2025 +`2.8.2` +- Allow AWS_LAMBDA_MAX_CONCURRENCY to be One. Crash the RIC if it is set to an un-parsable string to an integer or an out of bounds value. + ### June 26, 2025 `2.8.1` - Refactoring diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 92d9d0f40..59f33145e 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.1 + 2.8.2 jar AWS Lambda Java Runtime Interface Client diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java index 1de3dca99..9fdec6b9f 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java @@ -108,8 +108,8 @@ public interface ReservedRuntimeEnvironmentVariables { String TZ = "TZ"; /* - * Used to set the required number of concurrent runtime loops, - * If AWS_LAMBDA_MAX_CONCURRENCY is not set, the default number of concurrent runtime loops is the number of cores. + * If set to a string parsable as an integer > 0, It enables multiconcurrency mode. + * Otherwise, if it is set to an invalid value, it will crash the whole RIC process. */ String AWS_LAMBDA_MAX_CONCURRENCY = "AWS_LAMBDA_MAX_CONCURRENCY"; } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java index d98ed71c8..7108c7801 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java @@ -14,6 +14,8 @@ public class ConcurrencyConfig { private static final int AWS_LAMBDA_MAX_CONCURRENCY_LIMIT = 1000; private final int numberOfPlatformThreads; + private final String INVALID_CONFIG_MESSAGE_PREFIX = String.format("User configured %s is invalid. Please make sure it is a positive number more than zero and less than or equal %d", + ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY, AWS_LAMBDA_MAX_CONCURRENCY_LIMIT); public ConcurrencyConfig(LambdaContextLogger logger) { this(logger, new EnvReader()); @@ -24,21 +26,16 @@ public ConcurrencyConfig(LambdaContextLogger logger, EnvReader envReader) { try { String readLambdaMaxConcurrencyEnvVar = envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY); - // Log only if env var is actually set to an invalid value. Otherwise default to no concurrency silently. - if (!(readLambdaMaxConcurrencyEnvVar == null || readLambdaMaxConcurrencyEnvVar.isEmpty())) { + if (readLambdaMaxConcurrencyEnvVar != null) { readNumOfPlatformThreads = Integer.parseInt(readLambdaMaxConcurrencyEnvVar); - if (readNumOfPlatformThreads <= 0 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) { + if (readNumOfPlatformThreads < 1 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) { throw new IllegalArgumentException(); } } } catch (Exception e) { - readNumOfPlatformThreads = 0; - String message = String.format( - "User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to no concurrency.", - ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY, - AWS_LAMBDA_MAX_CONCURRENCY_LIMIT, - UserFault.trace(e)); - logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED); + String message = String.format("%s\n%s", INVALID_CONFIG_MESSAGE_PREFIX, UserFault.trace(e)); + logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + throw e; } this.numberOfPlatformThreads = readNumOfPlatformThreads; @@ -49,7 +46,7 @@ public String getConcurrencyConfigMessage() { } public boolean isMultiConcurrent() { - return this.numberOfPlatformThreads > 1; + return this.numberOfPlatformThreads >= 1; } public int getNumberOfPlatformThreads() { diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java index 248625e37..096d52bb0 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java @@ -14,8 +14,9 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import static org.junit.Assert.assertThrows; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.contains; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @@ -27,6 +28,8 @@ class ConcurrencyConfigTest { @Mock private EnvReader envReader; + private static final String exitingRuntimeString = String.format("User configured %s is invalid.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY); + @Test void testDefaultConfiguration() { when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn(null); @@ -37,35 +40,51 @@ void testDefaultConfiguration() { assertEquals(false, config.isMultiConcurrent()); } + @Test + void testBelowMinPlatformThreadsLimit() { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("0"); + + assertThrows(IllegalArgumentException.class, () -> new ConcurrencyConfig(lambdaLogger, envReader)); + verify(lambdaLogger).log(contains(exitingRuntimeString), eq(LogLevel.ERROR)); + } + + @Test + void testMinValidPlatformThreadsConfig() { + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("1"); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + verifyNoInteractions(lambdaLogger); + assertEquals(1, config.getNumberOfPlatformThreads()); + assertEquals(true, config.isMultiConcurrent()); + } + @Test void testValidPlatformThreadsConfig() { when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("4"); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + verifyNoInteractions(lambdaLogger); assertEquals(4, config.getNumberOfPlatformThreads()); assertEquals(true, config.isMultiConcurrent()); } @Test - void testInvalidPlatformThreadsConfig() { + void testExceedingPlatformThreadsLimit() { when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("invalid"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("1001"); - ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); - assertEquals(0, config.getNumberOfPlatformThreads()); - verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN)); - assertEquals(false, config.isMultiConcurrent()); + assertThrows(IllegalArgumentException.class, () -> new ConcurrencyConfig(lambdaLogger, envReader)); + verify(lambdaLogger).log(contains(exitingRuntimeString), eq(LogLevel.ERROR)); } @Test - void testExceedingPlatformThreadsLimit() { + void testInvalidPlatformThreadsConfig() { when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("1001"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("invalid"); - ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); - assertEquals(0, config.getNumberOfPlatformThreads()); - verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN)); - assertEquals(false, config.isMultiConcurrent()); + assertThrows(NumberFormatException.class, () -> new ConcurrencyConfig(lambdaLogger, envReader)); + verify(lambdaLogger).log(contains(exitingRuntimeString), eq(LogLevel.ERROR)); } @Test @@ -74,6 +93,7 @@ void testGetConcurrencyConfigMessage() { ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); String expectedMessage = "Starting 4 concurrent function handler threads."; + verifyNoInteractions(lambdaLogger); assertEquals(expectedMessage, config.getConcurrencyConfigMessage()); assertEquals(true, config.isMultiConcurrent()); } @@ -81,6 +101,7 @@ void testGetConcurrencyConfigMessage() { @Test void testGetConcurrencyConfigWithNoConcurrency() { ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + verifyNoInteractions(lambdaLogger); assertEquals(0, config.getNumberOfPlatformThreads()); assertEquals(false, config.isMultiConcurrent()); } From 8fe3580bbb419c3adfc6de99abe5fd27fdbf8720 Mon Sep 17 00:00:00 2001 From: Astraea Quinn Sinclair Date: Tue, 15 Jul 2025 09:08:43 +0100 Subject: [PATCH 11/26] =?UTF-8?q?Version=20bump:=20serialization=201.1.5?= =?UTF-8?q?=E2=86=921.1.6,=20events=203.16.0=E2=86=923.16.1,=20update=20de?= =?UTF-8?q?pendencies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bump aws-lambda-java-serialization from 1.1.5 to 1.1.6 - Bump aws-lambda-java-events from 3.16.0 to 3.16.1 - Update aws-lambda-java-tests dependencies to use new versions - Update aws-lambda-java-runtime-interface-client serialization dependency 1.1.2→1.1.6 - Update aws-lambda-java-events-sdk-transformer events dependency 3.11.2→3.16.1 --- aws-lambda-java-events/pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index 8799966be..925273e9b 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.16.1 jar AWS Lambda Java Events Library diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 6589ca742..da07133c1 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -45,7 +45,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.16.1 org.junit.jupiter From 428d1255901ba2280ba533bcc90b98f6c46611a8 Mon Sep 17 00:00:00 2001 From: Astraea Quinn Sinclair Date: Tue, 15 Jul 2025 09:26:46 +0100 Subject: [PATCH 12/26] Fix runtime interface client workflows to use local serialization dependency Add local build step for aws-lambda-java-serialization before building runtime interface client. ## Why This Fix is Needed The runtime interface client depends on aws-lambda-java-serialization version 1.1.6, but this version doesn't exist in Maven Central yet. By building and installing the serialization package locally first, we ensure: 1. The correct version (1.1.6) is available in the local Maven repository 2. The runtime interface client build won't fail looking for a non-existent version on Maven Central 3. The workflow tests the actual code changes together ## Changes Made - runtime-interface-client_merge_to_main.yml: Added local serialization build step - runtime-interface-client_pr.yml: Added local serialization build step to both smoke-test and build jobs This ensures CI/CD pipeline works correctly with the new dependency versions. --- .../workflows/runtime-interface-client_merge_to_main.yml | 4 ++++ .github/workflows/runtime-interface-client_pr.yml | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index e07b191e1..88f8afde2 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -47,6 +47,10 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} + - name: Build and install serialization dependency locally + working-directory: ./aws-lambda-java-serialization + run: mvn clean install -DskipTests + - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client run: make build diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index cbc5b5ab1..d1821d2ce 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -31,6 +31,10 @@ jobs: working-directory: ./aws-lambda-java-serialization run: mvn clean install -DskipTests + - name: Build and install serialization dependency locally + working-directory: ./aws-lambda-java-serialization + run: mvn clean install -DskipTests + - name: Runtime Interface Client smoke tests - Run 'pr' target working-directory: ./aws-lambda-java-runtime-interface-client run: make pr @@ -59,6 +63,10 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} + - name: Build and install serialization dependency locally + working-directory: ./aws-lambda-java-serialization + run: mvn clean install -DskipTests + - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client run: make build From 477c0805ed9247d9c6f7abc567d68f6693ce7826 Mon Sep 17 00:00:00 2001 From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:12:48 +0100 Subject: [PATCH 13/26] Update ric pom.xml --- aws-lambda-java-runtime-interface-client/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 59f33145e..7a0d0264a 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.2 + 2.8.3 jar AWS Lambda Java Runtime Interface Client From 44cd05e502c4717400a398a4a6b9276044588b6c Mon Sep 17 00:00:00 2001 From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com> Date: Thu, 17 Jul 2025 13:41:48 +0100 Subject: [PATCH 14/26] Update runtime-interface-client_merge_to_main.yml --- .github/workflows/runtime-interface-client_merge_to_main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index 88f8afde2..94294501c 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -49,7 +49,7 @@ jobs: - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization - run: mvn clean install -DskipTests + run: mvn clean install - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client From ba4ef1c0862e5997ac9764bf07a7e108d11bece3 Mon Sep 17 00:00:00 2001 From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com> Date: Thu, 17 Jul 2025 14:02:05 +0100 Subject: [PATCH 15/26] Update runtime-interface-client_pr.yml --- .github/workflows/runtime-interface-client_pr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index d1821d2ce..b5631d2b4 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -33,7 +33,7 @@ jobs: - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization - run: mvn clean install -DskipTests + run: mvn clean install - name: Runtime Interface Client smoke tests - Run 'pr' target working-directory: ./aws-lambda-java-runtime-interface-client @@ -65,7 +65,7 @@ jobs: - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization - run: mvn clean install -DskipTests + run: mvn clean install - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client From 3ac07d05773a299bfc10faa0dd0ce3e854f07af4 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Fri, 25 Jul 2025 18:06:33 +0100 Subject: [PATCH 16/26] Ensure EventHandlerLoader Thread Safety. (#95) * Make handler response buffers thread safe. * Add multiconcurrency tests * ThreadLocal instead of Allocating new buffers every invoke. * Thread local log4jContextPutMethod * Fix indentations * Add CountDownLatch to ensure all calls are done simultaneously. --------- Co-authored-by: Mohammed Ehab --- .../api/client/EventHandlerLoader.java | 37 ++++----- .../api/client/EventHandlerLoaderTest.java | 79 ++++++++++++++++++- 2 files changed, 96 insertions(+), 20 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java index af146fd93..afb3e3df7 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java @@ -57,10 +57,10 @@ private enum Platform { UNKNOWN } - private static volatile PojoSerializer contextSerializer; - private static volatile PojoSerializer cognitoSerializer; + private static volatile ThreadLocal> contextSerializer = new ThreadLocal<>(); + private static volatile ThreadLocal> cognitoSerializer = new ThreadLocal<>(); - private static final EnumMap>> typeCache = new EnumMap<>(Platform.class); + private static final ThreadLocal>>> typeCache = ThreadLocal.withInitial(() -> new EnumMap<>(Platform.class)); private static final Comparator methodPriority = new Comparator() { public int compare(Method lhs, Method rhs) { @@ -127,10 +127,11 @@ private static PojoSerializer getSerializer(Platform platform, Type type } private static PojoSerializer getSerializerCached(Platform platform, Type type) { - Map> cache = typeCache.get(platform); + EnumMap>> threadTypeCache = typeCache.get(); + Map> cache = threadTypeCache.get(platform); if (cache == null) { cache = new HashMap<>(); - typeCache.put(platform, cache); + threadTypeCache.put(platform, cache); } PojoSerializer serializer = cache.get(type); @@ -143,17 +144,17 @@ private static PojoSerializer getSerializerCached(Platform platform, Typ } private static PojoSerializer getContextSerializer() { - if (contextSerializer == null) { - contextSerializer = GsonFactory.getInstance().getSerializer(LambdaClientContext.class); + if (contextSerializer.get() == null) { + contextSerializer.set(GsonFactory.getInstance().getSerializer(LambdaClientContext.class)); } - return contextSerializer; + return contextSerializer.get(); } private static PojoSerializer getCognitoSerializer() { - if (cognitoSerializer == null) { - cognitoSerializer = GsonFactory.getInstance().getSerializer(LambdaCognitoIdentity.class); + if (cognitoSerializer.get() == null) { + cognitoSerializer.set(GsonFactory.getInstance().getSerializer(LambdaCognitoIdentity.class)); } - return cognitoSerializer; + return cognitoSerializer.get(); } @@ -527,15 +528,14 @@ private static LambdaRequestHandler wrapPojoHandler(RequestHandler instance, Typ private static LambdaRequestHandler wrapRequestStreamHandler(final RequestStreamHandler handler) { return new LambdaRequestHandler() { - private final ByteArrayOutputStream output = new ByteArrayOutputStream(1024); - private Functions.V2 log4jContextPutMethod = null; + private final ThreadLocal outputBuffers = ThreadLocal.withInitial(() -> new ByteArrayOutputStream(1024)); + private ThreadLocal> log4jContextPutMethod = new ThreadLocal<>(); - private void safeAddRequestIdToLog4j(String log4jContextClassName, - InvocationRequest request, Class contextMapValueClass) { + private void safeAddRequestIdToLog4j(String log4jContextClassName, InvocationRequest request, Class contextMapValueClass) { try { Class log4jContextClass = ReflectUtil.loadClass(AWSLambda.getCustomerClassLoader(), log4jContextClassName); - log4jContextPutMethod = ReflectUtil.loadStaticV2(log4jContextClass, "put", false, String.class, contextMapValueClass); - log4jContextPutMethod.call("AWSRequestId", request.getId()); + log4jContextPutMethod.set(ReflectUtil.loadStaticV2(log4jContextClass, "put", false, String.class, contextMapValueClass)); + log4jContextPutMethod.get().call("AWSRequestId", request.getId()); } catch (Exception e) { // nothing to do here } @@ -558,6 +558,7 @@ private void safeAddContextToLambdaLogger(LambdaContext context) { } public ByteArrayOutputStream call(InvocationRequest request) throws Error, Exception { + ByteArrayOutputStream output = outputBuffers.get(); output.reset(); LambdaCognitoIdentity cognitoIdentity = null; @@ -591,7 +592,7 @@ public ByteArrayOutputStream call(InvocationRequest request) throws Error, Excep safeAddRequestIdToLog4j("org.apache.log4j.MDC", request, Object.class); safeAddRequestIdToLog4j("org.apache.logging.log4j.ThreadContext", request, String.class); // if put method not assigned in either call to safeAddRequestIdtoLog4j then log4jContextPutMethod = null - if (log4jContextPutMethod == null) { + if (log4jContextPutMethod.get() == null) { System.err.println("Customer using log4j appender but unable to load either " + "org.apache.log4j.MDC or org.apache.logging.log4j.ThreadContext. " + "Customer cannot see RequestId in log4j log lines."); diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoaderTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoaderTest.java index 76e6f0249..aae2f1afe 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoaderTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoaderTest.java @@ -4,8 +4,16 @@ import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; class EventHandlerLoaderTest { @@ -37,7 +45,6 @@ void PojoHandlerTest_oneParamEvent() throws Exception { assertSuccessfulInvocation(lambdaRequestHandler); } - @Test void PojoHandlerTest_oneParamContext() throws Exception { String handler = "test.lambda.handlers.POJOHanlderImpl::oneParamHandler_context"; @@ -74,4 +81,72 @@ private static InvocationRequest getTestInvocationRequest() { invocationRequest.setXrayTraceId("traceId"); return invocationRequest; } -} \ No newline at end of file + + // Multithreaded test methods + + @Test + void RequestHandlerTest_Multithreaded() throws Exception { + testHandlerConcurrency("test.lambda.handlers.RequestHandlerImpl"); + } + + @Test + void RequestStreamHandlerTest_Multithreaded() throws Exception { + testHandlerConcurrency("test.lambda.handlers.RequestStreamHandlerImpl"); + } + + @Test + void PojoHandlerTest_noParams_Multithreaded() throws Exception { + testHandlerConcurrency("test.lambda.handlers.POJOHanlderImpl::noParamsHandler"); + } + + @Test + void PojoHandlerTest_oneParamEvent_Multithreaded() throws Exception { + testHandlerConcurrency("test.lambda.handlers.POJOHanlderImpl::oneParamHandler_event"); + } + + @Test + void PojoHandlerTest_oneParamContext_Multithreaded() throws Exception { + testHandlerConcurrency("test.lambda.handlers.POJOHanlderImpl::oneParamHandler_context"); + } + + @Test + void PojoHandlerTest_twoParams_Multithreaded() throws Exception { + testHandlerConcurrency("test.lambda.handlers.POJOHanlderImpl::twoParamsHandler"); + } + + private void testHandlerConcurrency(String handlerName) throws Exception { + // Create one handler instance + LambdaRequestHandler handler = getLambdaRequestHandler(handlerName); + + int threadCount = 10; + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + List> futures = new ArrayList<>(); + CountDownLatch startLatch = new CountDownLatch(1); + + try { + for (int i = 0; i < threadCount; i++) { + futures.add(executor.submit(() -> { + try { + InvocationRequest request = getTestInvocationRequest(); + startLatch.await(); + ByteArrayOutputStream result = handler.call(request); + return result.toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } + })); + } + + // Release all threads simultaneously and Verify all invocations return the expected result + startLatch.countDown(); + + for (Future future : futures) { + String result = future.get(5, TimeUnit.SECONDS); + assertEquals("\"success\"", result); + } + } finally { + executor.shutdown(); + assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS)); + } + } +} From 1a7af454aa66eb4ea095c65de1f977c8f0dc26a0 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Wed, 3 Sep 2025 19:43:27 +0100 Subject: [PATCH 17/26] Make Trace ID accessible through Context (#101) * Make Trace ID accessible through Context * Change Constructor --------- Co-authored-by: Mohammed Ehab --- .github/workflows/runtime-interface-client_pr.yml | 8 ++++---- aws-lambda-java-core/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-core/pom.xml | 2 +- .../com/amazonaws/services/lambda/runtime/Context.java | 10 ++++++++++ aws-lambda-java-runtime-interface-client/pom.xml | 2 +- .../lambda/runtime/api/client/EventHandlerLoader.java | 1 + .../lambda/runtime/api/client/api/LambdaContext.java | 7 +++++++ .../runtime/api/client/api/LambdaContextTest.java | 3 ++- .../api/client/logging/AbstractLambdaLoggerTest.java | 2 +- .../api/client/logging/JsonLogFormatterTest.java | 2 ++ 10 files changed, 33 insertions(+), 8 deletions(-) diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index b5631d2b4..5819218c9 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -27,10 +27,6 @@ jobs: working-directory: ./aws-lambda-java-core run: mvn clean install - - name: Build and install serialization dependency locally - working-directory: ./aws-lambda-java-serialization - run: mvn clean install -DskipTests - - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization run: mvn clean install @@ -62,6 +58,10 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} + + - name: Build and install core dependency locally + working-directory: ./aws-lambda-java-core + run: mvn clean install - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization diff --git a/aws-lambda-java-core/RELEASE.CHANGELOG.md b/aws-lambda-java-core/RELEASE.CHANGELOG.md index 527e7dd46..aebc8ecd9 100644 --- a/aws-lambda-java-core/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-core/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### September 3, 2025 +`1.4.0` +- Getter support for x-ray trace ID through the Context object + ### May 26, 2025 `1.3.0` - Adding support for multi tenancy ([#545](https://github.com/aws/aws-lambda-java-libs/pull/545)) diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index 77245c9be..cca9d0cdf 100644 --- a/aws-lambda-java-core/pom.xml +++ b/aws-lambda-java-core/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-core - 1.3.0 + 1.4.0 jar AWS Lambda Java Core Library diff --git a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java index 152765df1..ed9311a11 100644 --- a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java +++ b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java @@ -109,4 +109,14 @@ public interface Context { default String getTenantId() { return null; } + + /** + * + * Returns the X-Ray trace ID associated with the request. + * + * @return null by default + */ + default String getXrayTraceId() { + return null; + } } diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 7a0d0264a..189743054 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -61,7 +61,7 @@ com.amazonaws aws-lambda-java-core - 1.3.0 + 1.4.0 com.amazonaws diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java index afb3e3df7..f679c217c 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java @@ -583,6 +583,7 @@ public ByteArrayOutputStream call(InvocationRequest request) throws Error, Excep LambdaEnvironment.FUNCTION_VERSION, request.getInvokedFunctionArn(), request.getTenantId(), + request.getXrayTraceId(), clientContext ); diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java index bd1463db6..20b77262d 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java @@ -23,6 +23,7 @@ public class LambdaContext implements Context { private final CognitoIdentity cognitoIdentity; private final ClientContext clientContext; private final String tenantId; + private final String xrayTraceId; private final LambdaLogger logger; public LambdaContext( @@ -36,6 +37,7 @@ public LambdaContext( String functionVersion, String invokedFunctionArn, String tenantId, + String xrayTraceId, ClientContext clientContext ) { this.memoryLimit = memoryLimit; @@ -49,6 +51,7 @@ public LambdaContext( this.functionVersion = functionVersion; this.invokedFunctionArn = invokedFunctionArn; this.tenantId = tenantId; + this.xrayTraceId = xrayTraceId; this.logger = com.amazonaws.services.lambda.runtime.LambdaRuntime.getLogger(); } @@ -98,6 +101,10 @@ public String getTenantId() { return tenantId; } + public String getXrayTraceId() { + return xrayTraceId; + } + public LambdaLogger getLogger() { return logger; } diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java index 58880be43..f7da76198 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java @@ -19,6 +19,7 @@ public class LambdaContextTest { private static final LambdaClientContext CLIENT_CONTEXT = new LambdaClientContext(); public static final int MEMORY_LIMIT = 128; public static final String TENANT_ID = "tenant-id"; + public static final String X_RAY_TRACE_ID = "x-ray-trace-id"; @Test public void getRemainingTimeInMillis() { @@ -55,6 +56,6 @@ public void getRemainingTimeInMillis_Deadline() throws InterruptedException { private LambdaContext createContextWithDeadline(long deadlineTimeInMs) { return new LambdaContext(MEMORY_LIMIT, deadlineTimeInMs, REQUEST_ID, LOG_GROUP_NAME, LOG_STREAM_NAME, - FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, CLIENT_CONTEXT); + FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, X_RAY_TRACE_ID, CLIENT_CONTEXT); } } diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java index f97602e37..3a5ee8d5f 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java @@ -84,7 +84,7 @@ public void testMultiConcurrentLoggingWithoutLogLevelInJSON() { final int nThreads = 5; ExecutorService es = Executors.newFixedThreadPool(nThreads); for (int i = 0; i < nThreads; i++) { - es.submit(() -> logger.setLambdaContext(new LambdaContext(Integer.MAX_VALUE, Long.MAX_VALUE, reqIDPrefix + Thread.currentThread().getName(), "", "", "", null, "", "", "", null))); + es.submit(() -> logger.setLambdaContext(new LambdaContext(Integer.MAX_VALUE, Long.MAX_VALUE, reqIDPrefix + Thread.currentThread().getName(), "", "", "", null, "", "", "", null, null))); } final int nMessages = 100_000; diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java index 531e9ca94..91ce9d2a3 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java @@ -30,6 +30,7 @@ void testFormattingWithLambdaContext() { null, "function-arn", null, + null, null ); assertFormatsString("test log", LogLevel.WARN, context); @@ -48,6 +49,7 @@ void testFormattingWithTenantIdInLambdaContext() { null, "function-arn", "tenant-id", + "xray-trace-id", null ); assertFormatsString("test log", LogLevel.WARN, context); From e9c084ea084f26b5aed8bcae9d466b20dc7149ba Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Tue, 9 Sep 2025 11:46:22 +0100 Subject: [PATCH 18/26] Migrate to Deploying using Maven Central (#103) Co-authored-by: Mohammed Ehab --- aws-lambda-java-runtime-interface-client/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 189743054..c854fabcd 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.3 + 2.8.4 jar AWS Lambda Java Runtime Interface Client From 2250e99a2ed537a2a3dde63375944ce4b70e28ff Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:30:26 +0100 Subject: [PATCH 19/26] Fix performance Issue By Using Blocking Calls instead of Busy Waiting (#106) Co-authored-by: Mohammed Ehab --- .../services/lambda/runtime/api/client/AWSLambda.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java index 9c6dd78b1..10d595c7b 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java @@ -39,6 +39,7 @@ import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; /** * The entrypoint of this class is {@link AWSLambda#startRuntime}. It performs two main tasks: @@ -253,10 +254,10 @@ protected static void startRuntimeLoops(LambdaRequestHandler lambdaRequestHandle } } finally { platformThreadExecutor.shutdown(); - while (true) { - if (platformThreadExecutor.isTerminated()) { - break; - } + try { + platformThreadExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } } } else { From 085c9754f497b6ba3240bea3a8cef3286ba823b3 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:30:55 +0100 Subject: [PATCH 20/26] Log errorType and errorMessage from RAPID (#105) Co-authored-by: Mohammed Ehab --- .../src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp index 3897d37db..84a84b439 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp @@ -370,7 +370,7 @@ runtime::post_outcome runtime::do_post( if (!is_success(aws::http::response_code(http_response_code))) { logging::log_error( - LOG_TAG, "Failed to post handler success response. Http response code: %ld.", http_response_code); + LOG_TAG, "Failed to post handler success response. Http response code: %ld. %s", http_response_code, resp.get_body().c_str()); return aws::http::response_code(http_response_code); } From e61bc6342a6d731e8699f3e8e558e4664a1249a0 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:52:42 +0100 Subject: [PATCH 21/26] Version Bump RIC to 2.8.5 (#107) Co-authored-by: Mohammed Ehab --- README.md | 2 +- aws-lambda-java-runtime-interface-client/README.md | 4 ++-- .../RELEASE.CHANGELOG.md | 13 +++++++++++++ aws-lambda-java-runtime-interface-client/pom.xml | 2 +- .../test/integration/test-handler/pom.xml | 2 +- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 70d7c7316..609485625 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.2 + 2.8.5 ``` diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index e3c68fe8a..c63dcfeef 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.2 + 2.8.5 @@ -203,7 +203,7 @@ platform-specific JAR by setting the ``. com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.8.5 linux-x86_64 ``` diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index ff3d8bec5..a8c34dec8 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,16 @@ +### September 17, 2025 +`2.8.5` +- Log errorType and errorMessage from RAPID in C++ Client. +- Performance Upgrade for Multiconcurrency Mode. + +### September 9, 2025 +`2.8.4` +- Make Trace ID Accessible through Context Object. + +### July 19, 2025 +`2.8.3` +- Ensure EventHandlerLoader Thread Safety. + ### June 26, 2025 `2.8.2` - Allow AWS_LAMBDA_MAX_CONCURRENCY to be One. Crash the RIC if it is set to an un-parsable string to an integer or an out of bounds value. diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index c854fabcd..b77c38e00 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.4 + 2.8.5 jar AWS Lambda Java Runtime Interface Client diff --git a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml index 5304d44b9..35ea694ab 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml +++ b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml @@ -15,7 +15,7 @@ com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.8.5 From f93664f998fcf3c52787fae1dc2bceaf639c2f82 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:13:11 +0100 Subject: [PATCH 22/26] MultiConcurrent XRAY TraceID using utils-lite (#96) Co-authored-by: Mohammed Ehab --- .../pom.xml | 38 +-- .../lambda/runtime/api/client/AWSLambda.java | 11 +- .../runtime/api/client/AWSLambdaTest.java | 230 +++++++++++++++++- 3 files changed, 258 insertions(+), 21 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index b77c38e00..0d3e7fb04 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -1,6 +1,6 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client @@ -47,9 +47,9 @@ separately from the Runtime Interface Client functionality until we figure something else out. --> true - - - + + +