awslabs / awslabs/aws-crt-nodejs
mqtt5 client event handler bindings leave open handles keeping node process running
- Dominant language
- TypeScript
- Stars
- 43
- Forks
- 32
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 1
Description
### Describe the bug
The AWS MQTT5 client leaves open handles on its event handlers that are instantiated within the constructor. These open handles prevent NodeJS process from closing properly.
I was able to create a simple script that replicates the issue. It shows the same open handles that I've found when using `why-is-node-running` with our test framework.
The open handles are are from the AWS MQTT5 Client event handler bindings shown by `why-is-node-running`:
```
There are 6 handle(s) keeping the process running
# aws_mqtt5_client_on_stopped
/aws-iot-device-sdk-v2-repro/node_modules/aws-crt/dist/native/mqtt5.js:143 - this._super(binding_1.default.mqtt5_client_new(this, config, (client) => { Mqtt5Client._s_on_stopped(client); }, (client) => { Mqtt5Client._s_on_attempting_connect(client); }, (client, connack, settings) => { Mqtt5Client._s_on_connection_success(client, connack, settings); }, (client, errorCode, connack) => { Mqtt5Client._s_on_connection_failure(client, new error_1.CrtError(errorCode), connack); }, (client, errorCode, disconnect) => { Mqtt5Client._s_on_disconnection(client, new error_1.CrtError(errorCode), disconnect); }, (client, message) => { Mqtt5Client._s_on_message_received(client, message); }, config.clientBootstrap ? config.clientBootstrap.native_handle() : null, config.socketOptions ? config.socketOptions.native_handle() : null, config.tlsCtx ? config.tlsCtx.native_handle() : null, config.httpProxyOptions ? config.httpProxyOptions.create_native_handle() : null));
/aws-iot-device-sdk-v2-repro/test.js:37 - const client = new mqtt5.Mqtt5Client(mqttConfig);
# aws_mqtt5_client_on_attempting_connect
/aws-iot-device-sdk-v2-repro/node_modules/aws-crt/dist/native/mqtt5.js:143 - this._super(binding_1.default.mqtt5_client_new(this, config, (client) => { Mqtt5Client._s_on_stopped(client); }, (client) => { Mqtt5Client._s_on_attempting_connect(client); }, (client, connack, settings) => { Mqtt5Client._s_on_connection_success(client, connack, settings); }, (client, errorCode, connack) => { Mqtt5Client._s_on_connection_failure(client, new error_1.CrtError(errorCode), connack); }, (client, errorCode, disconnect) => { Mqtt5Client._s_on_disconnection(client, new error_1.CrtError(errorCode), disconnect); }, (client, message) => { Mqtt5Client._s_on_message_received(client, message); }, config.clientBootstrap ? config.clientBootstrap.native_handle() : null, config.socketOptions ? config.socketOptions.native_handle() : null, config.tlsCtx ? config.tlsCtx.native_handle() : null, config.httpProxyOptions ? config.httpProxyOptions.create_native_handle() : null));
/aws-iot-device-sdk-v2-repro/test.js:37 - const client = new mqtt5.Mqtt5Client(mqttConfig);
# aws_mqtt5_client_on_connection_success
/aws-iot-device-sdk-v2-repro/node_modules/aws-crt/dist/native/mqtt5.js:143 - this._super(binding_1.default.mqtt5_client_new(this, config, (client) => { Mqtt5Client._s_on_stopped(client); }, (client) => { Mqtt5Client._s_on_attempting_connect(client); }, (client, connack, settings) => { Mqtt5Client._s_on_connection_success(client, connack, settings); }, (client, errorCode, connack) => { Mqtt5Client._s_on_connection_failure(client, new error_1.CrtError(errorCode), connack); }, (client, errorCode, disconnect) => { Mqtt5Client._s_on_disconnection(client, new error_1.CrtError(errorCode), disconnect); }, (client, message) => { Mqtt5Client._s_on_message_received(client, message); }, config.clientBootstrap ? config.clientBootstrap.native_handle() : null, config.socketOptions ? config.socketOptions.native_handle() : null, config.tlsCtx ? config.tlsCtx.native_handle() : null, config.httpProxyOptions ? config.httpProxyOptions.create_native_handle() : null));
/aws-iot-device-sdk-v2-repro/test.js:37 - const client = new mqtt5.Mqtt5Client(mqttConfig);
# aws_mqtt5_client_on_connection_failure
/aws-iot-device-sdk-v2-repro/node_modules/aws-crt/dist/native/mqtt5.js:143 - this._super(binding_1.default.mqtt5_client_new(this, config, (client) => { Mqtt5Client._s_on_stopped(client); }, (client) => { Mqtt5Client._s_on_attempting_connect(client); }, (client, connack, settings) => { Mqtt5Client._s_on_connection_success(client, connack, settings); }, (client, errorCode, connack) => { Mqtt5Client._s_on_connection_failure(client, new error_1.CrtError(errorCode), connack); }, (client, errorCode, disconnect) => { Mqtt5Client._s_on_disconnection(client, new error_1.CrtError(errorCode), disconnect); }, (client, message) => { Mqtt5Client._s_on_message_received(client, message); }, config.clientBootstrap ? config.clientBootstrap.native_handle() : null, config.socketOptions ? config.socketOptions.native_handle() : null, config.tlsCtx ? config.tlsCtx.native_handle() : null, config.httpProxyOptions ? config.httpProxyOptions.create_native_handle() : null));
/aws-iot-device-sdk-v2-repro/test.js:37 - const client = new mqtt5.Mqtt5Client(mqttConfig);
# aws_mqtt5_client_on_disconnection
/aws-iot-device-sdk-v2-repro/node_modules/aws-crt/dist/native/mqtt5.js:143 - this._super(binding_1.default.mqtt5_client_new(this, config, (client) => { Mqtt5Client._s_on_stopped(client); }, (client) => { Mqtt5Client._s_on_attempting_connect(client); }, (client, connack, settings) => { Mqtt5Client._s_on_connection_success(client, connack, settings); }, (client, errorCode, connack) => { Mqtt5Client._s_on_connection_failure(client, new error_1.CrtError(errorCode), connack); }, (client, errorCode, disconnect) => { Mqtt5Client._s_on_disconnection(client, new error_1.CrtError(errorCode), disconnect); }, (client, message) => { Mqtt5Client._s_on_message_received(client, message); }, config.clientBootstrap ? config.clientBootstrap.native_handle() : null, config.socketOptions ? config.socketOptions.native_handle() : null, config.tlsCtx ? config.tlsCtx.native_handle() : null, config.httpProxyOptions ? config.httpProxyOptions.create_native_handle() : null));
/aws-iot-device-sdk-v2-repro/test.js:37 - const client = new mqtt5.Mqtt5Client(mqttConfig);
# aws_mqtt5_client_on_message_received
/aws-iot-device-sdk-v2-repro/node_modules/aws-crt/dist/native/mqtt5.js:143 - this._super(binding_1.default.mqtt5_client_new(this, config, (client) => { Mqtt5Client._s_on_stopped(client); }, (client) => { Mqtt5Client._s_on_attempting_connect(client); }, (client, connack, settings) => { Mqtt5Client._s_on_connection_success(client, connack, settings); }, (client, errorCode, connack) => { Mqtt5Client._s_on_connection_failure(client, new error_1.CrtError(errorCode), connack); }, (client, errorCode, disconnect) => { Mqtt5Client._s_on_disconnection(client, new error_1.CrtError(errorCode), disconnect); }, (client, message) => { Mqtt5Client._s_on_message_received(client, message); }, config.clientBootstrap ? config.clientBootstrap.native_handle() : null, config.socketOptions ? config.socketOptions.native_handle() : null, config.tlsCtx ? config.tlsCtx.native_handle() : null, config.httpProxyOptions ? config.httpProxyOptions.create_native_handle() : null));
/aws-iot-device-sdk-v2-repro/test.js:37 - const client = new mqtt5.Mqtt5Client(mqttConfig);
^C
```
If you have any idea of how to resolve/avoid this issue, please let me know.
### Regression Issue
- [ ] Select this option if this issue appears to be a regression.
### Expected Behavior
I expect the AWS MQTT5 client event handlers to be cleaned up successfully allowing the NodeJS process to close successfully.
### Current Behavior
The AWS MQTT5 client leave open handles for the event handlers which prevents NodeJS process from closing and causing it to hang.
### Reproduction Steps
Here is a simple package file and NodeJS script file to replicate the issue, my coworker was able to get the same console output with these. However, you'll need your own AWS IoT endpoint, private key pem file and client certificate file.
package.json
```
{
"dependencies": {
"aws-iot-device-sdk-v2": "^1.21.1",
"why-is-node-running": "^2.3.0"
}
}
```
test.js script
```
const { mqtt5, iot } = require('aws-iot-device-sdk-v2');
const whyIsNodeRunning = require('why-is-node-running');
const awsIotEndpoint = '';
const builder = iot.AwsIotMqtt5ClientConfigBuilder.newDirectMqttBuilderWithMtlsFromPath(
awsIotEndpoint,
'./clientCertificate.pem',
'./awsIoTPrivateKey.pem'
);
builder.withConnectProperties();
const mqttConfig = builder.build();
const client = new mqtt5.Mqtt5Client(mqttConfig);
const main = async () => {
whyIsNodeRunning();
};
main();
```
And just run `npm install` and `node ./test.js`.
### Possible Solution
_No response_
### Additional Information/Context
_No response_
### aws-crt-nodejs version used
1.21.8
### nodejs version used
20.18.1
### Operating System and version
Ubuntu 22.04.5 LTS
Contributor guide
Research direction
Start with the constructor call shown at dist/native/mqtt5.js:143 and reproduce the issue using the provided test.js script and why-is-node-running. Trace the MQTT5 event-handler bindings and verify that the process exits without the six reported open handles after the client is created.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100