Skip to content
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

Race condition in Device.js / disconnect() #73

Open
nmasse-itix opened this issue Jun 7, 2024 · 0 comments
Open

Race condition in Device.js / disconnect() #73

nmasse-itix opened this issue Jun 7, 2024 · 0 comments

Comments

@nmasse-itix
Copy link

Environment

I'm using the following code to connect to a Lego Powered Up Hub using node-ble on a Framework Laptop 13 AMD running Fedora 40.

Code to reproduce the issue

const debug = require('debug')('sample-code');

const {createBluetooth} = require('node-ble');
const {bluetooth, destroy} = createBluetooth();

function sleep(delay) {
    return new Promise(function(resolve) {
        setTimeout(resolve, delay);
    });
}

async function main () {
    const adapter = await bluetooth.defaultAdapter();
    if (! await adapter.isDiscovering()) {
        await adapter.startDiscovery();
    }

    const device = await adapter.waitDevice('9C:9A:C0:02:E8:69');

    // Bind event listeners
    device.on("connect", (e) => { 
        debug("Connected !");
    });
    device.on("disconnect", (e) => { 
        debug("Disconnected !");
    });

    // Connect to the device
    await device.connect();
    
    // Do some work here
    sleep(2000);

    // Disconnect from the device
    await device.disconnect();

    // Do some heavy computation here
    for (i = 0; i < 1000000000; i++) {
        i = i * 1;
    }
 
    // Cleanup
    destroy();
    return "End of main";
}
main()
  .then(debug)
  .catch(debug)

Run it with :

while sleep 1; do
  DEBUG="sample-code" node index.js
  echo
done

Expected behavior

The following message is expected to appear on the console, until Ctrl+C is pressed.

  sample-code Connected ! +0ms
  sample-code Disconnected ! +3s
  sample-code End of main +2s

Current behavior

2 out of 5 disconnect events are lost.

$ while; sleep 1; do DEBUG="sample-code" node index.js; echo; done
  sample-code Connected ! +0ms
  sample-code Disconnected ! +3s
  sample-code End of main +2s

  sample-code Connected ! +0ms
  sample-code Disconnected ! +3s
  sample-code End of main +2s

  sample-code Connected ! +0ms
  sample-code End of main +5s

  sample-code Connected ! +0ms
  sample-code Disconnected ! +3s
  sample-code End of main +2s

  sample-code Connected ! +0ms
  sample-code End of main +5s

  sample-code Connected ! +0ms
^C

Possible explanation

In Device.js, in the disconnect method :

  /**
   * Disconnect remote device
   */
  async disconnect () {
    await this.helper.callMethod('Disconnect')
    this.helper.removeListeners()
  }

The removeListeners method is called right after the disconnect method is called. It leaves only a thin window where this.helper can send a disconnect event before the event listener are removed.

Depending on how your system is loaded / scheduled, the disconnect event might arrive after the event listeners have been removed.

Possible fix

Maybe remove all listeners after all disconnect events have been sent ?

  /**
   * Connect to remote device
   */
  async connect () {
    const cb = (propertiesChanged) => {
      if ('Connected' in propertiesChanged) {
        const { value } = propertiesChanged.Connected
        if (value) {
          this.emit('connect', { connected: true })
        } else {
          this.emit('disconnect', { connected: false })
-         this.helper.removeListeners()
        }
      }
    }

    this.helper.on('PropertiesChanged', cb)
    await this.helper.callMethod('Connect')
  }

  /**
   * Disconnect remote device
   */
  async disconnect () {
    await this.helper.callMethod('Disconnect')
-   this.helper.removeListeners()
  }

It does not match at 100% the previous API contract.

If the device has been disconnected externally (power off, weak signal, etc), currently the event handlers receives a disconnected event and a connect event as soon as the device comes back.

With the suggested fix, only a disconnected event is delivered.

What was the case for removing listeners upon explicit disconnection ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant