Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### September 2, 2026
`2.12.1`
- Emit a structured `runtime_worker_pool_initializing` DEBUG log event once during INIT in multi-concurrent (Lambda Managed Instances) mode, reporting the worker pool size (`workerCount`) and the maximum concurrency the execution environment supports (`executionEnvironmentMaxConcurrency`). Only visible when the function log level is DEBUG or lower; not emitted for standard on-demand functions.

### July 17, 2026
`2.12.0`
- Add `Lambda-Runtime-Invocation-Id` header support for cross-wiring protection. The RIC now echoes the invocation ID received from RAPID on `/next` back on `/response` and `/error`, enabling RAPID to detect and reject stale responses from timed-out invocations.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,11 @@ protected static void startRuntimeLoops(LambdaRequestHandler lambdaRequestHandle
if (concurrencyConfig.isMultiConcurrent()) {
lambdaLogger.log(concurrencyConfig.getConcurrencyConfigMessage(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.INFO : LogLevel.UNDEFINED);
ExecutorService platformThreadExecutor = Executors.newFixedThreadPool(concurrencyConfig.getNumberOfPlatformThreads());
lambdaLogger.logStructuredEvent(
new WorkerPoolInitializedEvent(
concurrencyConfig.getNumberOfPlatformThreads(),
concurrencyConfig.getNumberOfPlatformThreads()),
LogLevel.DEBUG);
try {
for (int i = 0; i < concurrencyConfig.getNumberOfPlatformThreads(); i++) {
startRuntimeLoopWithExecutor(lambdaRequestHandler, lambdaLogger, platformThreadExecutor, runtimeClient);
Expand Down Expand Up @@ -373,4 +378,15 @@ private static void logExceptionCloudWatch(LambdaContextLogger lambdaLogger, Exc
protected static URLClassLoader getCustomerClassLoader() {
return customerClassLoader;
}

static class WorkerPoolInitializedEvent {
final String event = "runtime_worker_pool_initializing";
final int workerCount;
final int executionEnvironmentMaxConcurrency;

WorkerPoolInitializedEvent(int workerCount, int executionEnvironmentMaxConcurrency) {
this.workerCount = workerCount;
this.executionEnvironmentMaxConcurrency = executionEnvironmentMaxConcurrency;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ public void log(byte[] message) {
this.log(message, LogLevel.UNDEFINED);
}

public void logStructuredEvent(Object event, LogLevel logLevel) {
if (logFiltering.isEnabled(logLevel)) {
this.logMessage(logFormatter.format(event, logLevel), logLevel);
}
}

public void setLambdaContext(LambdaContext lambdaContext) {
this.logFormatter.setLambdaContext(lambdaContext);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,22 @@ public class JsonLogFormatter implements LogFormatter {

@Override
public String format(String message, LogLevel logLevel) {
return serialize(createLogMessage(message, logLevel));
}

@Override
public String format(Object message, LogLevel logLevel) {
return serialize(createLogMessage(message, logLevel));
}

private String serialize(StructuredLogMessage msg) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
StructuredLogMessage msg = createLogMessage(message, logLevel);
serializer.toJson(msg, stream);
stream.write('\n');
return new String(stream.toByteArray(), StandardCharsets.UTF_8);
}

private StructuredLogMessage createLogMessage(String message, LogLevel logLevel) {
private StructuredLogMessage createLogMessage(Object message, LogLevel logLevel) {
StructuredLogMessage msg = new StructuredLogMessage();
msg.timestamp = dateFormatter.format(LocalDateTime.now());
msg.message = message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
public interface LogFormatter {
String format(String message, LogLevel logLevel);

default String format(Object message, LogLevel logLevel) {
return format(String.valueOf(message), logLevel);
}

default void setLambdaContext(LambdaContext context) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class StructuredLogMessage {
public String timestamp;
public String message;
Comment thread
vip-amzn marked this conversation as resolved.
public Object message;
public LogLevel level;
public String AWSRequestId;
public String tenantId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,40 @@ void testSequentialWithVirtualMachineErrorStopsLoop() throws Throwable {
assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get());
}

@Test
@Timeout(value = 1, unit = TimeUnit.MINUTES)
void testWorkerPoolInitializedEventEmittedOnceInMultiConcurrentMode() throws Throwable {
when(concurrencyConfig.isMultiConcurrent()).thenReturn(true);
when(concurrencyConfig.getNumberOfPlatformThreads()).thenReturn(4);

when(runtimeClient.nextInvocationWithExponentialBackoff(lambdaLogger))
.thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException);

AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient);

org.mockito.ArgumentCaptor<Object> eventCaptor = org.mockito.ArgumentCaptor.forClass(Object.class);
verify(lambdaLogger, times(1)).logStructuredEvent(eventCaptor.capture(), eq(LogLevel.DEBUG));

AWSLambda.WorkerPoolInitializedEvent event = (AWSLambda.WorkerPoolInitializedEvent) eventCaptor.getValue();
assertEquals("runtime_worker_pool_initializing", event.event);
assertEquals(4, event.workerCount);
assertEquals(4, event.executionEnvironmentMaxConcurrency);
}

@Test
@Timeout(value = 1, unit = TimeUnit.MINUTES)
void testWorkerPoolInitializedEventNotEmittedInSequentialMode() throws Throwable {
when(concurrencyConfig.isMultiConcurrent()).thenReturn(false);

InvocationRequest fatalRequest = mock(InvocationRequest.class);
when(fatalRequest.getId()).thenThrow(UserFault.makeUserFault(new IOError(new Throwable()), true)).thenReturn("fatal");
when(runtimeClient.nextInvocation()).thenReturn(fatalRequest);

AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient);

verify(lambdaLogger, never()).logStructuredEvent(any(), any());
}

@Test
@Timeout(value = 1, unit = TimeUnit.MINUTES)
void testInvocationIdIsPassedToReportSuccess() throws Throwable {
Expand Down
Loading