Next: C++/CLI limitations?
From: Alf P. Steinbach on 6 Jul 2005 06:03 * David Abrahams: > alfps(a)start.no (Alf P. Steinbach) writes: > > > * Peter Dimov: > >> > > > >> > > No recovery is possible after a failed assert. > >> > >> [The above] means that performing stack unwinding after a failed > >> assert is usually a bad idea. > > > > I didn't think of that interpretation, but OK. > > > > The interpretation, or rather, what you _meant_ to say in the first > > place, > > AFAICT that was a *conclusion* based on what Peter had said before. "It's impossible to do stack unwinding, therefore it's usually a bad idea to do stack unwinding." I didn't think of that. It's, uh... > > is an opinion, which makes it more difficult to discuss. > > > > After a failed assert it's known that something, which could be anything > > (e.g. full corruption of memory), is wrong. Attempting to execute even > > one teeny tiny little instruction might do unimaginable damage. Yet you > > think it's all right to not only terminate the process but also to log > > things, which involves file handling, as long as one doesn't do a stack > > rewind up from the point of the failed assert. > > There are two problems with stack unwinding at that point: > > 1. It executes more potentially damaging instructions than necessary, That is an unwarranted assumption. In practice the opposite is probably more likely for many classes of applications, but it does depend: there are situations where stack unwinding is not advicable, and there are situations where it is advicable. The decision is just like the judgement call of assert versus exception versus return value: you judge the severity, the consequences of this or that way of handling it, what you have time for (:-)), even what maintainance programmers are likely to understand, etc. > since none of the destructors or catch blocks involved have > anything to do with process termination or logging. Ditto, the above is just assumptions, but you might turn it around and get a valid statement: _if_ the assumptions above hold, then that is a situation where stack unwinding would perhaps not be a good idea. > 2. The flow of execution proceeds into logic that is usually involved > with the resumption of normal execution, and it's easy to end up > back in code that executes as though everything is still fine. Not sure what you mean with "resumption of normal execution" since destructors are very much oriented towards directly terminating in such cases. Destructors are the most likely actors that may terminate the process if further problems manifest themselves. Either by directly calling abort or terminate, or by throwing (where C++ rules guarantee termination). Regarding "code that executes as though everything is still fine": First of all, most likely everything at higher levels _is_ just as fine, or not, as it ever was: when you detect that null-pointer argument (say) it doesn't usually mean more than a simple typo or the like at the calling site. That goes into the equation for likely good versus bad of doing this or that. Secondly, destructors are designed to make things fine if they aren't already: they're cleaners, clean-up routines, and after a destructor has run successfully there's no longer that class invariant that can be bad, so by cleaning up you're systematically removing potential badness. > > This leads me to suspect that you're confusing a failed assert with > > a corrupted stack, or that you think that a failure to clean up 100% > > might be somehow devastating. Anyway, an explanation of your > > opinion would be great, and this time, please write what you mean, > > not something entirely different. > > Ouch. I wouldn't and didn't put it that way. -- A: Because it messes up the order in which people normally read text. Q: Why is it such a bad thing? A: Top-posting. Q: What is the most annoying thing on usenet and in e-mail? [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Alf P. Steinbach on 6 Jul 2005 06:40 * David Abrahams: > > > But as mentioned above, it's an extreme > ^^^^ > What is an extreme? Peter's approach (b). Also approach (a), but here the reference was to approach (b). > > although in some other languages (e.g. Java) you get null-pointer > > exceptions & the like. > > IMO null-pointer exceptions are a joke; it's a way of claiming that > the language is typesafe and making that sound bulletproof: all you do > is turn programming errors into exceptions with well-defined behavior! > Fantastic! Now my program goes on doing... something... even though > its state might be completely garbled. Agreed. > > If we did have some kind of "hard" exception supported by the > > language, or even just standardized and supported by convention, > > then the vast jungle of potential problems that stands in the way of > > further normal execution wouldn't matter so much: catching that hard > > exception at some uppermost control level you know that the process > > has to terminate, not continue with normal execution (which was the > > problem), and you know what actions you've designated for that case > > (also known by throwers, or at least known to be irrelevant to > > them), so that's what the code has to attempt to do. > > And what happens if the corrupted programming state causes a crash > during unwinding (e.g. from a destructor)? The same as would have happened if we'd initiated that right away: the difference is that it doesn't have to happen, and generally won't, and even if it does one might have collected useful info on the way. ;-) > What makes executing the unwinding actions the right thing to do? How > do you know which unwinding actions will get executed at the point > where you detect that your program state is broken? Both questions are impossible to answer due to built-in assumptions. For the first question, there is no more "the right thing" than there is "best", out of context. For the second question, you generally don't know the unwinding actions, and, well, you know that... ;-) > >> [snip] > >> ....and make it someone else's problem. Code higher up the call stack > >> mike know how to deal with it, right? ;-) > > > > In the case of a "hard" exception it's not "might", it's a certainty. > > ?? I don't care how you flavor the exception; the appropriate recovery > action cannot always be known by the outermost layer of the program. When you get to the outermost layer you are (or rather, the program is) already mostly recovered: you have logged the most basic information (of course you did that right away, before unwinding), you have removed lots of potential badness (due to destructors cleaning up), and perhaps as part of that logged more rich & useful state information, and now all you have to do is to attempt to do more chancy high level error reporting before terminating in a hopefully clean way -- if you like to do very dangerous things, as they evidently do at Microsoft, you could also at this point store descriptions of the state and attempt a Bird Of Phoenix resurrection, a.k.a. restart. > Furthermore, what the outermost layer of the program knows how to do > becomes irrelevant if there is a crash during unwinding. A crash during unwinding is OK: it's no worse than what you would have had with no unwinding. Two main potential problems are (1) a hang, which can occur during cleanup, and (2) unforeseen bad effects such as trashing data on the disk or committing a transaction erronously. Such potential problems will have to be weighted against the probability of obtaining failure data at all, obtaining rich failure data, pleasing or at least not seriously annoying the user (in the case of an interactive app), etc., in each case. Cheers, - Alf -- A: Because it messes up the order in which people normally read text. Q: Why is it such a bad thing? A: Top-posting. Q: What is the most annoying thing on usenet and in e-mail? [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: David Abrahams on 6 Jul 2005 18:01 alfps(a)start.no (Alf P. Steinbach) writes: > * David Abrahams: >> alfps(a)start.no (Alf P. Steinbach) writes: >> >> > * Peter Dimov: >> >> > > >> >> > > No recovery is possible after a failed assert. >> >> >> >> [The above] means that performing stack unwinding after a failed >> >> assert is usually a bad idea. >> > >> > I didn't think of that interpretation, but OK. >> > >> > The interpretation, or rather, what you _meant_ to say in the first >> > place, >> >> AFAICT that was a *conclusion* based on what Peter had said before. > > "It's impossible to do stack unwinding, therefore it's usually a bad > idea to do stack unwinding." I didn't think of that. It's, uh... You clipped everything but the first sentence of Peter's paragraph, which makes what he's saying look like a simpleminded tautology, and now you're ridiculing it. Nice. >> > After a failed assert it's known that something, which could be anything >> > (e.g. full corruption of memory), is wrong. Attempting to execute even >> > one teeny tiny little instruction might do unimaginable damage. Yet you >> > think it's all right to not only terminate the process but also to log >> > things, which involves file handling, as long as one doesn't do a stack >> > rewind up from the point of the failed assert. >> >> There are two problems with stack unwinding at that point: >> >> 1. It executes more potentially damaging instructions than necessary, > > That is an unwarranted assumption. Let's ignore, for the moment, that the runtime library's unwinding code is being executed and _its_ invariants may have been violated. If you arrange your program so that the only automatic objects with nontrivial destructors you use are specifically designed to do something when the program state is broken, then you will be executing the minimal number of potentially damaging instructions. Do you think that's a likely scenario? I would hate to write programs under a restriction that I could only use automatic objects with nontrivial destructors to account for a condition that should never occur. It would also prevent the use of most libraries. > In practice the opposite is probably more likely for many classes of > applications, Wow, so many classes of applications actually can be written that way? I presume they wouldn't use the standard (or any other) libraries. >> 2. The flow of execution proceeds into logic that is usually involved >> with the resumption of normal execution, and it's easy to end up >> back in code that executes as though everything is still fine. > > Not sure what you mean with "resumption of normal execution" I mean what happens in most continuously running programs (not single-pass translators like compilers) after error reporting or exception translation at the end of a catch block that doesn't end with a rethrow. > since destructors are very much oriented towards directly > terminating in such cases. Destructors are oriented towards destroying an object, and have no particular relationship to overall error-handling strategies. > Destructors are the most likely actors that may terminate the > process if further problems manifest themselves. Destructors intentionally taking a program down is a common idiom in the code you've seen? Can you show me even one example of that in open source code somewhere? I'm really curious. > Either by directly calling abort or terminate, or by throwing (where > C++ rules guarantee termination). C++ rules don't guarantee termination if a destructor throws, unless unwinding is already in progress. > Regarding "code that executes as though everything is still fine": > > First of all, most likely everything at higher levels _is_ just as > fine, That is an unwarranted assumption. > or not, as it ever was: when you detect that null-pointer argument (say) it > doesn't usually mean more than a simple typo or the like at the calling > site. That is an unwarranted assumption. > That goes into the equation for likely good versus bad of doing this > or that. Yes. Those equations are what make (b) difficult. There are no disciplined ways of understanding the risks of the choices you have to make. > Secondly, destructors are designed to make things fine if they > aren't already: they're cleaners, clean-up routines, and after a > destructor has run successfully there's no longer that class > invariant that can be bad, so by cleaning up you're systematically > removing potential badness. It's easy to imagine that running a bunch of destructors increases "badness," e.g. by leaving dangling pointers behind. >> > This leads me to suspect that you're confusing a failed assert with >> > a corrupted stack, or that you think that a failure to clean up 100% >> > might be somehow devastating. Anyway, an explanation of your >> > opinion would be great, and this time, please write what you mean, >> > not something entirely different. >> >> Ouch. > > I wouldn't and didn't put it that way. I didn't rewrite your text at all. Unless someone is spoofing you, you put it exactly that way. -- Dave Abrahams Boost Consulting www.boost-consulting.com [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: David Abrahams on 6 Jul 2005 18:02 alfps(a)start.no (Alf P. Steinbach) writes: >> > If we did have some kind of "hard" exception supported by the >> > language, or even just standardized and supported by convention, >> > then the vast jungle of potential problems that stands in the way of >> > further normal execution wouldn't matter so much: catching that hard >> > exception at some uppermost control level you know that the process >> > has to terminate, not continue with normal execution (which was the >> > problem), and you know what actions you've designated for that case >> > (also known by throwers, or at least known to be irrelevant to >> > them), so that's what the code has to attempt to do. >> >> And what happens if the corrupted programming state causes a crash >> during unwinding (e.g. from a destructor)? > > The same as would have happened if we'd initiated that right away: Initiated what? > the difference is that it doesn't have to happen, Difference between what and what? > and generally won't, Based on what do you say that? > and even if it does one might have collected useful info on the > way. ;-) Maybe. As long as you're clear that it's wishful thinking. Also, there seems to be little good reason to use unwinding to collect that info. You can establish a chain of reporting frames and traverse that when your precondition is violated without doing any unwinding (use TLS to ensure thread safety if you need it). >> What makes executing the unwinding actions the right thing to do? How >> do you know which unwinding actions will get executed at the point >> where you detect that your program state is broken? > > Both questions are impossible to answer due to built-in assumptions. > For the first question, there is no more "the right thing" than > there is "best", out of context. That's my point exactly. The author of the code where the precondition violation is detected doesn't _know_ the context, so she can't know what is appropriate. > For the second question, you generally don't know the unwinding > actions, and, well, you know that... ;-) Yes, also part of my point. >> >> [snip] >> >> ....and make it someone else's problem. Code higher up the call stack >> >> mike know how to deal with it, right? ;-) >> > >> > In the case of a "hard" exception it's not "might", it's a certainty. >> >> ?? I don't care how you flavor the exception; the appropriate recovery >> action cannot always be known by the outermost layer of the program. > > When you get to the outermost layer you are (or rather, the program is) > already mostly recovered: you have logged the most basic information (of > course you did that right away, before unwinding), That's not recovery! There's normally no recovering from broken invariants, because except for a few places where you second-guess your entire worldview as a programmer, your whole program has been written with the assumption that they hold. > you have removed lots of potential badness (due to destructors > cleaning up), That's pretty vague. What kind of "potential badness" do you think gets removed? > and perhaps as part of that logged more rich & useful state > information, Unwinding is totally unnecessary for that purpose. > and now all you have to do is to attempt to do more > chancy high level error reporting before terminating in a hopefully > clean way -- if you like to do very dangerous things, as they > evidently do at Microsoft, you could also at this point store > descriptions of the state and attempt a Bird Of Phoenix > resurrection, a.k.a. restart. Again, unwinding is totally unnecessary for that purpose. >> Furthermore, what the outermost layer of the program knows how to >> do becomes irrelevant if there is a crash during unwinding. > > A crash during unwinding is OK: it's no worse than what you would > have had with no unwinding. Of course it is worse if you are postponing anything important until the outer layer, because the crash will prevent you from getting to that important action. AFAICT, the only good reason to unwind is so that you can resume normal execution. If you're not going to do that, unwinding just destroys important debugging information and makes your program more vulnerable to crashes that may occur due to the execution of noncritical destructors and catch blocks. -- Dave Abrahams Boost Consulting www.boost-consulting.com [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Bob Bell on 6 Jul 2005 18:46
Alf P. Steinbach wrote: > * David Abrahams: > > Furthermore, what the outermost layer of the program knows how to do > > becomes irrelevant if there is a crash during unwinding. > > A crash during unwinding is OK: it's no worse than what you would have had > with no unwinding. You seem to be confusing "crash" and "abort". Bob [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ] |