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
Convert Exceptions to Throwables #28
Conversation
I am thinking of having the By rethrowing it we force program termination by bubbling the Error upwards. If necessary users can still catch it, but by default we get proper behavior. |
I'd prefer emphasizing like
I've never reflected over that assert doesn't throw an Exception. Btw, how are assert allowed to be called in a nothrow and potentially |
Nice idea except that a lot of Error's in D aren't actually threadsafe, that includes: So hitting a range error on multiple threads corrupts memory. Yay. EDIT: it isn't as bad, but its not nice either: #28 (comment) |
That sounds crazy. How do you deduce that they aren't thread-safe? And what do you mean by "corrupts memory"? |
Looking at it again it isn't as bad as I originally thought. Most throwing functions in core.exception throw use staticError, which constructs an Error in some static storage. I had overlooked the fact that the static storage is actually thread local. It is still a problem though if you have successive Errors on the same thread. The later one will overwrite the previous one. It is also a problem if you hold on to the Error until after the thread is gone (tls is destructed). |
I see. Thanks for the clarification. What about private __gshared AssertHandler _assertHandler = null; ? When is that set? |
I think it is mostly null for regular applications. Don't think many people use it. For me it has two problems:
|
It throws an Error. Both Exception and Error inherit from Throwable. Exceptions you can safely catch, Errors you shouldn't.
Errors thrown in How can they be thrown in nothrow? That is why catching a This all suggests when we get an |
So, in the end I decided to go with a controlled shutdown. This is what druntime is doing and I don't see a good reason to stand out here. Senders themselves and the algorithms around them won't catch Errors for you, when one happens they will just bubble up to whatever code started those senders. If that is the main thread, then druntime will catch it for you, print it, and terminate the runtime followed by exiting the program. If it happens to be a non-main thread, druntime's Thread class will catch the Error and rethrow it once your program joins the thread (which it should!). If you use a Sender that starts a new thread (a ThreadSender or an StdTaskPool currently), those will catch any Errors on those threads, send them over to whoever is awaiting the Sender (whoever called In contrast to regular Exceptions if a At any time users can catch Error's themselves and implement different behavior if so desired. Although it deserves to be said, if an Error gets throw in or over @nogc code, any destructors - and likely any scope(exit) or scope(failure) - is not going to be called. |
return new ThrowableClone!AssertError(t.info, a.msg, a.file, a.line, a.next); | ||
if (auto r = cast(RangeError)t) | ||
return new ThrowableClone!RangeError(t.info, r.file, r.line, r.next); | ||
if (auto e = cast(Error)t) |
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.
Why specialize handling of AssertError
and RangeError
only when there are sub-classes of Error such as FinalizeError
? Is it because those are the only ones with constructor parameters different from Error
's?
Wouldn't it have been useful (and likely faster instead of successive calls to dynamic-cast) for Error
(and Object
in the general case) to have an abstract dup
member that preserves typeinfo? Explicitly implemented for each sub-class, though. Just a thought.
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.
Why specialize handling of AssertError and RangeError only when there are sub-classes of Error such as FinalizeError?
I had a whole list. Then realized a lot of those where actually added in latest master only. Which made be realize I will never be able to specialize for all of them. So then I did the most useful ones only.
Wouldn't it have been useful (and likely faster instead of successive calls to dynamic-cast) for Error (and Object in the general case) to have an abstract dup member that preserves typeinfo?
It would be immensely useful. But there isn't one. I don't think one will be added as well.
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.
Ok, cool. Why don't you think such a member will be added?
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.
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.
There's also the question of dup vs deep dup. Maybe a deep dup be partially inferred using .tupleof
.
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.
Ok with me to merge this as is, @skoppe and maybe add an issue about handling of more sub-classes of Error
.
Throwables are required because we cannot let anything get past us. In single threaded applications you only have to deal with Exceptions. Any error will simply propagate upwards and terminate the application. In multi-threaded applications you cannot have threads die willy-nilly, instead you want the owner (ultimately the main thread) to know it errored. and rethrow. If we only do it for Exceptions, any triggered assert will kill the thread while the rest of the application hangs waiting until the thread is done. That is why we catch throwables, move them to the owner, and rethrow there.
Throwables are required because we cannot let anything get past us. In single
threaded applications you only have to deal with Exceptions. Any error will
simply propagate upwards and terminate the application. In multi-threaded
applications you cannot have threads die willy-nilly, instead you want the owner
(ultimately the main thread) to know it errored.
and rethrow.
If we only do it for Exceptions, any triggered assert will kill the thread while
the rest of the application hangs waiting until the thread is done.
That is why we catch throwables, move them to the owner, and rethrow there.