Skip to content
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

Improve JavaProxyThrowable integration #1198

Closed
jonpryor opened this issue Jan 17, 2018 · 1 comment · Fixed by #8185
Closed

Improve JavaProxyThrowable integration #1198

jonpryor opened this issue Jan 17, 2018 · 1 comment · Fixed by #8185
Assignees
Labels
Area: Mono.Android Issues with the Android API binding (Mono.Android.dll).

Comments

@jonpryor
Copy link
Member

Note: This should be done in the Java.Interop repo, and then we should ditch Android.Runtime.JavaProxyThrowable in favor of Java.Interop.JavaProxyThrowable.

Related: Issue #1188.

It appears that JavaProxyThrowable doesn't integrate well with Java: if Java code calls Throwable.printStackTrace(), it might not also print -- or might truncate -- the output of Throwable.getMessage(), and Throwable.getMessage() is where JavaProxyThrowable squirrels away the "originating" System.Exception stack trace.

We should update JavaProxyThrowable so that it uses Throwable.setStackTrace() to merge or replace the System.Exception stack frames in with the Throwable stack frames.

I believe that this will in turn improve integration with e.g. HockeyApp and other apps which utilize Throwable.printStackTrace().

@jonpryor jonpryor added the Area: App Runtime Issues in `libmonodroid.so`. label Jan 17, 2018
@jonpryor jonpryor added this to the d15-8 milestone Jan 17, 2018
@jonpryor jonpryor modified the milestones: d15-8, d15-9 Apr 30, 2018
@jonpryor jonpryor modified the milestones: d15-9, d16-1 Dec 15, 2018
@pjcollins pjcollins modified the milestones: d16-1, Under Consideration Jun 20, 2019
jonpryor added a commit that referenced this issue Apr 26, 2021
Xamarin.Android v11.3.0 will be shipped as part of
Visual Studio 2019 16.10 Preview 3 and subsequent releases.

Changes: mono/debugger-libs@8bf09ce...08268b7

  * mono/debugger-libs@08268b7: Add Module.ApplyChanges (#342)
  * mono/debugger-libs@1b5a55a: Fix assembly version after converting projects to SDK style

Changes: dotnet/java-interop@f9faaab...f39db25

  * dotnet/java-interop@f39db25c: Branching for d16-10.
  * dotnet/java-interop@f4e68b5b: [generator] Separate metadata fixup step from parsing step. (#822)

Changes: xamarin/monodroid@76c04cd...69e9111

  * xamarin/monodroid@69e911139: Branching for d16-10.
  * xamarin/monodroid@9f9ee378c: [tools/msbuild] Missing translations for XA0135 (#1198)

Changes: dotnet/android-tools@d92fc3e...c5732a0

  * dotnet/android-tools@c5732a0: [Xamarin.Android.Tools.AndroidSdk] Support Microsoft Dist JDK (#117)
  * dotnet/android-tools@52ef989: [Xamarin.Android.Tools.AndroidSdk] Fix CS0168 warning (#116)
jonpryor added a commit that referenced this issue Apr 26, 2021
Xamarin.Android v11.3.0 will be shipped as part of
Visual Studio 2019 16.10 Preview 3 and subsequent releases.

Changes: mono/debugger-libs@8bf09ce...08268b7

  * mono/debugger-libs@08268b7: Add Module.ApplyChanges (#342)
  * mono/debugger-libs@1b5a55a: Fix assembly version after converting projects to SDK style

Changes: dotnet/java-interop@f9faaab...f39db25

  * dotnet/java-interop@f39db25c: Branching for d16-10.
  * dotnet/java-interop@f4e68b5b: [generator] Separate metadata fixup step from parsing step. (#822)

Changes: xamarin/monodroid@76c04cd...69e9111

  * xamarin/monodroid@69e911139: Branching for d16-10.
  * xamarin/monodroid@9f9ee378c: [tools/msbuild] Missing translations for XA0135 (#1198)

Changes: dotnet/android-tools@d92fc3e...c5732a0

  * dotnet/android-tools@c5732a0: [Xamarin.Android.Tools.AndroidSdk] Support Microsoft Dist JDK (#117)
  * dotnet/android-tools@52ef989: [Xamarin.Android.Tools.AndroidSdk] Fix CS0168 warning (#116)
jonpryor pushed a commit that referenced this issue Jun 10, 2021
Changes: xamarin/monodroid@76c04cd...1f2ce15

  * xamarin/monodroid@1f2ce1562: [tools/msbuild] only run _GetPrimaryCpuAbi for Fast Dev (#1208)
  * xamarin/monodroid@691310ede: Bump android-sdk-installer to use Mono.Unix (#1207)
  * xamarin/monodroid@48843fcb2: [tools/msbuild] <GetPrimaryCpuAbi/> selects backup RIDs for .NET 6 (#1205)
  * xamarin/monodroid@4af48f54b: [tools/fastdev] Add error checking when writing data to disk. (#1204)
  * xamarin/monodroid@65b7b2dd4: [tools/fastdev] Rework Unix timestamp calculation code in xamarin.find. (#1202)
  * xamarin/monodroid@401f170e9: [build] fix `dotnet tool install` command (#1203)
  * xamarin/monodroid@a879b250b: Bump to 032d840, xamarin/androidtools@355d015 (#1199)
  * xamarin/monodroid@9f9ee378c: [tools/msbuild] Missing translations for XA0135 (#1198)

Changes: dotnet/android-tools@683f375...49936d6

  * dotnet/android-tools@49936d6: Merge pull request #124 from xamarin/update-libzipsharp
  * dotnet/android-tools@ef78dfc: Bump LibZipSharp to 2.0.0-alpha6
  * dotnet/android-tools@e3d708c: [BaseTasks] fix `\`-delimited paths on macOS (#122)
  * dotnet/android-tools@bdcf899: Reference the new Mono.Unix nuget (#123)
  * dotnet/android-tools@90d7621: [BaseTasks] add ABI detection for RIDs (#121)
  * dotnet/android-tools@79e3b97: [JdkInfo] handle invalid XML from /usr/libexec/java_home (#120)
  * dotnet/android-tools@81519fe: Add SECURITY.md (#119)


Xamarin.Android uses the Mono's Mono.Posix assembly on Unix machines
to perform tasks not possible with BCL classes, provided by the
[`Mono.Posix.NETStandard` NuGet][0].  The `Mono.Posix` source has been
extracted into the [mono/mono.posix repo][1], which is used to build
the new [`Mono.Unix` NuGet package][2].

Update the xamarin/xamarin-android repo -- and various dependencies --
to use the `Mono.Unix` package instead of `Mono.Posix.NETStandard`.
This includes the [`Xamarin.LibZipSharp` NuGet][3], as of
dotnet/android-libzipsharp@cf5e33c6.

`Mono.Unix` no longer uses the older `libMonoPosixHelper` dynamic
library, replaced by a new `libMono.Unix` native library.
Unfortunately, this change broke a number of tests since the
`Mono.Unix.dll` assembly was no longer able to find its companion
native shared library.  While the `Mono.Posix.NETStandard` NuGet
package provides the `libMonoPosixHelper` native library, in practice
the *actual* `libMonoPosixHelper` that was used was the "system"
library included with the system mono.

`Mono.Unix`'s new native helper library, however, must be taken from
the NuGet and both the Mono and dotnet runtimes must be told where to
load the library from once a P/Invoke into `Mono.Unix` is encountered
in managed code.  The native library is copied from the NuGet to the
referencing application's output directory and it should be loaded from
there.  This proved to be easy for the "legacy" Mono: a simple
[`dllmap` configuration][4] and everything works as it should.

With `dotnet` however, dllmap doesn't work.  `dotnet` has instead a
number of mechanisms to configure where the native libraries can be
found (5 I think).  Unfortunately, the mechanisms either require that
a main executable of the application calls the APIs on entry (e.g. in
the `Main()` method) or that a JSON configuration file is provided for
the application, telling the runtime where the native libraries reside.
In case of `Xamarin.Android.Build.Tasks` there is no main executable we
can configure, since it works in the MSBuild context, providing tasks
and utilities to build Xamarin.Android apps.  In this instance, `dotnet`
could be persuaded to find the libraries by calling one of the 5 APIs.
The problem with this approach, however, is that this action would have
to be performed at **every** possible entry point to the
`Xamarin.Android.Build.Tasks` assembly, since any of them could be used
as the first one.  While certainly possible, it would be both fragile
and an unnecessary maintenance burden.  Instead, a simpler (albeit a
bit kludgy) solution was chosen: the `src/Xamarin.Android.Build.Tasks`
build process now takes care of generating a fat (multi-architecture)
binary for macOS hosts (including `x86-64` and `arm64` architectures)
using the `lipo` utility, then it copies the resulting binary to the
same directory where `Xamarin.Android.Build.Tasks.dll` and
`Mono.Unix.dll` live.  The Linux shared library is also copied to the
same location.  The `dotnet` runtime is able to find and load shared
libraries that are in the same directory as the assembly that needs
them and everything works as expected.

[0]: https://www.nuget.org/packages/Mono.Posix.NETStandard/5.20.1-preview
[1]: https://github.com/mono/mono.posix
[2]: https://www.nuget.org/packages/Mono.Unix/
[3]: https://www.nuget.org/packages/Xamarin.LibZipSharp
[4]: https://www.mono-project.com/docs/advanced/pinvoke/dllmap/
@knocte
Copy link
Contributor

knocte commented Feb 20, 2022

No plans to get this worked on? :( I get from time to time crashes that are only seen in GooglePlayConsole which cannot be captured by AppDomain.CurrentDomain.UnhandledException event. Here's an example:

android.runtime.JavaProxyThrowable
android.runtime.JavaProxyThrowable: at Java.Interop.JniEnvironment+InstanceMethods.CallObjectMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x0006e] in <52f10d87dbbb4d09a29042ac70363317>:0
at Java.Interop.JniPeerMembers+JniInstanceMethods.InvokeVirtualObjectMethod (System.String encodedMember, Java.Interop.IJavaPeerable self, Java.Interop.JniArgumentValue* parameters) [0x0002a] in <52f10d87dbbb4d09a29042ac70363317>:0
at AndroidX.AppCompat.App.AlertDialog+Builder.Create () [0x0000a] in <6d3c6b73981a41788429b90c0b0983c0>:0
at Xamarin.Forms.Platform.Android.PopupManager+PopupRequestHelper+DialogBuilder.Create () [0x00008] in <04c545f414d24a37af95d995791bb9a9>:0
at Xamarin.Forms.Platform.Android.PopupManager+PopupRequestHelper.OnAlertRequested (Xamarin.Forms.Page sender, Xamarin.Forms.Internals.AlertArguments arguments) [0x00028] in <04c545f414d24a37af95d995791bb9a9>:0
at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&)
at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x0006a] in <5229225a94454bc59c40710c26e93043>:0
at [email protected] (System.Runtime.ExceptionServices.ExceptionDispatchInfo edi) [0x00000] in <f1f01bf047f43136b90da0590c91572c>
at Microsoft.FSharp.Control.Trampoline.Execute (Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] firstAction) [0x0009c] in <f1f01bf047f43136b90da0590c91572c>:0
at Microsoft.FSharp.Control.TrampolineHolder.ExecuteWithTrampoline (Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] firstAction) [0x00031] in <f1f01bf047f43136b90da0590c91572c>:0
at <StartupCode$FSharp-Core>[email protected] (System.Object o) [0x00017] in <f1f01bf047f43136b90da0590c91572c>:0
at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context (System.Object state) [0x00007] in <5229225a94454bc59c40710c26e93043>:0
at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00071] in <5229225a94454bc59c40710c26e93043>:0
at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <5229225a94454bc59c40710c26e93043>:0
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem () [0x00021] in <5229225a94454bc59c40710c26e93043>:0
at System.Threading.ThreadPoolWorkQueue.Dispatch () [0x00074] in <5229225a94454bc59c40710c26e93043>:0
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback () [0x00000] in <5229225a94454bc59c40710c26e93043>:0
  at mono.java.lang.RunnableImplementor.n_run (Native Method)
  at mono.java.lang.RunnableImplementor.run (RunnableImplementor.java:30)
  at android.os.Handler.handleCallback (Handler.java:938)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loopOnce (Looper.java:201)
  at android.os.Looper.loop (Looper.java:288)
  at android.app.ActivityThread.main (ActivityThread.java:7839)
  at java.lang.reflect.Method.invoke (Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:548)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1003)

knocte added a commit to nblockchain/geewallet that referenced this issue Feb 20, 2022
This was observed on Android[1], might have been introduced in this commit:
e1e0531

[1] Can only be reproduced when the date is modified, but not when
the date is not modified (and thus the "date has not been modified"
dialog kicks in). And somehow we're not getting a Sentry report on
this (likely because of dotnet/android#1198).
There's a Java exception logged in GooglePlayConsole that may be
related to this crash:

```
android.runtime.JavaProxyThrowable
android.runtime.JavaProxyThrowable: at Java.Interop.JniEnvironment+InstanceMethods.CallObjectMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x0006e] in <52f10d87dbbb4d09a29042ac70363317>:0
at Java.Interop.JniPeerMembers+JniInstanceMethods.InvokeVirtualObjectMethod (System.String encodedMember, Java.Interop.IJavaPeerable self, Java.Interop.JniArgumentValue* parameters) [0x0002a] in <52f10d87dbbb4d09a29042ac70363317>:0
at AndroidX.AppCompat.App.AlertDialog+Builder.Create () [0x0000a] in <6d3c6b73981a41788429b90c0b0983c0>:0
at Xamarin.Forms.Platform.Android.PopupManager+PopupRequestHelper+DialogBuilder.Create () [0x00008] in <04c545f414d24a37af95d995791bb9a9>:0
at Xamarin.Forms.Platform.Android.PopupManager+PopupRequestHelper.OnAlertRequested (Xamarin.Forms.Page sender, Xamarin.Forms.Internals.AlertArguments arguments) [0x00028] in <04c545f414d24a37af95d995791bb9a9>:0
at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&)
at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x0006a] in <5229225a94454bc59c40710c26e93043>:0
at [email protected] (System.Runtime.ExceptionServices.ExceptionDispatchInfo edi) [0x00000] in <f1f01bf047f43136b90da0590c91572c>
at Microsoft.FSharp.Control.Trampoline.Execute (Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] firstAction) [0x0009c] in <f1f01bf047f43136b90da0590c91572c>:0
at Microsoft.FSharp.Control.TrampolineHolder.ExecuteWithTrampoline (Microsoft.FSharp.Core.FSharpFunc`2[T,TResult] firstAction) [0x00031] in <f1f01bf047f43136b90da0590c91572c>:0
at <StartupCode$FSharp-Core>[email protected] (System.Object o) [0x00017] in <f1f01bf047f43136b90da0590c91572c>:0
at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context (System.Object state) [0x00007] in <5229225a94454bc59c40710c26e93043>:0
at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00071] in <5229225a94454bc59c40710c26e93043>:0
at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <5229225a94454bc59c40710c26e93043>:0
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem () [0x00021] in <5229225a94454bc59c40710c26e93043>:0
at System.Threading.ThreadPoolWorkQueue.Dispatch () [0x00074] in <5229225a94454bc59c40710c26e93043>:0
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback () [0x00000] in <5229225a94454bc59c40710c26e93043>:0
  at mono.java.lang.RunnableImplementor.n_run (Native Method)
  at mono.java.lang.RunnableImplementor.run (RunnableImplementor.java:30)
  at android.os.Handler.handleCallback (Handler.java:938)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loopOnce (Looper.java:201)
  at android.os.Looper.loop (Looper.java:288)
  at android.app.ActivityThread.main (ActivityThread.java:7839)
  at java.lang.reflect.Method.invoke (Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:548)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1003)
```
@grendello grendello self-assigned this Jul 12, 2023
grendello added a commit to grendello/xamarin-android that referenced this issue Jul 12, 2023
Fixes: dotnet#1198

Given an instance of `System.Exception`, we can use the
`System.Diagnostics.StackTrace` class to iterate over the list of
managed stack frames, translating each of them to equivalent
`Java.Lang.StackTraceElement` and afterwards passing the entire
collection to `Java.Lang.Throwable.SetStackTrace`.
@grendello grendello added Area: Mono.Android Issues with the Android API binding (Mono.Android.dll). and removed Area: App Runtime Issues in `libmonodroid.so`. labels Jul 12, 2023
jonpryor pushed a commit that referenced this issue Aug 23, 2023
…8185)

Context: #1198
Context: #1188 (comment)
Context: #4877
Context: #4927 (comment)

What happens with unhandled exceptions?

	throw new InvalidOperationException ("oops!");

This is a surprisingly complicated question:

If this happens when a debugger is attached, the debugger will get a
"first chance notification" at the `throw` site.  If execution
continues, odds are high that the app will abort if there is a JNI
transition in the callstack.

If no debugger is attached, then it depends on which thread threw the
unhandled exception.

If the thread which threw the unhandled exception is a .NET Thread:

	static void ThrowFromAnotherManagedThread() {
	    var t = new System.Threading.Thread(() => {
	        throw new new Java.Lang.Error ("from another thread?!");
	    });
	    t.Start ();
	    t.Join ();
	}

Then .NET will report the unhandled exception, *and*
the app will restart:

	F mono-rt : [ERROR] FATAL UNHANDLED EXCEPTION: System.InvalidOperationException: oops!
	F mono-rt :    at android_unhandled_exception.MainActivity.<>c.<ThrowFromAnotherManagedThread>b__1_0()
	F mono-rt :    at System.Threading.Thread.StartCallback()
	# app restarts

If the thread which threw the unhandled exception is a *Java* thread,
which could be the UI thread (e.g. thrown from an `Activity.OnCreate()`
override) or via a `Java.Lang.Thread` instance:

	static void ThrowFromAnotherJavaThread() {
	    var t = new Java.Lang.Thread(() => {
	        throw new InvalidOperationException ("oops!");
	    });
	    t.Start ();
	    t.Join ();
	}

Then .NET will report the unhandled exception, *and* the app will
*not* restart (which differs from using .NET threads):

	E AndroidRuntime: Process: com.companyname.android_unhandled_exception, PID: 5436
	E AndroidRuntime: android.runtime.JavaProxyThrowable: System.InvalidOperationException: oops!
	E AndroidRuntime:    at android_unhandled_exception.MainActivity.<>c.<ThrowFromAnotherJavaThread>b__2_0()
	E AndroidRuntime:    at Java.Lang.Thread.RunnableImplementor.Run()
	E AndroidRuntime:    at Java.Lang.IRunnableInvoker.n_Run(IntPtr , IntPtr )
	E AndroidRuntime:    at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V , IntPtr , IntPtr )
	E AndroidRuntime:        at mono.java.lang.RunnableImplementor.n_run(Native Method)
	E AndroidRuntime:        at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
	E AndroidRuntime:        at java.lang.Thread.run(Thread.java:1012)
	I MonoDroid: Android.Runtime.JavaProxyThrowable: Exception_WasThrown, Android.Runtime.JavaProxyThrowable
	I MonoDroid: 
	I MonoDroid:   --- End of managed Android.Runtime.JavaProxyThrowable stack trace ---
	I MonoDroid: android.runtime.JavaProxyThrowable: System.InvalidOperationException: oops!
	I MonoDroid:    at android_unhandled_exception.MainActivity.<>c.<ThrowFromAnotherJavaThread>b__2_0()
	I MonoDroid:    at Java.Lang.Thread.RunnableImplementor.Run()
	I MonoDroid:    at Java.Lang.IRunnableInvoker.n_Run(IntPtr , IntPtr )
	I MonoDroid:    at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V , IntPtr , IntPtr )
	I MonoDroid:     at mono.java.lang.RunnableImplementor.n_run(Native Method)
	I MonoDroid:     at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
	I MonoDroid:     at java.lang.Thread.run(Thread.java:1012)
	I MonoDroid: 
	I MonoDroid:   --- End of managed Android.Runtime.JavaProxyThrowable stack trace ---
	I MonoDroid: android.runtime.JavaProxyThrowable: System.InvalidOperationException: oops!
	I MonoDroid:    at android_unhandled_exception.MainActivity.<>c.<ThrowFromAnotherJavaThread>b__2_0()
	I MonoDroid:    at Java.Lang.Thread.RunnableImplementor.Run()
	I MonoDroid:    at Java.Lang.IRunnableInvoker.n_Run(IntPtr , IntPtr )
	I MonoDroid:    at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V , IntPtr , IntPtr )
	I MonoDroid:     at mono.java.lang.RunnableImplementor.n_run(Native Method)
	I MonoDroid:     at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
	I MonoDroid:     at java.lang.Thread.run(Thread.java

This "works", until we enter the world of crash logging for later
diagnosis and fixing.  The problem with our historical approach is
that we would "stuff" the .NET stack trace into the "message" of the
Java-side `Throwable` instance, and the "message" may not be
transmitted as part of the crash logging!

(This is noticeable by the different indentation levels for the
`at …` lines in the crash output.  Three space indents are from the
`Throwable.getMessage()` output, while four space indents are from
the Java-side stack trace.)

We *think* that we can improve this by replacing the Java-side stack
trace with a "merged" stack trace which includes both the Java-side
and .NET-side stack traces.  This does nothing for unhandled exceptions
on .NET threads, but does alter the output from Java threads:

	E AndroidRuntime: FATAL EXCEPTION: Thread-3
	E AndroidRuntime: Process: com.companyname.android_unhandled_exception, PID: 12321
	E AndroidRuntime: android.runtime.JavaProxyThrowable: [System.InvalidOperationException]: oops!
	E AndroidRuntime:        at android_unhandled_exception.MainActivity+<>c.<ThrowFromAnotherJavaThread>b__2_0(Unknown Source:0)
	E AndroidRuntime:        at Java.Lang.Thread+RunnableImplementor.Run(Unknown Source:0)
	E AndroidRuntime:        at Java.Lang.IRunnableInvoker.n_Run(Unknown Source:0)
	E AndroidRuntime:        at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(Unknown Source:0)
	E AndroidRuntime:        at mono.java.lang.RunnableImplementor.n_run(Native Method)
	E AndroidRuntime:        at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
	E AndroidRuntime:        at java.lang.Thread.run(Thread.java:1012)
	I MonoDroid: UNHANDLED EXCEPTION:
	I MonoDroid: Android.Runtime.JavaProxyThrowable: Exception_WasThrown, Android.Runtime.JavaProxyThrowable
	I MonoDroid: 
	I MonoDroid:   --- End of managed Android.Runtime.JavaProxyThrowable stack trace ---
	I MonoDroid: android.runtime.JavaProxyThrowable: [System.InvalidOperationException]: oops!
	I MonoDroid:     at android_unhandled_exception.MainActivity+<>c.<ThrowFromAnotherJavaThread>b__2_0(Unknown Source:0)
	I MonoDroid:     at Java.Lang.Thread+RunnableImplementor.Run(Unknown Source:0)
	I MonoDroid:     at Java.Lang.IRunnableInvoker.n_Run(Unknown Source:0)
	I MonoDroid:     at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(Unknown Source:0)
	I MonoDroid:     at mono.java.lang.RunnableImplementor.n_run(Native Method)
	I MonoDroid:     at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
	I MonoDroid:     at java.lang.Thread.run(Thread.java:1012)
	I MonoDroid: 
	I MonoDroid:   --- End of managed Android.Runtime.JavaProxyThrowable stack trace ---
	I MonoDroid: android.runtime.JavaProxyThrowable: [System.InvalidOperationException]: oops!
	I MonoDroid:     at android_unhandled_exception.MainActivity+<>c.<ThrowFromAnotherJavaThread>b__2_0(Unknown Source:0)
	I MonoDroid:     at Java.Lang.Thread+RunnableImplementor.Run(Unknown Source:0)
	I MonoDroid:     at Java.Lang.IRunnableInvoker.n_Run(Unknown Source:0)
	I MonoDroid:     at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(Unknown Source:0)
	I MonoDroid:     at mono.java.lang.RunnableImplementor.n_run(Native Method)
	I MonoDroid:     at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
	I MonoDroid:     at java.lang.Thread.run(Thread.java:1012)

Note how `at …` is always a four-space indent and always lines up.
*Hopefully* this means that crash loggers can provide more useful
information.

TODO:

  * Create an "end-to-end" test which uses an actual crash logger
    (which one?) in order to better understand what the
    "end user" experience is.

  * The "merged" stack trace always places the managed stack trace
    above the Java-side stack trace.  This means things will look
    "weird"/"wrong" if you have an *intermixed* stack trace, e.g.
    (Java code calls .NET code which calls Java code)+
    which eventually throws from .NET.
jonathanpeppers pushed a commit that referenced this issue Aug 23, 2023
…8185)

Context: #1198
Context: #1188 (comment)
Context: #4877
Context: #4927 (comment)

What happens with unhandled exceptions?

	throw new InvalidOperationException ("oops!");

This is a surprisingly complicated question:

If this happens when a debugger is attached, the debugger will get a
"first chance notification" at the `throw` site.  If execution
continues, odds are high that the app will abort if there is a JNI
transition in the callstack.

If no debugger is attached, then it depends on which thread threw the
unhandled exception.

If the thread which threw the unhandled exception is a .NET Thread:

	static void ThrowFromAnotherManagedThread() {
	    var t = new System.Threading.Thread(() => {
	        throw new new Java.Lang.Error ("from another thread?!");
	    });
	    t.Start ();
	    t.Join ();
	}

Then .NET will report the unhandled exception, *and*
the app will restart:

	F mono-rt : [ERROR] FATAL UNHANDLED EXCEPTION: System.InvalidOperationException: oops!
	F mono-rt :    at android_unhandled_exception.MainActivity.<>c.<ThrowFromAnotherManagedThread>b__1_0()
	F mono-rt :    at System.Threading.Thread.StartCallback()
	# app restarts

If the thread which threw the unhandled exception is a *Java* thread,
which could be the UI thread (e.g. thrown from an `Activity.OnCreate()`
override) or via a `Java.Lang.Thread` instance:

	static void ThrowFromAnotherJavaThread() {
	    var t = new Java.Lang.Thread(() => {
	        throw new InvalidOperationException ("oops!");
	    });
	    t.Start ();
	    t.Join ();
	}

Then .NET will report the unhandled exception, *and* the app will
*not* restart (which differs from using .NET threads):

	E AndroidRuntime: Process: com.companyname.android_unhandled_exception, PID: 5436
	E AndroidRuntime: android.runtime.JavaProxyThrowable: System.InvalidOperationException: oops!
	E AndroidRuntime:    at android_unhandled_exception.MainActivity.<>c.<ThrowFromAnotherJavaThread>b__2_0()
	E AndroidRuntime:    at Java.Lang.Thread.RunnableImplementor.Run()
	E AndroidRuntime:    at Java.Lang.IRunnableInvoker.n_Run(IntPtr , IntPtr )
	E AndroidRuntime:    at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V , IntPtr , IntPtr )
	E AndroidRuntime:        at mono.java.lang.RunnableImplementor.n_run(Native Method)
	E AndroidRuntime:        at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
	E AndroidRuntime:        at java.lang.Thread.run(Thread.java:1012)
	I MonoDroid: Android.Runtime.JavaProxyThrowable: Exception_WasThrown, Android.Runtime.JavaProxyThrowable
	I MonoDroid: 
	I MonoDroid:   --- End of managed Android.Runtime.JavaProxyThrowable stack trace ---
	I MonoDroid: android.runtime.JavaProxyThrowable: System.InvalidOperationException: oops!
	I MonoDroid:    at android_unhandled_exception.MainActivity.<>c.<ThrowFromAnotherJavaThread>b__2_0()
	I MonoDroid:    at Java.Lang.Thread.RunnableImplementor.Run()
	I MonoDroid:    at Java.Lang.IRunnableInvoker.n_Run(IntPtr , IntPtr )
	I MonoDroid:    at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V , IntPtr , IntPtr )
	I MonoDroid:     at mono.java.lang.RunnableImplementor.n_run(Native Method)
	I MonoDroid:     at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
	I MonoDroid:     at java.lang.Thread.run(Thread.java:1012)
	I MonoDroid: 
	I MonoDroid:   --- End of managed Android.Runtime.JavaProxyThrowable stack trace ---
	I MonoDroid: android.runtime.JavaProxyThrowable: System.InvalidOperationException: oops!
	I MonoDroid:    at android_unhandled_exception.MainActivity.<>c.<ThrowFromAnotherJavaThread>b__2_0()
	I MonoDroid:    at Java.Lang.Thread.RunnableImplementor.Run()
	I MonoDroid:    at Java.Lang.IRunnableInvoker.n_Run(IntPtr , IntPtr )
	I MonoDroid:    at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V , IntPtr , IntPtr )
	I MonoDroid:     at mono.java.lang.RunnableImplementor.n_run(Native Method)
	I MonoDroid:     at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
	I MonoDroid:     at java.lang.Thread.run(Thread.java

This "works", until we enter the world of crash logging for later
diagnosis and fixing.  The problem with our historical approach is
that we would "stuff" the .NET stack trace into the "message" of the
Java-side `Throwable` instance, and the "message" may not be
transmitted as part of the crash logging!

(This is noticeable by the different indentation levels for the
`at …` lines in the crash output.  Three space indents are from the
`Throwable.getMessage()` output, while four space indents are from
the Java-side stack trace.)

We *think* that we can improve this by replacing the Java-side stack
trace with a "merged" stack trace which includes both the Java-side
and .NET-side stack traces.  This does nothing for unhandled exceptions
on .NET threads, but does alter the output from Java threads:

	E AndroidRuntime: FATAL EXCEPTION: Thread-3
	E AndroidRuntime: Process: com.companyname.android_unhandled_exception, PID: 12321
	E AndroidRuntime: android.runtime.JavaProxyThrowable: [System.InvalidOperationException]: oops!
	E AndroidRuntime:        at android_unhandled_exception.MainActivity+<>c.<ThrowFromAnotherJavaThread>b__2_0(Unknown Source:0)
	E AndroidRuntime:        at Java.Lang.Thread+RunnableImplementor.Run(Unknown Source:0)
	E AndroidRuntime:        at Java.Lang.IRunnableInvoker.n_Run(Unknown Source:0)
	E AndroidRuntime:        at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(Unknown Source:0)
	E AndroidRuntime:        at mono.java.lang.RunnableImplementor.n_run(Native Method)
	E AndroidRuntime:        at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
	E AndroidRuntime:        at java.lang.Thread.run(Thread.java:1012)
	I MonoDroid: UNHANDLED EXCEPTION:
	I MonoDroid: Android.Runtime.JavaProxyThrowable: Exception_WasThrown, Android.Runtime.JavaProxyThrowable
	I MonoDroid: 
	I MonoDroid:   --- End of managed Android.Runtime.JavaProxyThrowable stack trace ---
	I MonoDroid: android.runtime.JavaProxyThrowable: [System.InvalidOperationException]: oops!
	I MonoDroid:     at android_unhandled_exception.MainActivity+<>c.<ThrowFromAnotherJavaThread>b__2_0(Unknown Source:0)
	I MonoDroid:     at Java.Lang.Thread+RunnableImplementor.Run(Unknown Source:0)
	I MonoDroid:     at Java.Lang.IRunnableInvoker.n_Run(Unknown Source:0)
	I MonoDroid:     at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(Unknown Source:0)
	I MonoDroid:     at mono.java.lang.RunnableImplementor.n_run(Native Method)
	I MonoDroid:     at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
	I MonoDroid:     at java.lang.Thread.run(Thread.java:1012)
	I MonoDroid: 
	I MonoDroid:   --- End of managed Android.Runtime.JavaProxyThrowable stack trace ---
	I MonoDroid: android.runtime.JavaProxyThrowable: [System.InvalidOperationException]: oops!
	I MonoDroid:     at android_unhandled_exception.MainActivity+<>c.<ThrowFromAnotherJavaThread>b__2_0(Unknown Source:0)
	I MonoDroid:     at Java.Lang.Thread+RunnableImplementor.Run(Unknown Source:0)
	I MonoDroid:     at Java.Lang.IRunnableInvoker.n_Run(Unknown Source:0)
	I MonoDroid:     at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(Unknown Source:0)
	I MonoDroid:     at mono.java.lang.RunnableImplementor.n_run(Native Method)
	I MonoDroid:     at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
	I MonoDroid:     at java.lang.Thread.run(Thread.java:1012)

Note how `at …` is always a four-space indent and always lines up.
*Hopefully* this means that crash loggers can provide more useful
information.

TODO:

  * Create an "end-to-end" test which uses an actual crash logger
    (which one?) in order to better understand what the
    "end user" experience is.

  * The "merged" stack trace always places the managed stack trace
    above the Java-side stack trace.  This means things will look
    "weird"/"wrong" if you have an *intermixed* stack trace, e.g.
    (Java code calls .NET code which calls Java code)+
    which eventually throws from .NET.
@github-actions github-actions bot locked and limited conversation to collaborators Feb 4, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Area: Mono.Android Issues with the Android API binding (Mono.Android.dll).
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants