awslabs / awslabs/aws-crt-nodejs

mqtt5 client event handler bindings leave open handles keeping node process running

Open
#600 7 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.