Skip to content

Commit

Permalink
feat(aws-stream-handler): add flag to customize callbackWaitsForEmpty…
Browse files Browse the repository at this point in the history
…EventLoop (#264)
  • Loading branch information
H4ad committed Sep 17, 2024
1 parent 53d431b commit 30a59f9
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 0 deletions.
33 changes: 33 additions & 0 deletions src/handlers/aws/aws-stream.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,22 @@ declare const awslambda: {
};
};

/**
* The interface that customizes the {@link AwsStreamHandler}
*
* @breadcrumb Handlers / AwsStreamHandler
* @public
*/
export type AwsStreamHandlerOptions = {
/**
* Set the value of the property `callbackWaitsForEmptyEventLoop`, you can set to `false` to fix issues with long execution due to not cleaning the event loop ([ref](https://github.com/H4ad/serverless-adapter/issues/264)).
* In the next release, this value will be changed to `false`.
*
* @defaultValue undefined
*/
callbackWaitsForEmptyEventLoop?: boolean;
};

/**
* The interface that describes the internal context used by the {@link AwsStreamHandler}
*
Expand Down Expand Up @@ -89,6 +105,17 @@ export class AwsStreamHandler<TApp> extends BaseHandler<
AWSStreamResponseMetadata,
void
> {
//#region Constructor

/**
* Construtor padrão
*/
constructor(private readonly options?: AwsStreamHandlerOptions) {
super();
}

//#endregion

//#region Public Methods

/**
Expand All @@ -114,6 +141,12 @@ export class AwsStreamHandler<TApp> extends BaseHandler<
log: ILogger,
): ServerlessHandler<Promise<void>> {
return awslambda.streamifyResponse(async (event, response, context) => {
if (this.options?.callbackWaitsForEmptyEventLoop !== undefined) {
// TODO(h4ad): Set the following property to false by default
context.callbackWaitsForEmptyEventLoop =
this.options.callbackWaitsForEmptyEventLoop;
}

const streamContext = { response, context };

this.onReceiveRequest(
Expand Down
76 changes: 76 additions & 0 deletions test/handlers/aws-stream.handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,4 +435,80 @@ describe('AwsStreamHandler', () => {
}),
);
});

it('callbackWaitsForEmptyEventLoop should not be modified', async () => {
const app = express();

app.get('/', (_, res) => {
res.json({ ok: true });
});

const expressFramework = new ExpressFramework();

const handler = awsStreamHandler.getHandler(
app,
expressFramework,
adapters,
resolver,
binarySettings,
respondWithErrors,
logger,
);

const event = createApiGatewayV2('GET', '/', {}, { test: 'true' });
const defaultValueForCallback = Symbol('1');
const context = {
test: Symbol('unique'),
callbackWaitsForEmptyEventLoop: defaultValueForCallback,
};

const writable = new WritableMock();

await handler(event, writable, context);

expect(context).toHaveProperty(
'callbackWaitsForEmptyEventLoop',
defaultValueForCallback,
);
});

describe('callbackWaitsForEmptyEventLoop should be changed', () => {
for (const value of [true, false]) {
it(`to ${value}`, async () => {
const app = express();

app.get('/', (_, res) => {
res.json({ ok: true });
});

const expressFramework = new ExpressFramework();
const customAwsHandler = new AwsStreamHandler({
callbackWaitsForEmptyEventLoop: value,
});

const handler = customAwsHandler.getHandler(
app,
expressFramework,
adapters,
resolver,
binarySettings,
respondWithErrors,
logger,
);

const event = createApiGatewayV2('GET', '/', {}, { test: 'true' });
const defaultValueForCallback = Symbol('test');
const context = {
test: Symbol('unique'),
callbackWaitsForEmptyEventLoop: defaultValueForCallback,
};

const writable = new WritableMock();

await handler(event, writable, context);

expect(context).toHaveProperty('callbackWaitsForEmptyEventLoop', value);
});
}
});
});
25 changes: 25 additions & 0 deletions www/docs/main/handlers/aws.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,31 @@ export const handler = ServerlessAdapter.new(app)

As you see, we only have to change the `setHandler`, `setResolver` and `setAdapter` methods, that's all!

### My execution is taking too long

Well, maybe is caused because your event loop is not empty when the request finishes, in this case, you can set `callbackWaitsForEmptyEventLoop=false` on [AwsStreamHandler](../../api/handlers/AwsStreamHandler).


```ts title="index.ts"
import { ServerlessAdapter } from '@h4ad/serverless-adapter';
import { AwsStreamHandler } from '@h4ad/serverless-adapter/handlers/aws';
import { DummyResolver } from '@h4ad/serverless-adapter/resolvers/dummy';
import { ApiGatewayV2Adapter } from '@h4ad/serverless-adapter/adapters/aws';
import app from './app';

export const handler = ServerlessAdapter.new(app)
.setHandler(new AwsStreamHandler({ callbackWaitsForEmptyEventLoop: false }))
.setResolver(new DummyResolver())
.setAdapter(new ApiGatewayV2Adapter())
// continue to set the other options here.
//.addAdapter(new AlbAdapter())
//.addAdapter(new SQSAdapter())
//.addAdapter(new SNSAdapter())
//.setFramework(new ExpressFramework())
// after put all methods necessary, just call the build method.
.build();
```

### Can I use SQS and other services?

Well, I don't know, but you can try. At least with `SQS` I was able to get the messages, but I couldn't use the `partial batch response` feature.
Expand Down

0 comments on commit 30a59f9

Please sign in to comment.