Bug 8135 – throwing Error runs finally handler

Status
RESOLVED
Resolution
INVALID
Severity
normal
Priority
P2
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2012-05-23T12:34:00Z
Last change time
2012-05-24T19:35:42Z
Assigned to
nobody
Creator
code

Comments

Comment #0 by code — 2012-05-23T12:34:18Z
cat > bug.d << CODE extern(C) int printf(const char*, ...); void foo() { throw new Error("msg"); } void main() { try foo(); finally printf("finally\n"); } CODE dmd -run bug prints: finally object.Error: msg expected: object.Error: msg ---- The expected behavior was discussed in bug 7018 and on the mailing list. Throwing Errors (http://forum.dlang.org/thread/1566418.J7qGkEti3s@lyonel)
Comment #1 by issues.dlang — 2012-05-23T14:06:36Z
This is not a bug. Per the lanugage, there is no guarantee that finally blocks, scope statements, or destructors will be run when an Error is thrown, but neither is there any guarantee that they _won't_ be. So, while relying on them being executed when an Error is thrown is a bad idea, relying on them _not_ being executed when an Error is thrown is also a bad idea. The current implementation _does_ always execute finally blocks, scope statements, and destructors with Errors just like would occur with Exceptions, and there is quite a lot of debate over whether the language should be changed to reflect that. Walter is against it, but he's also one of the few who even realized that the language didn't guarantee that they would be executed for Errors. Other people who have been working on the exception handling in the compiler and druntime _did_ think that it was guaranteed, and they wrote that code with that in mind. In addition, I believe that the spec itself is actually silent on the matter. So, the short answer is that you should never rely on scope statements, finally blocks, or destructors being run or not run when an Error is thrown. A more thorough discussion of the issues is in this thread on dmd-internals: http://forum.dlang.org/post/1566418.J7qGkEti3s@lyonel
Comment #2 by code — 2012-05-23T19:40:45Z
>The current implementation _does_ always execute finally blocks, scope statements, and destructors with Errors just like would occur with Exceptions No they are not when you throw from a nothrow function. This is even worse when nothrow is inferred, because the actual behavior becomes instable. >So, the short answer is that you should never rely on scope statements, finally blocks, or destructors being run or not run when an Error is thrown. This is not an acceptable solution. The problem needs a clean decision and an according implementation or it will continue to create confusion. https://github.com/D-Programming-Language/druntime/pull/225#issuecomment-5857155 I'm wondering how the decision to use EH for fatal errors was made.
Comment #3 by issues.dlang — 2012-05-23T19:53:36Z
Well, unstable behavior from an Error is pretty much a given if your program doesn't die from it, unless it were treated completely like an Exception, in which case you couldn't throw an Error (including OutOfMemoryError) from a nothrow function, which would be unacceptable. Your program is pretty much instantly in an unstable state as soon as an Error is thrown. It's just a question of _how_ unstable, and the given implementation attempts far more cleanup than is technically required according to Walter, which is a double-edged sword, since that tends to make the code more stable when an Error is thrown, but if your program is in a bad state thanks to whatever caused the Error to be thrown, trying to do that cleanup could just make things worse. Regardless, I completely agree that the situation needs to be clarified. We've got the implementation going one way and Walter arguing another. And it would certainly be better if it were guaranteed that finally and friends were never executed when an Error is thrown than having them executed right now but possibly not later when the implementation is tweaked. So, in principle, I agree with the bug report, but as it stands, the current implementation is within the required behavior.
Comment #4 by alex — 2012-05-23T19:59:41Z
FWIW, I'm all for making Errors actually fatal and terminating the runtime immediately, but then OutOfMemory*Error* HAS GOT TO GO. I use D as a systems language, not as an applications language, and I *have* to be able to catch an out of memory condition. I don't know why this was made an Error in the first place, and in a systems language of all things. Yes, most developers may get it wrong, but what about the few of us that *don't*? Just my 2 cents/frustration as a developer writing a virtual machine in D...
Comment #5 by issues.dlang — 2012-05-23T20:04:56Z
Walter is of the firm belief that running out of memory is essentially unrecoverable, so I don't see OutOfMemoryError ever being anything other than an Error. If you really can't deal with that, you're probably going to have to use something other than the GC for memory management (or wrap all GC allocations in functions that can catch and deal with OutOfMemoryError immediately after its thrown). Even if the cleanup doesn't occur for Errors, it's quite possible to catch them and handle them, and if you catch them very close to the throw point, you can do so relatively safely. It's catching them far from the throw point that isn't going to work. http://d.puremagic.com/issues/show_bug.cgi?id=8137
Comment #6 by alex — 2012-05-23T20:10:03Z
From TDPL ยง9.4: "If you catch a Throwable, you may only perform a number of simple operations; most of the time, you probably want to print a message to the standard error or a log file, attempt to save whatever you can save to a separate file, stiffen that upper lip, and exit with as much dignity as possible." This doesn't seem terribly encouraging... Essentially, I need to catch OOME and continue full-blown program execution, which is very worrying.
Comment #7 by issues.dlang — 2012-05-23T20:19:00Z
You should be able to do so under very limited circumstances, but it was definitely a design decision of D that running out of memory would be considered to be unrecoverable.
Comment #8 by schveiguy — 2012-05-24T07:01:45Z
(In reply to comment #4) > FWIW, I'm all for making Errors actually fatal and terminating the runtime > immediately, but then OutOfMemory*Error* HAS GOT TO GO. Then you could mark almost nothing as nothrow. I think OutOfMemory should be an error. If you want to override the behavior because you have special circumstances, that should be possible (i.e. somehow prevent out of memory error from being thrown, but instead handle the situation in a different way). What about an enhancement of adding GC.mallocNoError and friends which instead return null when a memory block is not available instead of throwing?
Comment #9 by jens.k.mueller — 2012-05-24T07:15:30Z
I don't see why there should be no way to do some simple cleanup on an Error. Testing in contracts is useful and does no harm. Maybe Walter can give a concrete example where handling of Errors caused the program to be in a worse state compared to exit right away given the programmer had a solid understanding of what he was doing. Assuming I get an OutOfMemoryError having chances sending last words is useful. I fail to see how this can make it worse. It may be that he has seen to many misuses of handling errors. I would go with a C++ attitude: "You can catch, scope guard, etc. any Throwable and even try to recover from Exceptions. But Errors are not meant to be recovered from. You have been warned." TDPL also says that it's okay to do some cleanup.
Comment #10 by alex — 2012-05-24T09:44:33Z
(In reply to comment #8) > (In reply to comment #4) > > FWIW, I'm all for making Errors actually fatal and terminating the runtime > > immediately, but then OutOfMemory*Error* HAS GOT TO GO. > > Then you could mark almost nothing as nothrow. True. > > I think OutOfMemory should be an error. If you want to override the behavior > because you have special circumstances, that should be possible (i.e. somehow > prevent out of memory error from being thrown, but instead handle the situation > in a different way). > > What about an enhancement of adding GC.mallocNoError and friends which instead > return null when a memory block is not available instead of throwing? That could work too. I really just want the GC to not assume that an allocation error is fatal.
Comment #11 by code — 2012-05-24T12:55:08Z
>That could work too. I really just want the GC to not assume that an allocation error is fatal. You could simply override the behavior by providing your own 'extern(C) void onOutOfMemory()'. The linker will pick the one from druntime with the lowest precedence.
Comment #12 by alex — 2012-05-24T19:35:42Z
(In reply to comment #11) > >That could work too. I really just want the GC to not assume that an allocation error is fatal. > > You could simply override the behavior by providing your own > 'extern(C) void onOutOfMemory()'. The linker will pick the one > from druntime with the lowest precedence. I don't always want out of memory to be handled. In most cases, I do treat it as fatal, but in the few cases I don't, I just want to get a null value back from allocations in the core.memory module.