Skip to content

Commit 8860ad4

Browse files
authored
Merge branch 'awslabs:master' into with-metric
2 parents 6e3133d + 2536b7f commit 8860ad4

File tree

32 files changed

+600
-59
lines changed

32 files changed

+600
-59
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ Desktop.ini
8282
######################
8383
/bin/
8484
/deploy/
85+
/dist/
86+
/site/
8587

8688
######################
8789
# Logs

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,23 @@ This project follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) fo
88

99
## [Unreleased]
1010

11+
## [1.13.0] - 2022-12-14
12+
13+
### Added
14+
15+
* Feature: Idempotency - Handle Lambda timeout scenarios for INPROGRESS records (#933) by @jeromevdl
16+
17+
### Bug Fixes
18+
19+
* Fix: Envelope is not taken into account with built-in types (#960) by @jeromevdl
20+
* Fix: Code suggestion from CodeGuru (#984) by @kozub
21+
* Fix: Compilation warning with SqsLargeMessageAspect on gradle (#998) by @jeromevdl
22+
* Fix: Log message processing exceptions as occur (#1011) by @nem0-97
23+
24+
### Documentation
25+
26+
* Docs: Add missing grammar article (#976) by @fsmiamoto
27+
1128
## [1.12.3] - 2022-07-12
1229

1330
### Maintenance

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,17 @@ Powertools is available in Maven Central. You can use your favourite dependency
1717
<dependency>
1818
<groupId>software.amazon.lambda</groupId>
1919
<artifactId>powertools-tracing</artifactId>
20-
<version>1.12.3</version>
20+
<version>1.13.0</version>
2121
</dependency>
2222
<dependency>
2323
<groupId>software.amazon.lambda</groupId>
2424
<artifactId>powertools-logging</artifactId>
25-
<version>1.12.3</version>
25+
<version>1.13.0</version>
2626
</dependency>
2727
<dependency>
2828
<groupId>software.amazon.lambda</groupId>
2929
<artifactId>powertools-metrics</artifactId>
30-
<version>1.12.3</version>
30+
<version>1.13.0</version>
3131
</dependency>
3232
...
3333
</dependencies>

docs/utilities/idempotency.md

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,19 +355,141 @@ Imagine the function executes successfully, but the client never receives the re
355355

356356
This sequence diagram shows an example flow of what happens in the payment scenario:
357357

358-
![Idempotent sequence](../media/idempotent_sequence.png)
358+
<center>
359+
```mermaid
360+
sequenceDiagram
361+
participant Client
362+
participant Lambda
363+
participant Persistence Layer
364+
alt initial request
365+
Client->>Lambda: Invoke (event)
366+
Lambda->>Persistence Layer: Get or set (id=event.search(payload))
367+
activate Persistence Layer
368+
Note right of Persistence Layer: Locked to prevent concurrent<br/>invocations with <br/> the same payload.
369+
Lambda-->>Lambda: Call handler (event)
370+
Lambda->>Persistence Layer: Update record with result
371+
deactivate Persistence Layer
372+
Persistence Layer-->>Persistence Layer: Update record with result
373+
Lambda-->>Client: Response sent to client
374+
else retried request
375+
Client->>Lambda: Invoke (event)
376+
Lambda->>Persistence Layer: Get or set (id=event.search(payload))
377+
Persistence Layer-->>Lambda: Already exists in persistence layer. Return result
378+
Lambda-->>Client: Response sent to client
379+
end
380+
```
381+
<i>Idempotent sequence</i>
382+
</center>
359383

360384
The client was successful in receiving the result after the retry. Since the Lambda handler was only executed once, our customer hasn't been charged twice.
361385

362386
!!! note
363387
Bear in mind that the entire Lambda handler is treated as a single idempotent operation. If your Lambda handler can cause multiple side effects, consider splitting it into separate functions.
364388

389+
#### Lambda timeouts
390+
391+
This is automatically done when you annotate your Lambda handler with [@Idempotent annotation](#idempotent-annotation).
392+
393+
To prevent against extended failed retries when a [Lambda function times out](https://aws.amazon.com/premiumsupport/knowledge-center/lambda-verify-invocation-timeouts/), Powertools calculates and includes the remaining invocation available time as part of the idempotency record.
394+
395+
!!! example
396+
If a second invocation happens **after** this timestamp, and the record is marked as `INPROGRESS`, we will execute the invocation again as if it was in the `EXPIRED` state.
397+
This means that if an invocation expired during execution, it will be quickly executed again on the next retry.
398+
399+
!!! important
400+
If you are using the [@Idempotent annotation on another method](#idempotent-annotation-on-another-method) to guard isolated parts of your code, you must use `registerLambdaContext` method available in the `Idempotency` object to benefit from this protection.
401+
402+
Here is an example on how you register the Lambda context in your handler:
403+
404+
```java hl_lines="13-19" title="Registering the Lambda context"
405+
public class PaymentHandler implements RequestHandler<SQSEvent, List<String>> {
406+
407+
public PaymentHandler() {
408+
Idempotency.config()
409+
.withPersistenceStore(
410+
DynamoDBPersistenceStore.builder()
411+
.withTableName(System.getenv("IDEMPOTENCY_TABLE"))
412+
.build())
413+
.configure();
414+
}
415+
416+
@Override
417+
public List<String> handleRequest(SQSEvent sqsEvent, Context context) {
418+
Idempotency.registerLambdaContext(context);
419+
return sqsEvent.getRecords().stream().map(record -> process(record.getMessageId(), record.getBody())).collect(Collectors.toList());
420+
}
421+
422+
@Idempotent
423+
private String process(String messageId, @IdempotencyKey String messageBody) {
424+
logger.info("Processing messageId: {}", messageId);
425+
PaymentRequest request = extractDataFrom(messageBody).as(PaymentRequest.class);
426+
return paymentService.process(request);
427+
}
428+
429+
}
430+
```
431+
432+
#### Lambda timeout sequence diagram
433+
434+
This sequence diagram shows an example flow of what happens if a Lambda function times out:
435+
436+
<center>
437+
```mermaid
438+
sequenceDiagram
439+
participant Client
440+
participant Lambda
441+
participant Persistence Layer
442+
alt initial request
443+
Client->>Lambda: Invoke (event)
444+
Lambda->>Persistence Layer: Get or set (id=event.search(payload))
445+
activate Persistence Layer
446+
Note right of Persistence Layer: Locked to prevent concurrent<br/>invocations with <br/> the same payload.
447+
Note over Lambda: Time out
448+
Lambda--xLambda: Call handler (event)
449+
Lambda-->>Client: Return error response
450+
deactivate Persistence Layer
451+
else concurrent request before timeout
452+
Client->>Lambda: Invoke (event)
453+
Lambda->>Persistence Layer: Get or set (id=event.search(payload))
454+
Persistence Layer-->>Lambda: Request already INPROGRESS
455+
Lambda--xClient: Return IdempotencyAlreadyInProgressError
456+
else retry after Lambda timeout
457+
Client->>Lambda: Invoke (event)
458+
Lambda->>Persistence Layer: Get or set (id=event.search(payload))
459+
activate Persistence Layer
460+
Note right of Persistence Layer: Locked to prevent concurrent<br/>invocations with <br/> the same payload.
461+
Lambda-->>Lambda: Call handler (event)
462+
Lambda->>Persistence Layer: Update record with result
463+
deactivate Persistence Layer
464+
Persistence Layer-->>Persistence Layer: Update record with result
465+
Lambda-->>Client: Response sent to client
466+
end
467+
```
468+
<i>Idempotent sequence for Lambda timeouts</i>
469+
</center>
470+
365471
### Handling exceptions
366472

367473
If you are using the `@Idempotent` annotation on your Lambda handler or any other method, any unhandled exceptions that are thrown during the code execution will cause **the record in the persistence layer to be deleted**.
368474
This means that new invocations will execute your code again despite having the same payload. If you don't want the record to be deleted, you need to catch exceptions within the idempotent function and return a successful response.
369475

370-
![Idempotent sequence exception](../media/idempotent_sequence_exception.png)
476+
<center>
477+
```mermaid
478+
sequenceDiagram
479+
participant Client
480+
participant Lambda
481+
participant Persistence Layer
482+
Client->>Lambda: Invoke (event)
483+
Lambda->>Persistence Layer: Get or set (id=event.search(payload))
484+
activate Persistence Layer
485+
Note right of Persistence Layer: Locked during this time. Prevents multiple<br/>Lambda invocations with the same<br/>payload running concurrently.
486+
Lambda--xLambda: Call handler (event).<br/>Raises exception
487+
Lambda->>Persistence Layer: Delete record (id=event.search(payload))
488+
deactivate Persistence Layer
489+
Lambda-->>Client: Return error response
490+
```
491+
<i>Idempotent sequence exception</i>
492+
</center>
371493

372494
If an Exception is raised _outside_ the scope of a decorated method and after your method has been called, the persistent record will not be affected. In this case, idempotency will be maintained for your decorated function. Example:
373495

mkdocs.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ markdown_extensions:
5656
- pymdownx.snippets:
5757
base_path: '.'
5858
check_paths: true
59+
- pymdownx.superfences:
60+
custom_fences:
61+
- name: mermaid
62+
class: mermaid
63+
format: !!python/name:pymdownx.superfences.fence_code_format
5964
- meta
6065
- toc:
6166
permalink: true
@@ -76,7 +81,7 @@ extra_javascript:
7681

7782
extra:
7883
powertools:
79-
version: 1.12.3
84+
version: 1.13.0
8085

8186
repo_url: https://github.com/awslabs/aws-lambda-powertools-java
8287
edit_uri: edit/master/docs

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>software.amazon.lambda</groupId>
88
<artifactId>powertools-parent</artifactId>
9-
<version>1.12.3</version>
9+
<version>1.13.0</version>
1010
<packaging>pom</packaging>
1111

1212
<name>AWS Lambda Powertools for Java library Parent</name>
@@ -58,7 +58,7 @@
5858
<log4j.version>2.19.0</log4j.version>
5959
<jackson.version>2.14.1</jackson.version>
6060
<aspectj.version>1.9.7</aspectj.version>
61-
<aws.sdk.version>2.18.24</aws.sdk.version>
61+
<aws.sdk.version>2.18.37</aws.sdk.version>
6262
<aws.xray.recorder.version>2.13.0</aws.xray.recorder.version>
6363
<payloadoffloading-common.version>2.1.2</payloadoffloading-common.version>
6464
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

powertools-cloudformation/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<parent>
1111
<artifactId>powertools-parent</artifactId>
1212
<groupId>software.amazon.lambda</groupId>
13-
<version>1.12.3</version>
13+
<version>1.13.0</version>
1414
</parent>
1515

1616
<name>AWS Lambda Powertools for Java library Cloudformation</name>

powertools-core/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<parent>
1111
<artifactId>powertools-parent</artifactId>
1212
<groupId>software.amazon.lambda</groupId>
13-
<version>1.12.3</version>
13+
<version>1.13.0</version>
1414
</parent>
1515

1616
<name>AWS Lambda Powertools for Java library Core</name>

powertools-idempotency/pom.xml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>software.amazon.lambda</groupId>
99
<artifactId>powertools-parent</artifactId>
10-
<version>1.12.3</version>
10+
<version>1.13.0</version>
1111
</parent>
1212

1313
<artifactId>powertools-idempotency</artifactId>
@@ -91,7 +91,7 @@
9191
<dependency>
9292
<groupId>org.junit-pioneer</groupId>
9393
<artifactId>junit-pioneer</artifactId>
94-
<version>1.9.0</version>
94+
<version>1.9.1</version>
9595
<scope>test</scope>
9696
</dependency>
9797
<dependency>
@@ -122,6 +122,7 @@
122122
<dependency>
123123
<groupId>com.amazonaws</groupId>
124124
<artifactId>aws-lambda-java-tests</artifactId>
125+
<scope>test</scope>
125126
</dependency>
126127
<dependency>
127128
<groupId>com.amazonaws</groupId>

powertools-idempotency/src/main/java/software/amazon/lambda/powertools/idempotency/Idempotency.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ public static Idempotency getInstance() {
6565
return Holder.instance;
6666
}
6767

68+
/**
69+
* Can be used in a method which is not the handler to capture the Lambda context,
70+
* to calculate the remaining time before the invocation times out.
71+
*
72+
* @param lambdaContext
73+
*/
74+
public static void registerLambdaContext(Context lambdaContext) {
75+
getInstance().getConfig().setLambdaContext(lambdaContext);
76+
}
77+
6878
/**
6979
* Acts like a builder that can be used to configure {@link Idempotency}
7080
*

powertools-idempotency/src/main/java/software/amazon/lambda/powertools/idempotency/IdempotencyConfig.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414
package software.amazon.lambda.powertools.idempotency;
1515

16+
import com.amazonaws.services.lambda.runtime.Context;
1617
import software.amazon.lambda.powertools.idempotency.internal.cache.LRUCache;
1718

1819
import java.time.Duration;
@@ -28,6 +29,7 @@ public class IdempotencyConfig {
2829
private final String payloadValidationJMESPath;
2930
private final boolean throwOnNoIdempotencyKey;
3031
private final String hashFunction;
32+
private Context lambdaContext;
3133

3234
private IdempotencyConfig(String eventKeyJMESPath, String payloadValidationJMESPath, boolean throwOnNoIdempotencyKey, boolean useLocalCache, int localCacheMaxItems, long expirationInSeconds, String hashFunction) {
3335
this.localCacheMaxItems = localCacheMaxItems;
@@ -71,12 +73,20 @@ public String getHashFunction() {
7173
/**
7274
* Create a builder that can be used to configure and create a {@link IdempotencyConfig}.
7375
*
74-
* @return a new instance of {@link IdempotencyConfig.Builder}
76+
* @return a new instance of {@link Builder}
7577
*/
7678
public static Builder builder() {
7779
return new Builder();
7880
}
7981

82+
public void setLambdaContext(Context lambdaContext) {
83+
this.lambdaContext = lambdaContext;
84+
}
85+
86+
public Context getLambdaContext() {
87+
return lambdaContext;
88+
}
89+
8090
public static class Builder {
8191

8292
private int localCacheMaxItems = 256;

powertools-idempotency/src/main/java/software/amazon/lambda/powertools/idempotency/exceptions/IdempotencyInconsistentStateException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
/**
1717
* IdempotencyInconsistentStateException can happen under rare but expected cases
18-
* when persistent state changes in the small-time between put & get requests.
18+
* when persistent state changes in the small-time between put &amp; get requests.
1919
*/
2020
public class IdempotencyInconsistentStateException extends RuntimeException {
2121
private static final long serialVersionUID = -4293951999802300672L;

0 commit comments

Comments
 (0)