f-miyu / Plugin.CloudFirestore

MIT License
121 stars 44 forks source link

AddSnapshotListener exception #56

Closed angelru closed 3 years ago

angelru commented 3 years ago
  at Java.Interop.JniEnvironment+InstanceMethods.CallBooleanMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniMethodInfo method) [0x00009] in <bf671abdfa384ce99d758b134b9dd5bf>:0 
  at Android.Runtime.JNIEnv.CallBooleanMethod (System.IntPtr jobject, System.IntPtr jmethod) [0x0000e] in <a2b3cf1c2a854e1cb27453e57f7ea3bc>:0 
  at Java.Util.IIteratorInvoker.get_HasNext () [0x00033] in <a2b3cf1c2a854e1cb27453e57f7ea3bc>:0 
  at System.Linq.Extensions+<ToEnumerator_Dispose>d__5`1[T].MoveNext () [0x00063] in <a2b3cf1c2a854e1cb27453e57f7ea3bc>:0 
  at System.Linq.Enumerable+SelectIListIterator`2[TSource,TResult].MoveNext () [0x00029] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/System.Linq/src/System/Linq/Select.cs:487 
  at App.ViewModels.ListPageViewModel.QuerySnapshot () [0x0020a] in C:\Users\lps\Desktop\Proyectos\App\ViewModels\ListPageViewModel.cs:126 

Code:

       if (querySnapshot != null)
                {
                    lastVisibleDocument = querySnapshot.Documents.LastOrDefault();
                    foreach (var documentChange in querySnapshot.DocumentChanges) 
                    {
                        Message message;
                        switch (documentChange.Type)
                        {
                            case DocumentChangeType.Added:
                                message = documentChange.Document.ToObject<Message>();
                                var user = await Search(message.FromId);
                                message.FromName = user.Name;
                                List.Add(message);
                                break;
                            case DocumentChangeType.Modified:
                                break;
                            case DocumentChangeType.Removed:
                                break;
                        }
                    }
                }

Line 126 foreach (var documentChange in querySnapshot.DocumentChanges)

angelru commented 3 years ago

I have discovered that it has to do with this line, any solution? var user = await Search (message.FromId);

f-miyu commented 3 years ago

Do you mean it works well without that line? What is the code of Search?

angelru commented 3 years ago

@f-miyu https://forums.xamarin.com/discussion/184438/system-argumentexception-handle-must-be-valid

08-20 12:31:42.660 E/mono    (24148): Unhandled Exception:
08-20 12:31:42.660 E/mono    (24148): System.ArgumentException: Handle must be valid.
08-20 12:31:42.660 E/mono    (24148): Parameter name: instance
08-20 12:31:42.660 E/mono    (24148):   at Java.Interop.JniEnvironment+InstanceMethods.CallBooleanMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniMethodInfo method) [0x00009] in <42748fcc36b74733af2d9940a8f3cc8e>:0 
08-20 12:31:42.660 E/mono    (24148):   at Android.Runtime.JNIEnv.CallBooleanMethod (System.IntPtr jobject, System.IntPtr jmethod) [0x0000e] in <227a96d68a0440cea172be41b1306654>:0 
08-20 12:31:42.660 E/mono    (24148):   at Java.Util.IIteratorInvoker.get_HasNext () [0x00033] in <227a96d68a0440cea172be41b1306654>:0 
08-20 12:31:42.660 E/mono    (24148):   at System.Linq.Extensions+<ToEnumerator_Dispose>d__5`1[T].MoveNext () [0x00063] in <227a96d68a0440cea172be41b1306654>:0 
08-20 12:31:42.660 E/mono    (24148):   at System.Linq.Enumerable+SelectIListIterator`2[TSource,TResult].MoveNext () [0x00029] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/System.Linq/src/System/Linq/Select.cs:487 
08-20 12:31:42.660 E/mono    (24148):   at System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.<ThrowAsync>b__7_1 (System.Object state) [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs:1037 
08-20 12:31:42.660 E/mono    (24148):   at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context (System.Object state) [0x0000d] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:1370 
08-20 12:31:42.660 E/mono    (24148):   at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00071] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/referencesource/mscorlib/system/threading/executioncontext.cs:968 
08-20 12:31:42.660 E/mono    (24148):   at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/referencesource/mscorlib/system/threading/executioncontext.cs:910 
08-20 12:31:42.660 E/mono    (24148):   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem () [0x00021] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:1341 
08-20 12:31:42.660 E/mono    (24148):   at System.Threading.ThreadPoolWorkQueue.Dispatch () [0x00074] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:899 
08-20 12:31:42.660 E/mono    (24148):   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback () [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:1261 
08-20 12:31:42.662 E/mono-rt (24148): [ERROR] FATAL UNHANDLED EXCEPTION: System.ArgumentException: Handle must be valid.
08-20 12:31:42.662 E/mono-rt (24148): Parameter name: instance
08-20 12:31:42.662 E/mono-rt (24148):   at Java.Interop.JniEnvironment+InstanceMethods.CallBooleanMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniMethodInfo method) [0x00009] in <42748fcc36b74733af2d9940a8f3cc8e>:0 
08-20 12:31:42.662 E/mono-rt (24148):   at Android.Runtime.JNIEnv.CallBooleanMethod (System.IntPtr jobject, System.IntPtr jmethod) [0x0000e] in <227a96d68a0440cea172be41b1306654>:0 
08-20 12:31:42.662 E/mono-rt (24148):   at Java.Util.IIteratorInvoker.get_HasNext () [0x00033] in <227a96d68a0440cea172be41b1306654>:0 
08-20 12:31:42.662 E/mono-rt (24148):   at System.Linq.Extensions+<ToEnumerator_Dispose>d__5`1[T].MoveNext () [0x00063] in <227a96d68a0440cea172be41b1306654>:0 
08-20 12:31:42.662 E/mono-rt (24148):   at System.Linq.Enumerable+SelectIListIterator`2[TSource,TResult].MoveNext () [0x00029] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/System.Linq/src/System/Linq/Select.cs:487 
08-20 12:31:42.662 E/mono-rt (24148):   at System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.<ThrowAsync>b__7_1 (System.Object state) [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs:1037 
08-20 12:31:42.662 E/mono-rt (24148):   at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context (System.Object state) [0x0000d] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:1370 
08-20 12:31:42.662 E/mono-rt (24148):   at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00071] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/referencesource/mscorlib/system/threading/executioncontext.cs:968 
08-20 12:31:42.662 E/mono-rt (24148):   at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/referencesource/mscorlib/system/threading/executioncontext.cs:910 
08-20 12:31:42.662 E/mono-rt (24148):   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem () [0x00021] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:1341 
08-20 12:31:42.662 E/mono-rt (24148):   at System.Threading.ThreadPoolWorkQueue.Dispatch () [0x00074] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:899 
08-20 12:31:42.662 E/mono-rt (24148):   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback () [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:1261 
     private async Task<string> Search(string id)
        {
           var user = await FirebaseService.UserAsync(id);
           users.Add(id, user.Name);
           return user.Name;
        }

yes it works fine without that line

f-miyu commented 3 years ago

How is AddSnapshotListener called? Please provide this code in detail.

angelru commented 3 years ago

private IQuerySnapshot querySnapshot;

        CrossCloudFirestore.Current.Instance.GetCollection(Collection)
                               .AddSnapshotListener((snapshot, error) =>
                               {
                                   querySnapshot = snapshot;
                                   QuerySnapshot();
                               });
private async void QuerySnapshot()
  if (querySnapshot != null)
                {
                    lastVisibleDocument = querySnapshot.Documents.LastOrDefault();
                    foreach (var documentChange in querySnapshot.DocumentChanges) 
                    {
                        Message message;
                        switch (documentChange.Type)
                        {
                            case DocumentChangeType.Added:
                                message = documentChange.Document.ToObject<Message>();
                                var user = await Search(message.FromId);
                                message.FromName = user;
                                List.Add(message);
                                break;
                            case DocumentChangeType.Modified:
                                break;
                            case DocumentChangeType.Removed:
                                break;
                        }
                    }
                }
}
     private async Task<string> Search(string id)
        {
           var user = await FirebaseService.UserAsync(id);
           users.Add(id, user.Name);
           return user.Name;
        }

do I do something wrong? could it be a xamarin or c# thing? I have to say that the exception only happens sometimes, I think it may be memory issues one way to reproduce it is to enter and exit many times on the page, where the code is.

f-miyu commented 3 years ago

Try the following.

CrossCloudFirestore.Current.Instance.GetCollection(Collection)
                              .AddSnapshotListener((snapshot, error) =>
                              {
                                  QuerySnapshot(snapshot);
                              });
private async void QuerySnapshot(IQuerySnapshot querySnapshot)
if (querySnapshot != null)
             {
                 lastVisibleDocument = querySnapshot.Documents.LastOrDefault();
                 foreach (var documentChange in querySnapshot.DocumentChanges) 
                 {
                     Message message;
                     switch (documentChange.Type)
                     {
                         case DocumentChangeType.Added:
                             message = documentChange.Document.ToObject<Message>();
                             var user = await Search(message.FromId);
                             message.FromName = user;
                             List.Add(message);
                             break;
                         case DocumentChangeType.Modified:
                             break;
                         case DocumentChangeType.Removed:
                             break;
                     }
                 }
             }
}
angelru commented 3 years ago

@f-miyu the same thing keeps happening, I do not understand why

08-20 12:31:42.662 E/mono-rt (24148): at System.Linq.Extensions+<ToEnumerator_Dispose>d__51[T].MoveNext () [0x00063] in <227a96d68a0440cea172be41b1306654>:0 `

will it have something to do with it?

f-miyu commented 3 years ago

How about this?

private async void QuerySnapshot(IQuerySnapshot querySnapshot)
{
    if (querySnapshot != null) 
    {
        lastVisibleDocument = querySnapshot.Documents.LastOrDefault();

        var messages = await Task.WhenAll(
            querySnapshot.DocumentChanges
                .Where(change => change.Type == DocumentChangeType.Added)
                .Select(async change =>
                {
                    var message = change.Document.ToObject<Message>();
                    var user = await Search(message.FromId);
                    message.FromName = user.Name;
                    return message;
                }));

        List.AddRange(messages);
    }
}
angelru commented 3 years ago

@f-miyu It seems that it works well, although I will do more tests, this exception occurred because the task was not completed well? or because the ienumerable is not async? as can be done in c # 8.0 https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/november/csharp-iterating-with-async-enumerables-in-csharp-8, when i test further i will put a comment.

f-miyu commented 3 years ago

I'm not sure, but I think the cause is data race. Therefore, all DocumentChanges data are accessed synchronously.

angelru commented 3 years ago

thanks!

angelru commented 3 years ago

@f-miyu I have detected that within the AddSnapshotListener event I cannot check if an element exists within a list, for example:

private readonly Dictionary<string, string> users = new Dictionary<string, string>();

chatMessagesListener = CrossCloudFirestore.Current.Instance.GetCollection(Settings.ChatMessagesCollection)
                               .GetDocument(_chatRoom.ChatId)
                               .GetCollection(Settings.MessagesCollection)
                               .OrderBy(Settings.CreatedAt, false)
                               .AddSnapshotListener((snapshot, error) =>
                               {
                                            QuerySnapshot(snapshot);
                               });

private async void QuerySnapshot(IQuerySnapshot querySnapshot)
{
    if (snapshot!= null) 
    {
        lastVisibleDocument = snapshot.Documents.LastOrDefault();

        var messages = await Task.WhenAll(
            querySnapshot.DocumentChanges
                .Where(change => change.Type == DocumentChangeType.Added)
                .Select(async change =>
                {
                    var message = change.Document.ToObject<Message>();
                    var user = await Search(message.FromId);
                    message.FromName = user.Name;
                    return message;
                }));

        List.AddRange(messages);
    }
}

   private async Task<string> Search(string id)
        {
           string name;
           if (users.ContainsKey(id))
           {
              name = users[id];
           } 
        else 
         {
           var user = await FirebaseService.UserAsync(id);
           users.Add(id, user.Name);
           name = user.Name;
          }
          return name;
        }

in the search method it always goes to the else then the exception is thrown because the added id exists

f-miyu commented 3 years ago

You should use ConcurrentDictionary for thread-safe.

private readonly ConcurrentDictionary<string, Task<string>> users = new ConcurrentDictionary<string, Task<string>>();

private Task<string> Search(string id)
{
    return users.GetOrAdd(id, async id =>
    {
        var user = await FirebaseService.UserAsync(id);
        return user.Name;
    });
}
angelru commented 3 years ago

This is fantastic, I didn't know it, thank you very much for everything and especially for this great library :)