reactiveui / ReactiveUI

An advanced, composable, functional reactive model-view-viewmodel framework for all .NET platforms that is inspired by functional reactive programming. ReactiveUI allows you to abstract mutable state away from your user interfaces, express the idea around a feature in one readable place and improve the testability of your application.
https://www.reactiveui.net
MIT License
8.05k stars 1.12k forks source link

BindCommand unhandled exceptions are swallowed in Xamarin.Android #1859

Closed slajalin closed 5 years ago

slajalin commented 5 years ago

Do you want to request a feature or report a bug?

I'd like to report a bug.

What is the current behavior? We currently have a MVVM solution setup where we bind the same method in our ViewModels to both Xamarin.iOS and Xamarin.Android projects using the BindCommand method. While trying to set up crash reporting, we noticed that the exception handling is different between the two platforms.

Given our Reactive Command is the following: public ReactiveCommand<Unit, Unit> CrashCommand => ReactiveCommand.Create(() => { var someArray = new int[2]; var shouldBreak = arr[3]; });

in iOS debug, we hit a breakpoint and the application outputs the following stacktrace as well quits the app:

ReactiveUI.UnhandledErrorException: An object implementing IHandleObservableErrors (often a ReactiveCommand or ObservableAsPropertyHelper) has errored, thereby breaking its observable pipeline. To prevent this, ensure the pipeline does not error, or Subscribe to the ThrownExceptions property of the object in question to handle the erroneous case. ---> System.IndexOutOfRangeException: Index was outside the bounds of the array.
  at App.ViewModels.TestViewModel+<>c.<get_ForgotCommand>b__26_0 () [0x00008] in /{...}/TestViewModel.cs:43 
  at ReactiveUI.ReactiveCommand+<>c__DisplayClass0_0.<Create>b__1 (System.IObserver`1[T] observer) [0x00000] in <1eb4995f549c481c99087e3c915f4fc8>:0 
  at System.Reactive.AnonymousObservable`1[T].SubscribeCore (System.IObserver`1[T] observer) [0x00000] in <370f6a6bb34048878534065376a195cb>:0 
  at System.Reactive.ObservableBase`1[T].Subscribe (System.IObserver`1[T] observer) [0x00037] in <370f6a6bb34048878534065376a195cb>:0 
   --- End of inner exception stack trace ---

in Android debug, we still hit a breakpoint at the callstack

ReactiveUI.RxApp.<>c.<.cctor>b__11_1(System.IndexOutOfRangeException ex) in 
System.Reactive.AnonymousSafeObserver<System.Exception>.OnNext(System.IndexOutOfRangeException value) in 
System.Reactive.ScheduledObserver<System.Exception>.Run(object state, System.Action<object> recurse) in 
System.Reactive.Concurrency.Scheduler.<object>(object state1) in

but the error gets swallowed and a UnhandledErrorException is never thrown.

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem

  1. Create a solution with three projects: Library, Xamarin.Android, Xamarin.iOS
  2. In the library project create a ReactiveCommand property with a method that will throw an exception
  3. In the mobile projects bind a user interaction (ex: button press) to that ReactiveCommand through the BindCommand method. this.BindCommand(ViewModel, vm => vm.CrashCommand, v => v.CrashButton);
  4. Launch and press the button in each platform

What is the expected behavior? Both platforms should expect the same behavior in terms of handling unexpected exceptions and should throw UnhandledErrorExceptions by default to crash the app.

What is the motivation / use case for changing the behavior?

Which versions of ReactiveUI, and which platform / OS are affected by this issue? Did this work in previous versions of ReativeUI? Please also test with the latest stable and development snapshot

I am able to reproduce the issue using ReactiveUI 9.2.2 & 8.6.3 nugets. iOS simulator: iPhone XS Max - iOS 12.0 , we are targeting iOS 10.0 Android simulator: Nexus 5X API 26, we a minimum supporting Android 5.1 (API 22) & targeting API 28

Other information (e.g. stacktraces, related issues, suggestions how to fix)

Full iOS stacktrace Unhandled Exception: ReactiveUI.UnhandledErrorException: An object implementing IHandleObservableErrors (often a ReactiveCommand or ObservableAsPropertyHelper) has errored, thereby breaking its observable pipeline. To prevent this, ensure the pipeline does not error, or Subscribe to the ThrownExceptions property of the object in question to handle the erroneous case. ---> System.IndexOutOfRangeException: Index was outside the bounds of the array. at MyApp.Shared.ViewModels.MyViewModel+<>c.b__26_0 () [0x00008] in /repos/projects/testApp/MyApp.Shared/ViewModels/MyViewModel.cs:43 at ReactiveUI.ReactiveCommand+<>c__DisplayClass0_0.b__1 (System.IObserver`1[T] observer) [0x00000] in <1eb4995f549c481c99087e3c915f4fc8>:0 at System.Reactive.AnonymousObservable`1[T].SubscribeCore (System.IObserver`1[T] observer) [0x00000] in <370f6a6bb34048878534065376a195cb>:0 at System.Reactive.ObservableBase`1[T].Subscribe (System.IObserver`1[T] observer) [0x00037] in <370f6a6bb34048878534065376a195cb>:0 --- End of inner exception stack trace --- at ReactiveUI.RxApp+<>c__DisplayClass11_0.<.cctor>b__2 () [0x00010] in <1eb4995f549c481c99087e3c915f4fc8>:0 at System.Reactive.Concurrency.Scheduler.Invoke (System.Reactive.Concurrency.IScheduler scheduler, System.Action action) [0x00000] in <370f6a6bb34048878534065376a195cb>:0 at ReactiveUI.NSRunloopScheduler+<>c__DisplayClass2_0`1[TState].b__0 () [0x0000d] in <1eb4995f549c481c99087e3c915f4fc8>:0 at CoreFoundation.DispatchQueue.static_dispatcher_to_managed (System.IntPtr context) [0x00038] in /Library/Frameworks/Xamarin.iOS.framework/Versions/12.0.0.15/src/Xamarin.iOS/CoreFoundation/Dispatch.cs:344 at (wrapper native-to-managed) CoreFoundation.DispatchQueue.static_dispatcher_to_managed(intptr) --- End of stack trace from previous location where exception was thrown --- at (wrapper managed-to-native) UIKit.UIApplication.UIApplicationMain(int,string[],intptr,intptr) at UIKit.UIApplication.Main (System.String[] args, System.IntPtr principal, System.IntPtr delegate) [0x00005] in /Library/Frameworks/Xamarin.iOS.framework/Versions/12.0.0.15/src/Xamarin.iOS/UIKit/UIApplication.cs:79 at UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x0002c] in /Library/Frameworks/Xamarin.iOS.framework/Versions/12.0.0.15/src/Xamarin.iOS/UIKit/UIApplication.cs:63 at MyApp.iOS.Application.Main (System.String[] args) [0x00001] in /repos/projects/testApp/MyApp.iOS/Main.cs:12 2018-12-14 11:41:50.477211-0600 MyApp.iOS[87319:5643554] Unhandled managed exception: An object implementing IHandleObservableErrors (often a ReactiveCommand or ObservableAsPropertyHelper) has errored, thereby breaking its observable pipeline. To prevent this, ensure the pipeline does not error, or Subscribe to the ThrownExceptions property of the object in question to handle the erroneous case. (ReactiveUI.UnhandledErrorException) at ReactiveUI.RxApp+<>c__DisplayClass11_0.<.cctor>b__2 () [0x00010] in <1eb4995f549c481c99087e3c915f4fc8>:0 at System.Reactive.Concurrency.Scheduler.Invoke (System.Reactive.Concurrency.IScheduler scheduler, System.Action action) [0x00000] in <370f6a6bb34048878534065376a195cb>:0 at ReactiveUI.NSRunloopScheduler+<>c__DisplayClass2_0`1[TState].b__0 () [0x0000d] in <1eb4995f549c481c99087e3c915f4fc8>:0 at CoreFoundation.DispatchQueue.static_dispatcher_to_managed (System.IntPtr context) [0x00038] in /Library/Frameworks/Xamarin.iOS.framework/Versions/12.0.0.15/src/Xamarin.iOS/CoreFoundation/Dispatch.cs:344 at (wrapper native-to-managed) CoreFoundation.DispatchQueue.static_dispatcher_to_managed(intptr) --- End of stack trace from previous location where exception was thrown --- at (wrapper managed-to-native) UIKit.UIApplication.UIApplicationMain(int,string[],intptr,intptr) at UIKit.UIApplication.Main (System.String[] args, System.IntPtr principal, System.IntPtr delegate) [0x00005] in /Library/Frameworks/Xamarin.iOS.framework/Versions/12.0.0.15/src/Xamarin.iOS/UIKit/UIApplication.cs:79 at UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x0002c] in /Library/Frameworks/Xamarin.iOS.framework/Versions/12.0.0.15/src/Xamarin.iOS/UIKit/UIApplication.cs:63 at MyApp.iOS.Application.Main (System.String[] args) [0x00001] in /repos/projects/testApp/MyApp.iOS/Main.cs:12 --- inner exception --- Index was outside the bounds of the array. (System.IndexOutOfRangeException) at MyApp.Shared.ViewModels.MyViewModel+<>c.b__26_0 () [0x00008] in /repos/projects/testApp/MyApp.Shared/ViewModels/MyViewModel.cs:43 at ReactiveUI.ReactiveCommand+<>c__DisplayClass0_0.b__1 (System.IObserver`1[T] observer) [0x00000] in <1eb4995f549c481c99087e3c915f4fc8>:0 at System.Reactive.AnonymousObservable`1[T].SubscribeCore (System.IObserver`1[T] observer) [0x00000] in <370f6a6bb34048878534065376a195cb>:0 at System.Reactive.ObservableBase`1[T].Subscribe (System.IObserver`1[T] observer) [0x00037] in <370f6a6bb34048878534065376a195cb>:0 CalabashServer | XTC_SKIP_LPSERVER_TOKEN is not in the app environment CalabashServer | Will start LPServer with identifier: c36c12ce937d044d55d0c208f9d4502868233513 2018-12-14 11:41:50.477821-0600 MyApp.iOS[87319:5643554] critical: Stacktrace: 2018-12-14 11:41:50.477964-0600 MyApp.iOS[87319:5643554] critical: Native stacktrace: 2018-12-14 11:41:50.495532-0600 MyApp.iOS[87319:5643554] critical: 0 MyApp.iOS 0x000000010b806db4 mono_handle_native_crash + 244 2018-12-14 11:41:50.495737-0600 MyApp.iOS[87319:5643554] critical: 1 libsystem_platform.dylib 0x000000011bb15f5a _sigtramp + 26 2018-12-14 11:41:50.495852-0600 MyApp.iOS[87319:5643554] critical: 2 ??? 0x000000000000ffff 0x0 + 65535 2018-12-14 11:41:50.495972-0600 MyApp.iOS[87319:5643554] critical: 3 libsystem_c.dylib 0x000000011b89dc45 abort + 127 2018-12-14 11:41:50.496083-0600 MyApp.iOS[87319:5643554] critical: 4 MyApp.iOS 0x000000010ba23f5f xamarin_unhandled_exception_handler + 47 2018-12-14 11:41:50.496212-0600 MyApp.iOS[87319:5643554] critical: 5 MyApp.iOS 0x000000010b8a477e mono_invoke_unhandled_exception_hook + 158 2018-12-14 11:41:50.496323-0600 MyApp.iOS[87319:5643554] critical: 6 MyApp.iOS 0x000000010b806768 mono_handle_exception_internal + 5976 2018-12-14 11:41:50.496442-0600 MyApp.iOS[87319:5643554] critical: 7 MyApp.iOS 0x000000010b805009 mono_handle_exception + 25 2018-12-14 11:41:50.496552-0600 MyApp.iOS[87319:5643554] critical: 8 MyApp.iOS 0x000000010b784d23 mono_amd64_throw_exception + 131 2018-12-14 11:41:50.496655-0600 MyApp.iOS[87319:5643554] critical: 9 ??? 0x000000013b5c35a7 0x0 + 5290866087 2018-12-14 11:41:50.496769-0600 MyApp.iOS[87319:5643554] critical: 10 MyApp.iOS 0x000000010ba23b27 xamarin_process_managed_exception_gchandle + 55 2018-12-14 11:41:50.496872-0600 MyApp.iOS[87319:5643554] critical: 11 MyApp.iOS 0x000000010ba23ae3 xamarin_ftnptr_exception_handler + 19 2018-12-14 11:41:50.496991-0600 MyApp.iOS[87319:5643554] critical: 12 ??? 0x0000000141e3052b 0x0 + 5400364331 2018-12-14 11:41:50.497091-0600 MyApp.iOS[87319:5643554] critical: 13 libdispatch.dylib 0x000000011b741587 _dispatch_client_callout + 8 2018-12-14 11:41:50.497191-0600 MyApp.iOS[87319:5643554] critical: 14 libdispatch.dylib 0x000000011b74d3bc _dispatch_main_queue_callback_4CF + 1290 2018-12-14 11:41:50.497369-0600 MyApp.iOS[87319:5643554] critical: 15 CoreFoundation 0x00000001193b67f9 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9 2018-12-14 11:41:50.497586-0600 MyApp.iOS[87319:5643554] critical: 16 CoreFoundation 0x00000001193b0e86 __CFRunLoopRun + 2342 2018-12-14 11:41:50.497760-0600 MyApp.iOS[87319:5643554] critical: 17 CoreFoundation 0x00000001193b0221 CFRunLoopRunSpecific + 625 2018-12-14 11:41:50.497965-0600 MyApp.iOS[87319:5643554] critical: 18 GraphicsServices 0x00000001203271dd GSEventRunModal + 62 2018-12-14 11:41:50.498132-0600 MyApp.iOS[87319:5643554] critical: 19 UIKitCore 0x000000011c3b1115 UIApplicationMain + 140 2018-12-14 11:41:50.498279-0600 MyApp.iOS[87319:5643554] critical: 20 ??? 0x000000013f595e7e 0x0 + 5357788798 2018-12-14 11:41:50.498443-0600 MyApp.iOS[87319:5643554] critical: 21 ??? 0x000000013f595c13 0x0 + 5357788179 2018-12-14 11:41:50.498612-0600 MyApp.iOS[87319:5643554] critical: ================================================================= Got a SIGABRT while executing native code. This usually indicates a fatal error in the mono runtime or one of the native libraries used by your application. =================================================================
Full Android callstack ReactiveUI.RxApp.<>c.<.cctor>b__11_1(System.IndexOutOfRangeException ex) in System.Reactive.AnonymousSafeObserver.OnNext(System.IndexOutOfRangeException value) in System.Reactive.ScheduledObserver.Run(object state, System.Action recurse) in System.Reactive.Concurrency.Scheduler.(object state1) in System.Reactive.Concurrency.Scheduler.InvokeRec1(ReactiveUI.HandlerScheduler scheduler, System.Reactive.Concurrency.Scheduler.Pair>> pair) in ReactiveUI.HandlerScheduler.Schedule>>>(System.Reactive.Concurrency.Scheduler.Pair>> state, System.Func>>,System.IDisposable> action) in System.Reactive.Concurrency.Scheduler.Schedule(ReactiveUI.HandlerScheduler scheduler, object state, System.Action> action) in System.Reactive.ScheduledObserver.EnsureActiveSlow() in System.Reactive.ScheduledObserver.EnsureActive(int n) in System.Reactive.ScheduledObserver.EnsureActive() in System.Reactive.ObserveOnObserver.OnNextCore(System.IndexOutOfRangeException value) in System.Reactive.ObserverBase.OnNext(System.IndexOutOfRangeException value) in System.Reactive.Subjects.Subject.OnNext(System.IndexOutOfRangeException value) in ReactiveUI.ScheduledSubject.OnNext(System.IndexOutOfRangeException value) in ReactiveUI.ReactiveCommand.b__18_2(System.IndexOutOfRangeException ex) in System.Reactive.Linq.ObservableImpl.Catch._.OnError(System.IndexOutOfRangeException error) in System.Reactive.Linq.ObservableImpl.Do.OnNext._.OnError(System.IndexOutOfRangeException error) in System.Reactive.Linq.ObservableImpl.Concat._.OnError(System.IndexOutOfRangeException error) in System.Reactive.AutoDetachObserver.OnErrorCore(System.IndexOutOfRangeException exception) in System.Reactive.ObserverBase.Fail(System.IndexOutOfRangeException error) in System.Reactive.ObservableBase.Subscribe(System.Reactive.Linq.ObservableImpl.Concat._ observer) in System.ObservableExtensions.SubscribeSafe(System.Reactive.AnonymousObservable source, System.Reactive.Linq.ObservableImpl.Concat._ observer) in System.Reactive.TailRecursiveSink.MoveNext() in System.Reactive.Concurrency.AsyncLock.Wait(System.Action action) in System.Reactive.TailRecursiveSink.b__7_0(System.Action self) in System.Reactive.Concurrency.Scheduler.<>c.b__47_0(System.Action _action, System.Action> self) in System.Reactive.Concurrency.Scheduler.<>c__DisplayClass49_0 state1) in System.Reactive.Concurrency.Scheduler.InvokeRec1>(System.Reactive.Concurrency.ImmediateScheduler.AsyncLockScheduler scheduler, System.Reactive.Concurrency.Scheduler.Pair,System.Action,System.Action>>> pair) in System.Reactive.Concurrency.ImmediateScheduler.Schedule,System.Action,System.Action>>>>(System.Reactive.Concurrency.Scheduler.Pair,System.Action,System.Action>>> state, System.Func,System.Action,System.Action>>>,System.IDisposable> action) in System.Reactive.Concurrency.Scheduler.Schedule>(System.Reactive.Concurrency.ImmediateScheduler scheduler, System.Action state, System.Action,System.Action>> action) in System.Reactive.Concurrency.Scheduler.Schedule(System.Reactive.Concurrency.ImmediateScheduler scheduler, System.Action action) in System.Reactive.TailRecursiveSink.Run(System.IObservable[] sources) in System.Reactive.Linq.ObservableImpl.Concat.Run(System.Reactive.Linq.ObservableImpl.Concat._ sink) in System.Reactive.Producer._>.SubscribeRaw(System.Reactive.Linq.ObservableImpl.Do.OnNext._ observer, bool enableSafeguard) in System.ObservableExtensions.SubscribeSafe(System.Reactive.Linq.ObservableImpl.Concat source, System.Reactive.Linq.ObservableImpl.Do.OnNext._ observer) in System.Reactive.Linq.ObservableImpl.Do.OnNext.Run(System.Reactive.Linq.ObservableImpl.Do.OnNext._ sink) in System.Reactive.Producer.OnNext._>.SubscribeRaw(System.Reactive.Linq.ObservableImpl.Catch._ observer, bool enableSafeguard) in System.ObservableExtensions.SubscribeSafe(System.Reactive.Linq.ObservableImpl.Do.OnNext source, System.Reactive.Linq.ObservableImpl.Catch._ observer) in System.Reactive.Linq.ObservableImpl.Catch._.Run(System.Reactive.Linq.ObservableImpl.Do.OnNext source) in System.Reactive.Linq.ObservableImpl.Catch.Run(System.Reactive.Linq.ObservableImpl.Catch._ sink) in System.Reactive.Producer._>.SubscribeRaw(System.Reactive.Linq.ObservableImpl.Finally._ observer, bool enableSafeguard) in System.ObservableExtensions.SubscribeSafe(System.Reactive.Linq.ObservableImpl.Catch source, System.Reactive.Linq.ObservableImpl.Finally._ observer) in System.Reactive.Linq.ObservableImpl.Finally._.Run(System.Reactive.Linq.ObservableImpl.Catch source) in System.Reactive.Linq.ObservableImpl.Finally.Run(System.Reactive.Linq.ObservableImpl.Finally._ sink) in System.Reactive.Producer._>.SubscribeRaw(System.Reactive.Linq.ObservableImpl.AsObservable._ observer, bool enableSafeguard) in System.ObservableExtensions.SubscribeSafe(System.Reactive.Linq.ObservableImpl.Finally source, System.Reactive.Linq.ObservableImpl.AsObservable._ observer) in System.Reactive.Linq.ObservableImpl.AsObservable.Run(System.Reactive.Linq.ObservableImpl.AsObservable._ sink) in System.Reactive.Producer._>.SubscribeRaw(System.Reactive.Subjects.AsyncSubject observer, bool enableSafeguard) in System.ObservableExtensions.SubscribeSafe(System.Reactive.Linq.ObservableImpl.AsObservable source, System.Reactive.Subjects.AsyncSubject observer) in System.Reactive.Subjects.ConnectableObservable.Connect() in System.Reactive.Linq.ObservableImpl.RefCount._.Run(System.Reactive.Linq.ObservableImpl.RefCount parent) in System.Reactive.Linq.ObservableImpl.RefCount.Run(System.Reactive.Linq.ObservableImpl.RefCount._ sink) in System.Reactive.Producer._>.SubscribeRaw(System.Reactive.ObserveOnObserver observer, bool enableSafeguard) in System.ObservableExtensions.SubscribeSafe(System.Reactive.Linq.ObservableImpl.RefCount source, System.Reactive.ObserveOnObserver observer) in System.Reactive.Concurrency.ObserveOn.Scheduler.Run(System.Reactive.ObserveOnObserver sink) in System.Reactive.Producer>.SubscribeRaw(System.Reactive.Linq.ObservableImpl.Catch._ observer, bool enableSafeguard) in System.ObservableExtensions.SubscribeSafe(System.Reactive.Concurrency.ObserveOn.Scheduler source, System.Reactive.Linq.ObservableImpl.Catch._ observer) in System.Reactive.TailRecursiveSink.MoveNext() in System.Reactive.Concurrency.AsyncLock.Wait(System.Action action) in System.Reactive.TailRecursiveSink.b__7_0(System.Action self) in System.Reactive.Concurrency.Scheduler.<>c.b__47_0(System.Action _action, System.Action> self) in System.Reactive.Concurrency.Scheduler.<>c__DisplayClass49_0 state1) in System.Reactive.Concurrency.Scheduler.InvokeRec1>(System.Reactive.Concurrency.ImmediateScheduler.AsyncLockScheduler scheduler, System.Reactive.Concurrency.Scheduler.Pair,System.Action,System.Action>>> pair) in System.Reactive.Concurrency.ImmediateScheduler.Schedule,System.Action,System.Action>>>>(System.Reactive.Concurrency.Scheduler.Pair,System.Action,System.Action>>> state, System.Func,System.Action,System.Action>>>,System.IDisposable> action) in System.Reactive.Concurrency.Scheduler.Schedule>(System.Reactive.Concurrency.ImmediateScheduler scheduler, System.Action state, System.Action,System.Action>> action) in System.Reactive.Concurrency.Scheduler.Schedule(System.Reactive.Concurrency.ImmediateScheduler scheduler, System.Action action) in System.Reactive.TailRecursiveSink.Run(System.IObservable[] sources) in System.Reactive.Linq.ObservableImpl.Catch.Run(System.Reactive.Linq.ObservableImpl.Catch._ sink) in System.Reactive.Producer._>.Run(System.Reactive.Concurrency.CurrentThreadScheduler _, System.Reactive.Producer._>.State x) in System.Reactive.Concurrency.ScheduledItem._>.State>.InvokeCore() in System.Reactive.Concurrency.ScheduledItem.Invoke() in System.Reactive.Concurrency.CurrentThreadScheduler.Trampoline.Run(System.Reactive.Concurrency.SchedulerQueue queue) in System.Reactive.Concurrency.CurrentThreadScheduler.Schedule._>.State>(System.Reactive.Producer._>.State state, System.TimeSpan dueTime, System.Func._>.State,System.IDisposable> action) in System.Reactive.Concurrency.LocalScheduler.Schedule._>.State>(System.Reactive.Producer._>.State state, System.Func._>.State,System.IDisposable> action) in System.Reactive.Producer._>.SubscribeRaw(System.Reactive.AnonymousSafeObserver observer, bool enableSafeguard) in System.Reactive.Producer._>.Subscribe(System.Reactive.AnonymousObserver observer) in System.ObservableExtensions.Subscribe(System.Reactive.Linq.ObservableImpl.Catch source) in ReactiveUI.ReactiveCommandBase.ICommandExecute(System.Reactive.Unit parameter) in ReactiveUI.ReactiveCommandBase.System.Windows.Input.ICommand.Execute(object parameter) in ReactiveUI.FlexibleCommandBinder.(System.Reactive.EventPattern e) in System.Reactive.AnonymousSafeObserver>.OnNext(System.Reactive.EventPattern value) in System.Reactive.Subjects.Subject>.OnNext(System.Reactive.EventPattern value) in System.Reactive.Linq.ObservableImpl.FromEventPattern.Handler>.(Android.Support.V7.Widget.AppCompatButton sender, System.EventArgs eventArgs) in Android.Views.View.IOnClickListenerImplementor.OnClick(Android.Support.V7.Widget.AppCompatButton v) in Android.Views.View.IOnClickListenerInvoker.n_OnClick_Landroid_view_View_(System.IntPtr jnienv, System.IntPtr native__this, System.IntPtr native_v) in object.13() in
glennawatson commented 5 years ago

Hi Janet,

Thanks for such a detailed bug report.

Will take a look over the next week or so and see if I can fix it.

RLittlesII commented 5 years ago

@slajalin I will look into your repro steps and logs. In the mean time I would take a look at https://github.com/reactiveui/ReactiveUI/issues/1857, as it seems similar why you're getting an error.

And I'll make a comment on consistent behavior across platforms.

Last week I had an Android bug come back to me because a loading indicator was taking over the UI thread. I didn't add an ObserveOn(RxApp.MainThreadScheduler) to my observable pipeline. On iOS I got no such experience, because iOS handles the Rx code differently. For Android I had to explicitly dictate the thread to fix the problem.

My point is, your Xamarin C# code compiles down to the native platform, and we may not be able to achieve consistency because we aren't guaranteed it from the platforms. If it's achievable we'll work to get there.

Thanks for your thorough issue report.

slajalin commented 5 years ago

@glennawatson @RLittlesII thanks for the fast replies and looking into this.

I'll take a look at #1857 and see if there's any additional steps we might have missed. The difference between iOS and Android is understandable and we tried implementing the DefaultExceptionHandler in Android to natively throw the exception up and force a crash. However, we lost call stack information and it couldn't track down the original error.

Thanks for the help!

glennawatson commented 5 years ago

The reason why @rlittlesii pointed to #1857 is your line

public ReactiveCommand<Unit, Unit> CrashCommand => ReactiveCommand.Create(() => { var someArray = new int[2]; var shouldBreak = arr[3]; });

Should be:

public ReactiveCommand<Unit, Unit> CrashCommand { get; } => ReactiveCommand.Create(() => { var someArray = new int[2]; var shouldBreak = arr[3]; });

Your first approach would recreate a command each time.

The creator of #1857 made the same bug

slajalin commented 5 years ago

Ahh, good catch. I went and updated our constructor so that it's created there only once. However, I was still seeing the same behavior on Android with a breakpoint on the error, but no UnhandledErrorException.

Tried revisiting the DefaultExceptionHandler and overriding it in the Android app:

RxApp.DefaultExceptionHandler = Observer.Create<Exception>((ex) => {
     var exceptionString = "An object implementing IHandleObservableErrors (often a ReactiveCommand or ObservableAsPropertyHelper) has errored, thereby breaking its observable pipeline. To prevent this, ensure the pipeline does not error, or Subscribe to the ThrownExceptions property of the object in question to handle the erroneous case.";
     new Handler(Looper.MyLooper()).Post(() => throw new UnhandledErrorException(exceptionString, ex));
});

Now I'm seeing the exception thrown natively to crash the app and its captured in our reporter, but ideally, this would be the default behavior for Android?

RLittlesII commented 5 years ago

Android is honestly a special snowflake that doesn't play nice with Xamarin Forms at times. Cross platform doesn't mean consistency. It just means it "works" on both natively.

@cabauman may be able to shed a bit more light on the Android behavior as he works with that platform more than I do.

I'll still reproduce this to see if there is anything we can do to provide more consistency.

cabauman commented 5 years ago

Seems to be tied to the Schedule method of AndroidUIScheduler.cs. If I comment out the following part:

if (_looperId > 0 && _looperId == Java.Lang.Thread.CurrentThread().Id)
{
    return action(this, state);
}

then it throws and crashes as expected.

So if its already on the main thread, it invokes the action directly and somehow gets swallowed. But handler.Post always gives the desired behavior.

glennawatson commented 5 years ago

We are building some benchmarks at the moment (and support for running them) to make sure any fix we produce doesn't cause regressions. Also some tests if we can.