serverless-adapter icon indicating copy to clipboard operation
serverless-adapter copied to clipboard

Long execution time caused by `callbackWaitsForEmptyEventLoop=true` on AWS Stream

Open garfieldnate opened this issue 1 year ago • 6 comments

Feature Request

We needed to set callbackWaitsForEmptyEventLoop to false for our use-case; without this, our lambda invocations were taking 30s instead of 8ms, so it's a huge deal for us.

It was a bit difficult to figure out how to access and modify the AWS context object, so I wanted to share our solution here for others to use in the future:

import {
  type BinarySettings,
  type ILogger,
  ServerlessAdapter,
} from "@h4ad/serverless-adapter";
import {
  type AWSStreamContext,
  AwsStreamHandler,
} from "@h4ad/serverless-adapter/lib/handlers/aws";

// Extend AwsStreamHandler to access the context object
class CustomAwsStreamHandler<TApp> extends AwsStreamHandler<TApp> {
  protected onReceiveRequest(
    log: ILogger,
    event: APIGatewayProxyEventV2,
    context: AWSStreamContext,
    binarySettings: BinarySettings,
    respondWithErrors: boolean,
  ): void {
    super.onReceiveRequest(
      log,
      event,
      context,
      binarySettings,
      respondWithErrors,
    );

    context.context.callbackWaitsForEmptyEventLoop = false;
  }
}

// set the stream handler on the adapter
export const handler = ServerlessAdapter.new(app)
  .setHandler(new CustomAwsStreamHandler())
  ...
  .build();

garfieldnate avatar Sep 16 '24 03:09 garfieldnate

You can call this function and you will have access to the context and the event:

https://github.com/H4ad/serverless-adapter/blob/53d431b25a57622173d26b6ddbb6baca389a8c1d/src/core/current-invoke.ts#L28-L42

Are you using PromiseResolver or CallbackResolver?

H4ad avatar Sep 16 '24 17:09 H4ad

🤔 Where would I call this function? Our entire handler creation looks like this:

export const handler = ServerlessAdapter.new(app)
  .setFramework(new ExpressFramework())
  .setHandler(new CustomAwsStreamHandler())
  .setResolver(new DummyResolver())
  .addAdapter(new ApiGatewayV2Adapter())
  .build();

This is following the documentation, which states that only DummyResolver is needed for streaming. We are not using CallbackResolver or PromiseResolver.

garfieldnate avatar Sep 16 '24 22:09 garfieldnate

Oh, I got it, I didn't see you were using AWS Stream Handler.

Hm... in this case, I think is worthy to have a flag to control this behavior, I'm suspect you have something in your event loop that makes the lambda keep alive for that amount of time but since resolving the promise is not enough, having that flag always false should be good.

Thanks for reporting this, I will probably add a option to customize that value in the future, and in a breaking change, I will try to make it default to false always to avoid these kind of issues.

H4ad avatar Sep 16 '24 22:09 H4ad

That is wonderful, thank you!

Yes, we clearly have some DB connections or something that aren't getting closed. We always used this setting with the non-streaming API, so we just needed a way to continue using it.

garfieldnate avatar Sep 16 '24 23:09 garfieldnate

Just to let you know, I released a new version with the flag to make callbackWaits to false, see more: https://serverless-adapter.viniciusl.com.br/docs/main/handlers/aws#my-execution-is-taking-too-long

H4ad avatar Sep 21 '24 14:09 H4ad

Wow, thank you very much!

garfieldnate avatar Sep 21 '24 17:09 garfieldnate