Open lorenzofiamingo opened 3 years ago
Hi @lorenzofiamingo, we'll try to reproduce this, and let you know with the updates or findings. It'll also be better if you can share a sample project that I can run locally. Thanks.
Hi @rizafran, you should be able to reproduce it in any kind of project, just pasting the code above at any point, for example after FirebaseApp.configure() in AppDelegate.
Hi @rizafran. The code sample you provided will cause the main thread to block. This is a hazardous thing to do as it will cause your app to become unresponsive while waiting for Firestore to call back, which could take seconds, or even longer if the collection is large.
Firestore's API is specifically designed so that its work, like downloading documents from the server, does not block the main thread. Some of its initialization code (i.e. code that is executed the first time that it connects to the backend) requires that the main thread not be blocked. So by blocking the main thread it is causing Firestore's initialization to time out. Another customer had the same issue and it was resolved by moving the call to wait()
off of the main thread: https://github.com/firebase/firebase-ios-sdk/issues/7615.
I'm going to close this issue because we specifically do not support the use case of blocking the main thread waiting for Firestore to download documents from the server. Please try to restructure your code so that the main thread does not get blocked.
Hi @dconeybe.
The problem remains: setting the dispatchQueue
in the FirestoreSettings
has no effect. If I want to block the main thread for a reason, I should be able to move the asynchronous tasks of Firestore in an another queue.
Causes can be two:
getDocuments
is called not all the asynchronous tasks are sent to the dispatchQueue
of FirestoreSettings
.Firestore.firestore()
has some asynchronous tasks sent in the default FirestoreSetting.dispatchQueue
.If it's case 2 this should exist: Firestore.firestore(app: FirebaseApp, with settings: FirestoreSettings = .default)
in order to set the DispatchQueue before init.
If it's case 1 and you don't want to support that (what's the purpose of FirestoreSettings.dispatchQueue
, then?), I have to say that I cannot change the code, since I'm trying to create a NSIncrementalStore
for Firestore and it works on the DispatchQueue
that CoreData choose for it.
Therefore, you'll never probably be able to adopt async/await
without solving this use case, since an await
would be the perfect replacement for my sample code.
Setting the dispatch queue in FirestoreSettings
specifies the dispatch queue for dispatching user callbacks, such as calling the block of code specified to getDocuments()
. There are other internal implementation details where work is scheduled on the main thread that are not visible outside of the Firestore internals.
I must admit that I'm not personally familiar with NSIncrementalStore
so I don't fully understand why the main thread needs to be blocked in order to use it. But I encourage you to find an alternate way of using it since blocking the main thread is never a good idea. If you could provide a minimal app (e.g. via a GitHub repository) that demonstrates your use case, perhaps we could find an alternative.
As a workaround, would it be possible to make a Firestore call that hits the backend server without blocking the main thread before the problematic Firestore call? That way, the one-time initialization that schedules work on the main thread would have already completed.
Note that usage of async/await would not be blocked by this implementation detail of Firestore because await
does not block the main thread but rather suspends the current path of execution, allowing other code to continue on the main thread. In contrast, calling dispatchGroup.wait()
on the main thread blocks the main thread completely, preventing it from doing any other work.
https://github.com/lorenzofiamingo/FirestoreIncrementalStore
In the db I created two items, they arrive after a 10 seconds timeout. (as shown by the console, not the UI)
Thanks @lorenzofiamingo. I've been able to reproduce with your app. I'm talking to some team members about this issue and hope to reply in the next few days.
@dconeybe Thanks to you.
@lorenzofiamingo Could you provide some additional information about your use case? Any context you could provide could help investigation on our end.
@lorenzofiamingo Could you provide some additional information about your use case? Any context you could provide could help investigation on our end.
I’m trying to get Firestore to work with CoreData. To accomplish that in the best way possible I want to create a reusable and general purpose swift package. I’m exploring two ways:
1) Create a new NSPersistentStore
(NSIncrementalStore
) called NSFirebaseStore
2) Create a NSPersistentContainer
called NSPersitentFirebaseContainer
, as Apple did with CloudKit (NSPersistentCloudKitContainer
)
For the 1) way I was following this.
tl;dr The "internal implementation details where work is scheduled on the main thread" turns out to be in Firebase Auth, where the main dispatch queue is hardcoded for callbacks. I tested out changing these Auth callbacks to instead use a background dispatch queue and it worked perfectly in my isolated test, allowing the code presented in the first comment of this issue to succeed. I'll dig into this a little more.
I found the sources of the internal usages of the main dispatch queue. They all reside in FirebaseAuth:
I tested out a "fix" of replacing all calls to dispatch_get_main_queue()
with calls to dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
and that fixed the "Could not reach Cloud Firestore backend. Backend didn't respond within 10 seconds." error and also allowed the callback specified to getDocuments()
to actually be invoked.
I haven't yet investigated what the side effects would be of this change, and if it invalidates assumptions made elsewhere in the code about the threading model. If we were to go down this route then it would more likely look like providing a parameter/setting to the Auth library whereby a dispatch queue can be configured (rather than hardcoding the main dispatch queue). My expertise lies in Firestore (and not Auth) so I've added the "api: auth" label to this issue to see if someone with expertise in Auth can comment.
One last note before @morganchen12 takes over this issue... talking briefly with the Auth folks yielded this interesting document: https://github.com/firebase/firebase-ios-sdk/blob/master/FirebaseAuth/Docs/threading.md. tl;dr They explicitly decided to use the main dispatch queue for callbacks to make things easy from a concurrency perspective. This is not to say it couldn't be changed (or made to be configurable) but it will require some degree of care to change it correctly and in a backwards-compatible manner.
Hey all, apologies for the delay on this issue. @lorenzofiamingo can you try out the changes in this PR? https://github.com/firebase/firebase-ios-sdk/pull/8288
This will need to go through API review, but in the meantime it'd be good to get your feedback. cc @rosalyntan
Environment
Problem
I need to make a call to Firestore and wait for the query result. Since Firestore make its async calls on the main thread and I need my code to be on the main thread, I have to change the default
dispatchQueue
used by Firestore callbacks, in order to execute them while I'm waiting. When I run the project this is the output:So the code works, since I received the correct snapshot, but I had to wait 10 secs for the timeout of the internet connection (untrue, since the query ran well). My only explanation is that exist a piece of async code inside Firestore APIs that is not performed using the
dispatchQueue
offirestore.settings
(maybe the callback that stops the timer).Steps to reproduce:
Run the following code in a sample project
PS:
firestore.settings.dispatchQueue = DispatchQueue(label: "com.test.my-thread")
doesn't work in order to set the queue.