Skip to content

Issue148#149

Merged
stevehu merged 5 commits intomasterfrom
issue148
Jan 28, 2026
Merged

Issue148#149
stevehu merged 5 commits intomasterfrom
issue148

Conversation

@stevehu
Copy link
Contributor

@stevehu stevehu commented Jan 27, 2026

No description provided.

@stevehu stevehu requested a review from Copilot January 27, 2026 22:52
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR appears to address configuration handling and reloading for the Lambda invoker, with some cleanup of schema formatting and handler behavior.

Changes:

  • Fixes formatting of the lambda-invoker JSON schema file.
  • Refactors LambdaInvokerConfig to cache the underlying mapped configuration, introduce a singleton-style load method, and register the module via ModuleRegistry.
  • Updates LambdaFunctionHandler to use instance fields for config/client, factor out client initialization, fix the API call attempt timeout units, and add dynamic config reload behavior inside handleRequest.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 4 comments.

File Description
lambda-invoker/src/main/resources/config/lambda-invoker-schema.json Minor formatting change to the closing brace of the lambda-invoker JSON schema.
lambda-invoker/src/main/java/com/networknt/aws/lambda/LambdaInvokerConfig.java Introduces a cached mappedConfig map and a singleton-style load(String) method with ModuleRegistry registration for the lambda-invoker config.
lambda-invoker/src/main/java/com/networknt/aws/lambda/LambdaFunctionHandler.java Refactors handler to use instance config/client, centralizes client creation in initClient, corrects timeout units, adjusts logging, and adds dynamic reload of LambdaInvokerConfig and client inside handleRequest.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 96 to 105
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
LambdaInvokerConfig newConfig = LambdaInvokerConfig.load();
if(newConfig != config) {
synchronized (this) {
newConfig = LambdaInvokerConfig.load();
if(newConfig != config) {
config = newConfig;
client = initClient(config);
if(config.isMetricsInjection()) {
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the configuration changes, handleRequest replaces the client instance by calling initClient(config) but never closes the previous LambdaAsyncClient, which manages its own resources and threads. To avoid leaking connections/threads across reloads, close the existing client (if non-null) before replacing it, or reuse a single client instance per handler and update only the configuration-dependent state that actually needs to change.

Copilot uses AI. Check for mistakes.
Comment on lines 96 to 116
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
LambdaInvokerConfig newConfig = LambdaInvokerConfig.load();
if(newConfig != config) {
synchronized (this) {
newConfig = LambdaInvokerConfig.load();
if(newConfig != config) {
config = newConfig;
client = initClient(config);
if(config.isMetricsInjection()) {
// get the metrics handler from the handler chain for metrics registration. If we cannot get the
// metrics handler, then an error message will be logged.
Map<String, HttpHandler> handlers = Handler.getHandlers();
metricsHandler = (AbstractMetricsHandler) handlers.get(MetricsConfig.CONFIG_NAME);
if(metricsHandler == null) {
logger.error("An instance of MetricsHandler is not configured in the handler.yml.");
}
}
}
}
}
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new dynamic reload path in handleRequest (reloading LambdaInvokerConfig and reinitializing the LambdaAsyncClient) is not covered by tests in LambdaFunctionHandlerTest, which currently only exercises request-event serialization. Given that this logic affects live request handling and resource management, consider adding a unit or integration test that exercises a config change and verifies that the handler uses the updated configuration and client without errors.

Copilot uses AI. Check for mistakes.
Comment on lines 168 to +182
public static LambdaInvokerConfig load(String configName) {
if (CONFIG_NAME.equals(configName)) {
Map<String, Object> mappedConfig = Config.getInstance().getJsonMapConfig(configName);
if (instance != null && instance.getMappedConfig() == mappedConfig) {
return instance;
}
synchronized (LambdaInvokerConfig.class) {
mappedConfig = Config.getInstance().getJsonMapConfig(configName);
if (instance != null && instance.getMappedConfig() == mappedConfig) {
return instance;
}
instance = new LambdaInvokerConfig(configName);
ModuleRegistry.registerModule(CONFIG_NAME, LambdaInvokerConfig.class.getName(), Config.getNoneDecryptedInstance().getJsonMapConfigNoCache(CONFIG_NAME), null);
return instance;
}
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lazy singleton implementation here publishes instance from within a synchronized block but reads it (and its non-final fields like region, functions, etc.) without any synchronization or volatile, which violates Java's safe-publication rules and can expose other threads to a partially-constructed LambdaInvokerConfig instance. To make this thread-safe, either declare instance as volatile (or use an AtomicReference) and keep the double-checked pattern, or drop the unsynchronized fast path and always access instance behind the same class-level lock.

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +48
LambdaInvokerConfig config = LambdaInvokerConfig.load();
this.client = initClient(config);
if(config.isMetricsInjection()) {
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the constructor, the local variable LambdaInvokerConfig config = LambdaInvokerConfig.load(); shadows the instance field config, so the field remains null until the first handleRequest call triggers the reload block. This is confusing and unnecessarily causes the client to be reinitialized on the first request; consider assigning the loaded config to the instance field (e.g., this.config = LambdaInvokerConfig.load();) and reusing it to avoid shadowing and redundant initialization.

Suggested change
LambdaInvokerConfig config = LambdaInvokerConfig.load();
this.client = initClient(config);
if(config.isMetricsInjection()) {
this.config = LambdaInvokerConfig.load();
this.client = initClient(this.config);
if (this.config.isMetricsInjection()) {

Copilot uses AI. Check for mistakes.
@stevehu stevehu merged commit ff9145a into master Jan 28, 2026
1 check was pending
@stevehu stevehu deleted the issue148 branch January 28, 2026 02:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant