-
Notifications
You must be signed in to change notification settings - Fork 3.8k
[runtime] Rework how the native-to-managed eh callback is called. #5837
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
Conversation
mono/metadata/marshal.c
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to remove the finally clause here, as there are 2 possible cases:
- there is an exception (and we assume there is no
ftnptr_eh_callback): we are going to switch GC unsafe -> safe here so we end up unwinding in GC safe mode which is not allowed. - there is no exception: it works properly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean remove the clause but keep the detach call right ?
mono/mini/mini-exceptions.c
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have to call mono_threads_detach_coop here, otherwise the ftnptr_eh_callback will unwind in GC unsafe mode and that can lead to suspend locks (the runtime tries to suspend a thread but it's in GC unsafe mode and it doesn't hit any safe point)
|
What happens if there are no other managed frames further up the stack? Will mono abort with an unhandled exception message? |
|
@rolfbjarne if there is no managed frames up the stack, there will be an unhandled exception, and that is no different than today. |
|
@luhenry there might be a difference if there's a It looks like this PR will cause mono to abort the process before calling |
|
@rolfbjarne how should we treat exceptions which: 1. are not handled by a managed frame (because there is "no managed frames up the stack" for example), and 2. are not handled by a native frame? Should we have the native unhandled exception mechanism take precedence over the managed one (ie: report a native unhandled exception over a managed unhandled exception)? |
Instead of catching all exceptions in the native-to-managed wrapper and calling the callback from the catch clause, call it directly from the EH code. The previous approach caught all exceptions, which messed up debugging, since the debugger wouldn't break on uncaught exceptions raised from native-to-managed callbacks.
|
If a managed exception is not handled by a managed frame, and there's no
|
|
@rolfbjarne ok for the case you presented, but my question is about the case where: 1. the managed exception is not handled by a managed frame, 2. there IS a |
We need to do the check in the first pass, as pointed out by #5837 (comment)
|
@monojenkins build coop |
|
I don't remember all of it, but looks like it's ok from what the comments and looking at the code. @rolfbjarne could you confirm it looks good to you too? |
|
Edit: Apparently that's a known/expected interp failure. See #7739 |
|
@luhenry yes, from what I can tell (and remember) this looks good now |
|
@monojenkins build failed |
|
Merged master into this PR one more time and enabled all the tests that were disabled under coop on PRs. This should be good to go after CI and a review. |
|
If we run the installed handler in the first pass, we don't run |
|
So I think we need to refine the first pass result. Instead of just a boolean we should return some enum:
Then the second pass will treat unhandled-delegate-to-native as "handled" (ie, start unwinding, run finally clauses, etc) until we reach the wrapper again at which point we call the callback. I'm assuming that first pass will need to stop if it sees the wrapper and there's a callback installed. Does this make sense? Also: does |
…econd pass. The constraints are: 1. We must allow managed exceptions to be caught and finally blocks must run 2. We must not call the unmanaged exception handler if the first unwinding pass detects that there is a native-to-managed wrapper and a callback is installed that will handle managed exceptions (by unwinding the native code and either re-raising the managed exception or otherwise dealing with it). So we change the return value of handle_exception_first_pass to be: - MONO_FIRST_PASS_HANDLED - caught by a managed frame (with a matching filter if one was present). - MONO_FIRST_PASS_CALLBACK_TO_NATIVE - there's a native-to-managed wrapper and a ftnptr_eh_callback is installed. - MONO_FIRST_PASS_UNHANDLED - first pass unwound all the way and there was no wrapper or there was no callback installed. In mono_handle_exception_internal, we treat MONO_FIRST_PASS_CALLBACK_TO_NATIVE as handled and unwind the managed stack, invoking finally clauses as we go, until we reach the managed-to-native frame, at which point we invoke the callback.
|
Updated: Also added a test case using |
|
The new test needs some love to work in the FullAOT configuration. |
|
Linux ARMv5, Linux ARMv7 and Linux i386 failures are all #7578. |
|
@monojenkins bulid failed |
|
@luhenry One more review? |
| } | ||
|
|
||
| if (method->wrapper_type == MONO_WRAPPER_NATIVE_TO_MANAGED && ftnptr_eh_callback) { | ||
| result = MONO_FIRST_PASS_CALLBACK_TO_NATIVE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know the code enough but shouldn't we return here? It seems to me that, since we found a native-to-managed frame, we don't want to continue looking for a clause that could handle the exception. We don't want to call the callback just yet either because we want to run the finalizers, but the first pass should be finished here IIUC.
@vargaz @alexanderkyte @kumpera could you please confirm? Thank you.
luhenry
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could go for returning TRUE in handle_exception_first_pass and simply keep checks for the wrspper_type in both first and second pass (the first pass would just stop trying to find a catch clause, the second would call the callback)
| if (unhandled) | ||
| mono_debugger_agent_handle_exception ((MonoException *)obj, ctx, NULL, NULL); | ||
| else | ||
| else if (res != MONO_FIRST_PASS_CALLBACK_TO_NATIVE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we got here, res is MONO_FIRST_PASS_UNHANDLED so this check seems redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so - this is all in the else branch of if (res == MONO_FIRST_PASS_UNHANDLED), so it could be handled or passed to native.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yes, misread the code.
| free_stack = 0xffffff; | ||
| } | ||
|
|
||
| if (method->wrapper_type == MONO_WRAPPER_NATIVE_TO_MANAGED && ftnptr_eh_callback) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MONO_FIRST_PASS_CALLBACK_TO_NATIVE doesn't seem necessary since returning TRUE from handle_exception_first_pass and checking for the wrapper_type again has the same effect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the difference might be in how/when we signal the debugger... we don't want MONO_FIRST_PASS_CALLBACK_TO_NATIVE to tell the debugger that we're handling the exception since the callback is kind of internal to the embedder.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But obviously I haven't written a test for that part, so I'm just guessing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I based this comment on https://github.com/mono/mono/pull/5837/files#r176922871 which was wrong, so please ignore this review.
| free_stack = 0xffffff; | ||
| } | ||
|
|
||
| if (method->wrapper_type == MONO_WRAPPER_NATIVE_TO_MANAGED && ftnptr_eh_callback) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I based this comment on https://github.com/mono/mono/pull/5837/files#r176922871 which was wrong, so please ignore this review.
|
@monojenkins build failed |
…no#5837) * [runtime] Rework how the native-to-managed eh callback is called. Instead of catching all exceptions in the native-to-managed wrapper and calling the callback from the catch clause, call it directly from the EH code. The previous approach caught all exceptions, which messed up debugging, since the debugger wouldn't break on uncaught exceptions raised from native-to-managed callbacks. * [coop] Enable tests bug-58782-plain-throw and bug-58782-capture-and-throw for PRs * [mini] Installed native-to-managed exception handling callback from second pass. The constraints are: 1. We must allow managed exceptions to be caught and finally blocks must run 2. We must not call the unmanaged exception handler if the first unwinding pass detects that there is a native-to-managed wrapper and a callback is installed that will handle managed exceptions (by unwinding the native code and either re-raising the managed exception or otherwise dealing with it). So we change the return value of handle_exception_first_pass to be: - MONO_FIRST_PASS_HANDLED - caught by a managed frame (with a matching filter if one was present). - MONO_FIRST_PASS_CALLBACK_TO_NATIVE - there's a native-to-managed wrapper and a ftnptr_eh_callback is installed. - MONO_FIRST_PASS_UNHANDLED - first pass unwound all the way and there was no wrapper or there was no callback installed. In mono_handle_exception_internal, we treat MONO_FIRST_PASS_CALLBACK_TO_NATIVE as handled and unwind the managed stack, invoking finally clauses as we go, until we reach the managed-to-native frame, at which point we invoke the callback. * [test] Test for mono_install_ftnptr_eh_callback * [test] AOT-friendly test for mono_install_ftnptr_eh_callback
…no#5837) * [runtime] Rework how the native-to-managed eh callback is called. Instead of catching all exceptions in the native-to-managed wrapper and calling the callback from the catch clause, call it directly from the EH code. The previous approach caught all exceptions, which messed up debugging, since the debugger wouldn't break on uncaught exceptions raised from native-to-managed callbacks. * [coop] Enable tests bug-58782-plain-throw and bug-58782-capture-and-throw for PRs * [mini] Installed native-to-managed exception handling callback from second pass. The constraints are: 1. We must allow managed exceptions to be caught and finally blocks must run 2. We must not call the unmanaged exception handler if the first unwinding pass detects that there is a native-to-managed wrapper and a callback is installed that will handle managed exceptions (by unwinding the native code and either re-raising the managed exception or otherwise dealing with it). So we change the return value of handle_exception_first_pass to be: - MONO_FIRST_PASS_HANDLED - caught by a managed frame (with a matching filter if one was present). - MONO_FIRST_PASS_CALLBACK_TO_NATIVE - there's a native-to-managed wrapper and a ftnptr_eh_callback is installed. - MONO_FIRST_PASS_UNHANDLED - first pass unwound all the way and there was no wrapper or there was no callback installed. In mono_handle_exception_internal, we treat MONO_FIRST_PASS_CALLBACK_TO_NATIVE as handled and unwind the managed stack, invoking finally clauses as we go, until we reach the managed-to-native frame, at which point we invoke the callback. * [test] Test for mono_install_ftnptr_eh_callback * [test] AOT-friendly test for mono_install_ftnptr_eh_callback
…no/mono#5837) * [runtime] Rework how the native-to-managed eh callback is called. Instead of catching all exceptions in the native-to-managed wrapper and calling the callback from the catch clause, call it directly from the EH code. The previous approach caught all exceptions, which messed up debugging, since the debugger wouldn't break on uncaught exceptions raised from native-to-managed callbacks. * [coop] Enable tests bug-58782-plain-throw and bug-58782-capture-and-throw for PRs * [mini] Installed native-to-managed exception handling callback from second pass. The constraints are: 1. We must allow managed exceptions to be caught and finally blocks must run 2. We must not call the unmanaged exception handler if the first unwinding pass detects that there is a native-to-managed wrapper and a callback is installed that will handle managed exceptions (by unwinding the native code and either re-raising the managed exception or otherwise dealing with it). So we change the return value of handle_exception_first_pass to be: - MONO_FIRST_PASS_HANDLED - caught by a managed frame (with a matching filter if one was present). - MONO_FIRST_PASS_CALLBACK_TO_NATIVE - there's a native-to-managed wrapper and a ftnptr_eh_callback is installed. - MONO_FIRST_PASS_UNHANDLED - first pass unwound all the way and there was no wrapper or there was no callback installed. In mono_handle_exception_internal, we treat MONO_FIRST_PASS_CALLBACK_TO_NATIVE as handled and unwind the managed stack, invoking finally clauses as we go, until we reach the managed-to-native frame, at which point we invoke the callback. * [test] Test for mono_install_ftnptr_eh_callback * [test] AOT-friendly test for mono_install_ftnptr_eh_callback Commit migrated from mono/mono@b20d5ce
Instead of catching all exceptions in the native-to-managed wrapper and calling the callback from the catch clause, call it directly from the EH code. The
previous approach caught all exceptions, which messed up debugging, since the debugger wouldn't break on uncaught exceptions raised from native-to-managed
callbacks.