Skip to content

Bug: Idempotency middleware doesn't work for functions without return value #2987

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wassil opened this issue Aug 27, 2024 · 9 comments · Fixed by #3055
Closed

Bug: Idempotency middleware doesn't work for functions without return value #2987

wassil opened this issue Aug 27, 2024 · 9 comments · Fixed by #3055
Assignees
Labels
bug-upstream This item is related to a bug caused by upstream dependency completed This item is complete and has been merged/shipped idempotency This item relates to the Idempotency Utility

Comments

@wassil
Copy link

wassil commented Aug 27, 2024

Expected Behavior

When using the makeHandlerIdempotent middleware with a handler function that has no return value(undefined), the middleware should only call the handler once.

Current Behavior

The handler is called multiple times for the same event, even though the idempotency record exists in Dynamo.

The issue is that the package returns the stored return value from previous run from the middleware.
Here: https://github.com/aws-powertools/powertools-lambda-typescript/blob/main/packages/idempotency/src/IdempotencyHandler.ts#L243
And here: https://github.com/aws-powertools/powertools-lambda-typescript/blob/main/packages/idempotency/src/middleware/makeHandlerIdempotent.ts#L140

If the value returned from middleware is truthy, it causes middy to perform an early return(https://middy.js.org/docs/intro/early-interrupt/).
This works for handler that have a return value, but doesn't work when the return value is undefined - middy doesn't early return and the handler code is executed again.

Code snippet

export handler = middy().use(makeHandlerIdempotent(...)).handler(() => console.log("stuff"))

Steps to Reproduce

Use the code snippet, call the lambda multiple times with the same event

Possible Solution

In order to early return, a truthy value has to be returned from the middleware.
This kind of sucks, because the first time a lambda runs, it returns undefined and we'd have to return something else when idempotency key is present.
In our use case, the return value doesn't matter, but I'm not sure what would make sense as a generic solution.

Powertools for AWS Lambda (TypeScript) version

latest

AWS Lambda function runtime

20.x

Packaging format used

npm

Execution logs

No response

@wassil wassil added bug Something isn't working triage This item has not been triaged by a maintainer, please wait labels Aug 27, 2024
@wassil
Copy link
Author

wassil commented Aug 27, 2024

@dreamorosi added support for functions with no return value in #2521

Hope this helps with the triage!

@dreamorosi dreamorosi added idempotency This item relates to the Idempotency Utility confirmed The scope is clear, ready for implementation and removed triage This item has not been triaged by a maintainer, please wait labels Aug 27, 2024
@dreamorosi dreamorosi moved this from Triage to Backlog in Powertools for AWS Lambda (TypeScript) Aug 27, 2024
@dreamorosi
Copy link
Contributor

Hi @wassil, thank you for opening the issue and reporting the bug.

I was able to reproduce the behavior you describe and confirm that when the handler function doesn't return a value, the Middy.js middleware runs twice.

image

We'll look into fixing the issue.

@dreamorosi dreamorosi self-assigned this Aug 27, 2024
@dreamorosi
Copy link
Contributor

I have spent some time looking at the implementation (thanks for the exhaustive report!) and unfortunately I think we can't do anything unless Middy.js itself supports this use case.

I have opened an issue on their repo (middyjs/middy#1236) and will be waiting to hear from the maintainer(s) on whether this is something that can be supported or not.

I'll wait for a couple of days, after which I'll add a banner to our docs to explain the limitation and suggest customers to use other Idempotency methods (i.e. function wrapper or class method decorator) if their use case can return undefined.

@dreamorosi dreamorosi added bug-upstream This item is related to a bug caused by upstream dependency and removed bug Something isn't working labels Aug 27, 2024
@dreamorosi dreamorosi moved this from Backlog to On hold in Powertools for AWS Lambda (TypeScript) Aug 27, 2024
@dreamorosi dreamorosi added on-hold This item is on-hold and will be revisited in the future and removed confirmed The scope is clear, ready for implementation labels Aug 27, 2024
@wassil
Copy link
Author

wassil commented Aug 27, 2024

Yeah, there is no nice clean solution and I don't think there is a difference between return undefined and no return in JS.

I was considering writing my own idempotency middleware using this package. We also have a batch processing use-case where I want to mark individual messages as processed.
The only way I found was something along the lines of:

let promiseResolver;
{
  // start a dummy function that will be made idempotent
  before() => { makeIdempotent(() => new Promise(done => promiseResolver = done))(idempotencyKeyFromPayload); }
  // finish the dummy function so it's marked complete in the idempotency table 
  after() => { promiseResolver(); }
  // throw an error so the idempotency entry is cleared from the table
  onError() => { promiseResolver(new Error("BOOM!"))}
}

But it feels like a hack to be honest.
Have you considered exposing more of the functionality from the package so it's easier to build these special cases?
Basically something like "I have started processing", "Processing completed" and "Error during processing" functions.
All of these are available in IdempotencyHandler, but I understand that you want to keep that private.

@am29d
Copy link
Contributor

am29d commented Aug 27, 2024

We have something similar as a feature request that is already in python implementation #2887 but this is mostly for "Processing completed" case.

@willfarrell
Copy link

Middy documentation is now updated.

@dreamorosi
Copy link
Contributor

dreamorosi commented Aug 28, 2024

Have you considered exposing more of the functionality from the package so it's easier to build these special cases?

We haven't and in general we try to keep our public API tidy & focused, although we are open to proposals in this sense.

For this specific case, I'd like to propose the following actions:

  1. Update the Powertools for AWS docs to clarify this limitation when using the Middy.js makeHandlerIdempotent decorator
  2. Follow and monitor v6 development for Middy.js and see if they can make changes that would allow this type of feature. There were some proposals in the linked issue that might yield a solution and allow the use case without relying on early returns. If this is the case and the change lands in v6 (or v7) we'd recommend Powertools for AWS customers wanting to use the makeHandlerIdempotent middleware with functions that can return undefined to upgrade to said version
  3. Recommend customers with this use case to use one of the alternate methods Powertools for AWS offers.

Specifically, even when using other Middy.js middlewares, you can wrap your handler like this:

import middy from '@middy/core';
import { Logger } from '@aws-lambda-powertools/logger';
import { injectLambdaContext } from '@aws-lambda-powertools/logger/middleware';
import { makeIdempotent } from '@aws-lambda-powertools/idempotency';
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/dynamodb';

const logger = new Logger();

const persistenceStore = new DynamoDBPersistenceLayer({
  tableName: process.env.IDEMPOTENCY_TABLE_NAME as string,
});

export const handler = middy(
  makeIdempotent(
    async () => {
      logger.info('stuff');
    },
    { persistenceStore }
  )
).use(injectLambdaContext(logger));

In this example I am using another Powertools for AWS middleware (from Logger), but using the makeIdempotent wrapper function around the handler and then passing this to @middy/core roughly equates to using the makeHandlerIdempotent Middy.js-compatible middleware as first in the stack.

The difference however is that using this method your function is considered idempotent even if its return value is undefined. This is because we handle the logic internally in the Idempotency utility.

I will open a PR to update the docs shortly.

@dreamorosi dreamorosi moved this from On hold to Working on it in Powertools for AWS Lambda (TypeScript) Sep 12, 2024
@dreamorosi dreamorosi added confirmed The scope is clear, ready for implementation and removed on-hold This item is on-hold and will be revisited in the future labels Sep 12, 2024
@github-project-automation github-project-automation bot moved this from Working on it to Coming soon in Powertools for AWS Lambda (TypeScript) Sep 12, 2024
Copy link
Contributor

⚠️ COMMENT VISIBILITY WARNING ⚠️

This issue is now closed. Please be mindful that future comments are hard for our team to see.

If you need more assistance, please either tag a team member or open a new issue that references this one.

If you wish to keep having a conversation with other community members under this issue feel free to do so.

@github-actions github-actions bot added pending-release This item has been merged and will be released soon and removed confirmed The scope is clear, ready for implementation labels Sep 12, 2024
Copy link
Contributor

This is now released under v2.8.0 version!

@github-actions github-actions bot added completed This item is complete and has been merged/shipped and removed pending-release This item has been merged and will be released soon labels Sep 16, 2024
@dreamorosi dreamorosi moved this from Coming soon to Shipped in Powertools for AWS Lambda (TypeScript) Sep 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug-upstream This item is related to a bug caused by upstream dependency completed This item is complete and has been merged/shipped idempotency This item relates to the Idempotency Utility
Projects
4 participants