Skip to content

Memory leak (retain cycle) in [FIRQuery getDocumentsWithSource:completion:] #2564

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

Closed
ewanmellor opened this issue Mar 15, 2019 · 5 comments
Closed
Assignees
Milestone

Comments

@ewanmellor
Copy link

ewanmellor commented Mar 15, 2019

[REQUIRED] Step 2: Describe your environment

  • Xcode version: 10.1 (10B61)
  • Firebase SDK version: 5.16.0
  • Firebase Component: Firestore (Auth, Core, Database, Firestore, Messaging, Storage, etc)
  • Component version: Firebase/Firestore (5.16.0), FirebaseFirestore (= 1.0.0) from CocoaPods

[REQUIRED] Step 3: Describe the problem

There is a retain cycle identified by Xcode Instruments as shown below.

From Firestore/Source/API/FIRQuery.mm:113:

- (void)getDocumentsWithSource:(FIRFirestoreSource)source
                    completion:(void (^)(FIRQuerySnapshot *_Nullable snapshot,
                                         NSError *_Nullable error))completion {

  // [Snip]

  __block id<FIRListenerRegistration> listenerRegistration;
  FIRQuerySnapshotBlock listener = ^(FIRQuerySnapshot *snapshot, NSError *error) {
    if (error) {
      completion(nil, error);
      return;
    }

    // Remove query first before passing event to user to avoid user actions affecting the
    // now stale query.
    dispatch_semaphore_wait(registered, DISPATCH_TIME_FOREVER);
    [listenerRegistration remove];

    if (snapshot.metadata.fromCache && source == FIRFirestoreSourceServer) {
      completion(nil,  /* [Snip NSError] */);
    } else {
      completion(snapshot, nil);
    }
  };

  listenerRegistration = [self addSnapshotListenerInternalWithOptions:listenOptions
                                                             listener:listener];
  dispatch_semaphore_signal(registered);
}

My reading is as follows:

  1. FIRQuerySnapshotBlock listener is a callback block that is given to addSnapshotListenerInternalWithOptions.
  2. That callback block references __block id<FIRListenerRegistration> listenerRegistration.
  3. listenerRegistration is the result of the addSnapshotListenerInternalWithOptions call, which ultimately contains a reference to listener.
  4. Nothing ever nils listenerRegistration, so there is a retain cycle and a leak once the callback is complete.

I don't know enough about the code here to be sure, but my guess is that setting listenerRegistration = nil after each of the three completion() calls would fix the problem.

Screen Shot 2019-03-14 at 11 48 58 PM

@schmidt-sebastian
Copy link
Contributor

Thanks for sending this over! I will route this internally to the right person and we should be able to get this fixed soon.

Looking at this briefly, the call [listenerRegistration remove] nils out its internal reference to listener, which should break the retain cycle. We do, however, not call [listenerRegistration remove] in the error case. I wonder if this is the case you are hitting.

@ewanmellor
Copy link
Author

@schmidt-sebastian Yes, I think you're right. I've put a breakpoint on there and I'm going through that error path.

FIRQuerySnapshotBlock listener = ^(FIRQuerySnapshot *snapshot, NSError *error) {
    if (error) {
      completion(nil, error);
      return;
    }

@wilhuff wilhuff assigned wilhuff and unassigned rsgowman Mar 15, 2019
@wilhuff
Copy link
Contributor

wilhuff commented Mar 18, 2019

Just FYI, I have managed to reproduce this. It only happens on the error path, but removing the listener isn't the fix since listeners automatically are unregistered when there's an error. I'm working on a fix.

@wilhuff
Copy link
Contributor

wilhuff commented Mar 24, 2019

@ewanmellor I haven't forgotten you, but we have a big effort underway to make this SDK available in C++ and I folded the resolution of this into that effort. Our next release window is coming up and if all goes well these fixes will end up in there. Thanks for your patience.

@wilhuff
Copy link
Contributor

wilhuff commented Mar 26, 2019

Those three fixes have now been submitted and will go out with our next release.

@wilhuff wilhuff closed this as completed Mar 26, 2019
@wilhuff wilhuff added this to the M46 milestone Mar 26, 2019
@firebase firebase locked and limited conversation to collaborators Oct 20, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants