Flutter iOS crash with EXC_BAD_ACCESS error

644

A couple thoughts for the permanently increasing memory usage because it is practically impossible to answer your question without having a full example.

  1. Firebase caches the objects, thats why it is faster, the 2nd time. This should not effect your memory usage by an amount that breaks the app.

  2. I ask this question by suspecting that the stream still occupies the memory even after the user moves to another screen...

Maybe you are using Navigator.push* and never pop any of the previous routes, all the lists in the previous routes are being kept in memory.

  1. Track the dispose calls, see if the widgets get cleaned up. By using logging, breakpoints or even better track the widget counts by using the DevTools memory view: https://docs.flutter.dev/development/tools/devtools/memory The memory snapshots can tell you exactly where your memory is being used.

  2. In general it is better to move the sorting out of the build function. But this is more of a performance hint than a problem.

  3. Your memory problems may be unrelated to the EXC_BAD_ACCESS crash but you should try to solve them first. It will make it easier to find the underlying cause, if there is any.

Share:
644
edn
Author by

edn

And I am a big Machine Learning enthusiast. I am particularly focusing on Deep Neural Networks, Tensorflow and Python. You are welcome to get in touch for any discussion for closer cooperation.

Updated on January 03, 2023

Comments

  • edn
    edn over 1 year

    I am developing an app with flutter and testing the app on a physical iOS device (Iphone 7). The iOS version is: 15.3.1 And the flutter version is: 2.10.3

    When I am testing my app, I occasionally get crashes. The crash gives the error below. It does not always crash at the same place so I do not know what code to share here. The error message itself does not say much to me and I could not find any helpful information on the net wrt this error.

    * thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x443b8000443b8000)
        frame #0: 0x0000000198405048 libobjc.A.dylib`objc_msgSend + 8
    libobjc.A.dylib`objc_msgSend:
    ->  0x198405048 <+8>:  ldr    x13, [x0]
        0x19840504c <+12>: and    x16, x13, #0xffffffff8
        0x198405050 <+16>: mov    x15, x16
        0x198405054 <+20>: ldr    x11, [x16, #0x10]
    Target 0: (Runner) stopped.
    Lost connection to device.
    Exited (sigterm)
    

    I am totally clueless of what to check after many tries for several days. Would anyone with more experience in this field be able to help me?


    ADDENDUM:

    I wonder if I am doing sth wrong with my Stream Builders. Here is a shortened version of my code:

    class PrepareList extends StatefulWidget {
      final String place;
      const PrepareList({
        Key? key,
        required this.place,
      }) : super(key: key);
    
      @override
      State<PrepareList> createState() =>
          _PrepareListState();
    }
    
    class _PrepareListState
        extends State<PrepareList> {
      late final Stream? _listStream;
    
      @override
      void initState() {
        super.initState();
    
        String dbChild = "events/" + widget.place + "/";
    
        final db = FirebaseDatabase.instance
            .ref()
            .child(dbChild)
            .orderByKey()
            .limitToLast(1500);
    
        _listStream = db.onValue;
      }
    
      @override
      Widget build(BuildContext context) {
        return StreamBuilder(
            stream: _listStream,
            builder: (context, AsyncSnapshot<dynamic> dbEvent) {
              if (dbEvent.hasError) {
                return CircularProgressIndicator();
              }
              else if (dbEvent.hasData) {
                DataSnapshot dataSnapshot = dbEvent.data!.snapshot;
    
                List<EventDetails> placeEventList = [];
                if (dataSnapshot.value != null) {
                  (dataSnapshot.value as Map<dynamic, dynamic>)
                      .forEach((key, value) {
                    placeEventList.add(EventDetails.fromRTDB(value));
                  });
                  placeEventList
                      .sort((a, b) => a.dateEST.compareTo(b.dateEST));
    
                  return AttendeeList(placeEventList: placeEventList, place: place);
                } else {
                  return PlaceDataNotAvailable(place: widget.place);
                }
              } else {
                return CircularProgressIndicator();
              }
            });
      }
    }
    
    class AttendeeList extends StatefulWidget {
      final List<EventDetails> placeEventList;
      final String place;
      const AttendeeList({
        Key? key,
        required this.placeEventList,
        required this.place,
      }) : super(key: key);
    
      @override
      State<AttendeeList> createState() =>
          _AttendeeListState();
    }
    
    class _AttendeeListState
        extends State<AttendeeList> {
      late final Stream? _attendeeListStream;
    
      @override
      void initState() {
        super.initState();
    
        String dbChild = "attendees/" + widget.place + "/";
    
        final db = FirebaseDatabase.instance
            .ref()
            .child(dbChild)
            .orderByKey()
            .limitToLast(1500);
    
        _listStream = db.onValue;
      }
    
      @override
      Widget build(BuildContext context) {
        return StreamBuilder(
            stream: _attendeeListStream,
            builder: (context, AsyncSnapshot<dynamic> dbEvent) {
              if (dbEvent.hasError) {
                return CircularProgressIndicator();
              }
              else if (dbEvent.hasData) {
                DataSnapshot dataSnapshot = dbEvent.data!.snapshot;
    
                List<AttendeeDetails> attendeeList = [];
                if (dataSnapshot.value != null) {
                  (dataSnapshot.value as Map<dynamic, dynamic>)
                      .forEach((key, value) {
                    attendeeList.add(EventDetails.fromRTDB(value));
                  });
                  attendeeList
                      .sort((a, b) => a.dateEST.compareTo(b.dateEST));
    
                  return Scaffold(
                    body: ShowLists(placeEventList, attendeeList);
                } else {
                  return PlaceDataNotAvailable(place: widget.place);
                }
              } else {
                return CircularProgressIndicator();
              }
            });
      }
    }
    

    Widgets above can be called multiple times in the lifecycle of the app. The user comes to this screen by selecting a place on the initial screen, which executes the code in the PrepareList stateful widget, which in its turn calls the AttendeeList Stateful widget.

    I would like to emphisize that both PrepareList and AttendeeList use streams. And each time the code in these widgets is executed, a large number of nodes (1500 for each widget) are downloaded from database.

    One execution can look as the following:

    PrepareList("London");
    

    And another execution may look as the following, presenting a new list of items on the same screen:

    PrepareList("Manhattan");
    

    And what I observe is:

    When I run PrepareList("London"); for the first time, it takes some time to (3 to 4 seconds) to see the content on the screen. Then I run the PrepareList("Manhattan");, which also takes around 3 to 4 seconds to show the content. But when I run PrepareList("London"); again, the content appears on the screen very quickly, in ~1 second.

    In order to be able to call PrepareList(), I need to go to another screen, which means - in my understanding - that my stream subscription is cancelled each time I leave the screen associated to 2 widgets above. But is it so that the stream itself is NOT cancelled and the data remains in the memory?

    What I suspect: When using the app, as I call PrepareList(...) for multiple places (multiple times), it loads more and more data on the memory and it never cleans it. After a while, the app consumes all available memory and crashes by giving the error above, which tells me nothing meaningful. And as PrepareList(...) is executed more and more when using the app, the Iphone 7 gets heated, which I can easily feel. I even tested with Iphone 12, which does not get heated as Iphone 7 but crashes as well.

    I even tried to add dispose as following to both classes:

     @override
      void dispose() {
        super.dispose();
      }
    

    ... but it still did not help.

    Is my stream implementation true? Is what I suspect can be the underlying problem for this crash?

    Any help would very highly be appreciated!

    ADDENDUM 2:

    I kept trying... I used the app in a way so that PrepareList("..."); is triggered several times. I observed the memory usage in devtools as well. I can observe that the memory usage increases over time. And I got a new error this time saying something more concrete:

    [ServicesDaemonManager] interruptionHandler is called. -[FontServicesDaemonManager connection]_block_invoke
    [tcp] tcp_input [C17.1.1:3] flags=[R] seq=3749683210, ack=0, win=0 state=LAST_ACK rcv_nxt=3749683210, snd_una=3584722489
    [tcp] tcp_input [C17.1.1:3] flags=[R] seq=3749683210, ack=0, win=0 state=CLOSED rcv_nxt=3749683210, snd_una=3584722489
    * thread #46, name = 'DartWorker', stop reason = EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=1450 MB, unused=0x0)
        frame #0: 0x0000000108ef4d0c Flutter`dart::CompilerPass_TypePropagation::DoBody(dart::CompilerPassState*) const + 1644
    Flutter`dart::CompilerPass_TypePropagation::DoBody:
    ->  0x108ef4d0c <+1644>: str    xzr, [x20, x28, lsl #3]
        0x108ef4d10 <+1648>: ldr    x8, [x22, #0x48]
        0x108ef4d14 <+1652>: cmp    x24, x8
        0x108ef4d18 <+1656>: b.ge   0x108ef4d84               ; <+1764>
    Target 0: (Runner) stopped.
    Lost connection to device.
    

    This time, it says EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=1450 MB, unused=0x0). Seemingly, the memory usage is increasing over time. Why is the memory not freed when the user leaves the screen on the phone? (I ask this question by suspecting that the stream still occupies the memory even after the user moves to another screen...)

    • Pip
      Pip about 2 years
      investigating similar issue on Flutter v2.16.1, ios v15.1. Are you using Firebase DB? I've noticed a Firebase query shortly before the error occurs on my end?
    • edn
      edn about 2 years
      @Pip Thanks for sharing your comment. Yes I do use Firebase. It is though impossible to locate where it is happening because it may crash at totally different irrelevant places of the app. That would be great to understand what this error means (e.g. does that mean insufficient memory?) so that I will have an idea about what to look at.
    • kuhnroyal
      kuhnroyal about 2 years
      You won't get an answer like this. You need to provide a lot more info on plugins and probably the full crash log from the device.
    • edn
      edn about 2 years
      @kuhnroyal How do I get the full crash log from the device?
    • kuhnroyal
      kuhnroyal about 2 years
    • edn
      edn about 2 years
      @kuhnroyal I added the crash log from the phone now. Does it tell you anything? Please note that the question exceeded the total number of character limit so I provided the remaining part of the crash in an answer below.
    • edn
      edn about 2 years
      @kuhnroyal I added even more information to my question above. Any chance you can look at it?
    • edn
      edn about 2 years
      @RémiRousselet Any chance you can look at this question?
    • simple_code
      simple_code about 2 years
      EXC_BAD_ACCESS happens when objective C is trying to use an object that has already been freed. It could mean that there are too many UI widgets in memory at the same time? Are you using ListView.builder() or equivalent that recycles UI widgets? Maybe it's a good idea to separate the code where you're getting data from firebase from the code that is listing the data? Maybe you can try to build this to android or android emulator and see if you get a different error? Or maybe build it to web?
    • edn
      edn about 2 years
      @simple_code Thank you for your comment! Yes,I am using ListView.builder in parts of the code and the code that gets the data from Firebase is not necessarily separated from the code that lists the data. An yes, there are quite a number of UI widgets. I will start with trying to separate the Firebase implementation from UI implementation. EXC_BAD_ACCESS only happens occasionally at different part of the UI (not necessarily on the same page), which makes it extremely hard to localize where I should be focusing on. Any other comment or explanation more than welcome!!
  • edn
    edn about 2 years
    Thank you very much for your comments. I did some of them and it was truly helpful. My original problem with EXC_BAD_ACCESS appeared to be related to another problem which I will explain in a separate answer.