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

fix: Canceling a connectionState subscription results in hang #14

Open
jefflongo opened this issue Oct 7, 2024 · 4 comments
Open

fix: Canceling a connectionState subscription results in hang #14

jefflongo opened this issue Oct 7, 2024 · 4 comments
Labels
bug Something isn't working

Comments

@jefflongo
Copy link
Contributor

Description

Awaiting a connectionState subscription cancellation never completes.

Steps To Reproduce

subscription = device.connectionState.listen(onConnectionState);
...
await subscription.cancel(); // <-- Hangs here
await device.disconnect();

Expected Behavior
Expected behavior is that the subscription can be canceled so that the app can disconnect from the device without calling onConnectionState.

Additional Context

This code works on Android using the same version of Flutter Blue Plus (1.33.2)

@jefflongo jefflongo added the bug Something isn't working label Oct 7, 2024
@chan150
Copy link
Owner

chan150 commented Oct 7, 2024

subscription = device.connectionState.listen(onConnectionState);

...

await subscription.cancel(); // <-- Hangs here

await device.disconnect();

Can I get more information about this.

onConnectionState function is not called in my case.

@jefflongo
Copy link
Contributor Author

Sure, in my app I connect as follows:

Future<void> connect(
    {Function? onDisconnect, Duration timeout = const Duration(seconds: 35)}) async {
  try {
    // perform the connection
    Stopwatch stopwatch = Stopwatch()..start();
    await _device.connect(timeout: timeout, mtu: 517);
    stopwatch.stop();

    // listen for mtu changes
    final mtuSubscription = _device.mtu.listen((mtu) => _mtu = mtu);
    _device.cancelWhenDisconnected(mtuSubscription);

    // discover services
    _services = await _device.discoverServices(timeout: (timeout - stopwatch.elapsed).inSeconds);
  } catch (e) {
    await _device.disconnect(queue: false).catchError((e) {});
    log.w(e);
    throw Exception('FBP connect failed: $e');
  }

  // listen for disconnect
  _disconnectSubscription = _device.connectionState.listen((state) {
    if (state == fbp.BluetoothConnectionState.disconnected) {
      onDisconnect?.call();
    }
  });
  _device.cancelWhenDisconnected(_disconnectSubscription!, next: true, delayed: true);
}

After connecting, I test several characteristic writes. If they fail, I call this disconnect function:

Future<void> disconnect({bool withCallback = true}) async {
  try {
    if (!withCallback) {
      await _disconnectSubscription?.cancel();
    }
    await _device.disconnect();
  } catch (e) {
    log.w(e);
  }
}

The point here is to cancel the connectionState subscription before calling _device.disconnect() to prevent the connectionState callback from being called. Specifically, awaiting _disconnectSubscription?.cancel() never completes the future. I think this could be related to the stream being a broadcast stream and the stream controller not handling cancellation requests because the stream never closes.

@chan150
Copy link
Owner

chan150 commented Oct 8, 2024

It may be related with this post:
rohitsangwan01/win_ble#44

I'll take a deeper look into the issues.

@jefflongo
Copy link
Contributor Author

Could be related to this code? https://github.com/chan150/flutter_blue_plus_windows/blob/master/lib/src/windows/bluetooth_device_windows.dart#L181-L183

A StreamSubscription cancellation future will not complete if a stream generator doesn't yield or complete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants