Closed b055man closed 3 years ago
@dariuszseweryn the stacktrace from AndroidStudio seems to point directly at the place in question.. https://github.com/Polidea/RxAndroidBle/blob/8550bd66493c8e12449c784ca28ed0b167e67fc3/rxandroidble/src/main/java/com/polidea/rxandroidble2/RxBleAdapterStateObservable.java#L61
Hi @b055man Thanks for the report — could you create a minimal project that reproduces the issue?
@dariuszseweryn Just tried that using a newly created Flutter example as base with flutter_ble_lib 2.3.0, Flutter: 1.20.3
main.dart
import 'package:ble_lib_leak/connect_ble.dart';
import 'package:flutter/material.dart';
import 'package:flutter_ble_lib/flutter_ble_lib.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController _controller;
bool _isConnecting = false;
@override
void initState() {
_controller = TextEditingController();
_controller.value = TextEditingValue(text: '0A:1D:96:07:C1:1D');
super.initState();
}
@override
void dispose() {
BleManager().destroyClient();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
controller: _controller,
),
FlatButton(
child: Text('Start connecting'),
onPressed: _isConnecting
? null
: () {
setState(() {
_isConnecting = true;
});
print(_controller.value.text);
initiateBurstConnecting(_controller.value.text).then((_) =>
_isConnecting = false);
},
)
],
),
),
);
}
}
connect_ble.dart
import 'package:flutter_ble_lib/flutter_ble_lib.dart';
Future initiateBurstConnecting(String id) async {
final bleManager = BleManager();
await bleManager.createClient();
await bleManager.setLogLevel(LogLevel.verbose);
return _connect(bleManager.createUnsafePeripheral(id));
}
Future _connect(Peripheral peripheral, {int retryNum = 1}) async {
print('Trying to connect... : ${peripheral.identifier}, attempt: $retryNum');
bool connected = false;
final isConnected = await peripheral.isConnected();
if (!isConnected) {
try {
await peripheral.connect(
isAutoConnect: retryNum % 2 == 1,
refreshGatt: true,
timeout: Duration(milliseconds: 100));
connected = await peripheral.isConnected();
print('Device ${peripheral.identifier} connection result: $connected');
} catch (e) {
print('Detailed cause: $e');
connected = false;
}
}
print(
'Finished connecting to ${peripheral.identifier}: $connected, retry: $retryNum}');
return connected || await _connect(peripheral, retryNum: ++retryNum);
}
Press 'start connecting' and wait a bit. You'll get:
D/RxBle#ClientOperationQueue(28933): FINISHED DisconnectOperation(132979214) in 24 ms
E/MethodChannel#flutter_ble_lib(28933): Failed to handle method call
E/MethodChannel#flutter_ble_lib(28933): rx.exceptions.OnErrorNotImplementedException: Too many receivers, total of 1000, registered for pid: 28933, callerPackage: com.example.ble_lib_leak
E/MethodChannel#flutter_ble_lib(28933): at rx.internal.util.InternalObservableUtils$ErrorNotImplementedAction.call(InternalObservableUtils.java:386)
If you could create a repository which I could clone, build and recreate the issue — that would be perfect
@dariuszseweryn Ok, here you go: https://github.com/b055man/ble_lib_leak
@dariuszseweryn were you able to reproduce the issue maybe?
@mikolak @dariuszseweryn Can I be of any help with addressing this issue?
Thanks, A.
Hello @b055man
I am recently pretty much saturated with amount of work. Yes, you could help — what we need to do is to perform the test and trace down leaked BroadcastReceiver
s. It should be fairly simple using LeakCanary — it would probably need a minor native app change to integrate.
@dariuszseweryn
I'm not sure if LeakCanary is good in tracing leaked Broadcast receivers.. Anyway, I can see a leak warning (pointing to RxBleAdapterStateObservable) if I do even a single connection attempt and trigger the heap dump (simply by leaving the app/putting it in the background). What's also interesting is that if I destroy the BLE client after all connection attempts, there are no leak warnings, yet the receivers are still leaked - the app will crash upon hitting the 1000th connection attempt.
@dariuszseweryn @mikolak I run a similar test using the Flutter Reactive Ble package (https://pub.dev/packages/flutter_reactive_ble), which uses Polidea's rxandroidble library too (it interacts with it directly, not via the MultiplatformBleAdapter) - the difference is that they use the most-up-to-date version of the rxandroidble library, whereas the MultiAdapter still uses a 2-years old version (1.7.1). The RxStateObservable leak was fixed in version 1.10.0 https://github.com/Polidea/RxAndroidBle/pull/575 Isn't this the cause then?
@dariuszseweryn another updated: as expected, using a newer version of the rxandroidble library solves this issue... but it's not that easy to migrate, as it now uses RxJava2. However, thanks to @JamesMcIntosh PR here: https://github.com/Polidea/MultiPlatformBleAdapter/pull/67 I managed to build FlutterBlePlugin using an updated version of MultiPlatformBleAdapter and the latest rxandroidble library. And guess what - the issue does not reproduce there..
So, guys @dariuszseweryn @mikolak - any chance to pursue that James's PR and moving MultiPlatformBleAdapter to RxJava2, to allow using recent versions of the rxandroidble library?
@mikolak @dariuszseweryn Guys, can you comment, please?
Hi!
Sorry for the long silence, I'm completely swamped with work right now. I'd love to migrate it to new RxAndroidBLE, but I seem to recall there might be some issue with global error listeners (?). I hope to have some time from November. I'll consult it internally and try to get back to you, though I can't promise timely manner right now.
Thank you for investigating it so thoroughly!
@mikolak Thanks for providing the rough timeframe - I will bump the topic again sometime in Nov then, unless there are some updates before that time.
@mikolak @dariuszseweryn Hi guys, any updates on this one?
@b055man I've just released 3.0.0-beta (https://github.com/Polidea/FlutterBleLib/releases/tag/v3.0.0-beta) with RxAndroidBle 2. Give it a go, lemme know if you encounter any issues. Let me know if everything works fine as well, as this will have to leave beta some day.
Closing for now.
@mikolak Thanks. We'll give it a spin and I'll let you know the results.
I'm still experiencing memory leak with version 3.0.0-beta
.
When peripherial scan is running for about 3-5 minutes, calling peripherial.connect()
causes sudden memory spike to up 2GB and app crashes.
@PiotrJozefow I had a look last week and found a source of leaking, it's RxJava subscriptions in the MultiPlatformBleAdapter, will look at at PR when I get a moment. See: https://github.com/Polidea/FlutterBleLib/issues/563 and https://github.com/Polidea/MultiPlatformBleAdapter/issues/72
@JamesMcIntosh I have no idea why but the memory leak I have occurs only when I have flutter animationController running on repeat at the time of peripherial scan. Thanks for fixing this and I'm really looking forward to your PR being merged 💪
@PiotrJozefow are you repeatedly calling connect()
or another method in the library, your animationController may be continuously refreshing the widget... add some breakpoints and see what happening.
@bo55man @JamesMcIntosh have you encountered any issues using the beta?
@mikolak I haven't had a chance to get back to it yet but I'm pretty sure it was affected by the same problem because of the way RxJava2 is used with subscriptions / observales. https://github.com/Polidea/MultiPlatformBleAdapter/issues/72
Observed on version: 2.2.9
After leaving the application reconnecting to a gone device for several hours, the app is no longer operational - it is not possible to register any receivers as app hit the limit of 1000 registered ones.
When it happened once, I later tried to pinpoint the culprit (wasnt't sure if this may be scanning related, or maybe even Wifi portion of the app's functionality) and I reproduced it quickly by repeatedly initiating connections. Snippet from the code handling the connection:
Once 1000 connections were reached, this is what I got:
With a profiler from Android Studio I am indeed able to see that there are things that are not released and clearly point to flutter_ble_lib - or rather rxandroidble
Once that exception is hit, subsequent calls to connect method end instantly with the following error:
There doesn't seem to be a way of recovering from this - destroying the client does not help. Only way to resolve that seems to require swiping out the app from the recents application list.
I noticed an issue about a leak in the rxandroidble but it was related to scanning (https://github.com/Polidea/RxAndroidBle/issues/607) - this one seems to be a bigger issue.
Any hints are appreciated - this is a huge deal for the app I'm working on.