Re: [boost] [Boost-users] [review][constrained_value] Review of Constrained Value Library begins today

Mathias Gaunard wrote:
I do not believe boost should accept a library which would only work in debug mode. I believe boost is mostly about runtime libraries, not about debugging aids. --> Mika Heiskanen

Mika Heiskanen skrev:
How different users want to respond to errors should not be mandated by the library. Therefore it is very good that this library allows you to customize it. There is a difference between protecting against accidental misuse and deliberate misuse of a component. Why can rarely protect against the latter. Having invariants checked only in debug mode is very common, FWIW. -Thorsten

Thorsten Ottosen wrote:
I did not express my thoughts very well the last time. Retry: I am not against having invariants checked only in debug mode, if the user so chooses. I am however against it being the default mode. I believe the most sensible default would be to throw an exception both in debug and release modes, since I believe this to be the most common use case. --> Mika Heiskanen

Mika Heiskanen wrote:
Exceptions should almost never be used in response to broken invariants. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

David Abrahams wrote:
Exceptions should almost never be used in response to broken invariants.
Pardon my ignorance, but how come? The constraints I have to validate usually concern broken user input via the command line or a query string. How should I deal with them if not with exceptions? Or to be more precise, why should I not use constrained types for validating user input if it saves me a lot of if-statements in the long run? --> Mika Heiskanen

on Sat Dec 20 2008, Mika Heiskanen <mika.heiskanen-AT-fmi.fi> wrote:
That's not a broken invariant. A broken invariant means your program is broken.
I would never argue that you shouldn't do that. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

David Abrahams wrote:
So you see why I would prefer the constrained value class being reviewed to throw? Also, pardon my ignorance again, but would you care to explain how an expert would handle broken invariants in broken programs if not via exceptions? I was not aware there is a better solution. Regards, --> Mika Heiskanen

Hi Mika,
I guess David means to distinguish between run-time errors and invariants failures. Run-time errors are expected conditions. An attempt to open a file may fail. An attempt to connect to a remote host over the network may fail. Your program is supposed to anticipate those conditions. Run-time errors may or may not be signalled by exceptions, depending on whether stack unwinding is desirable or not. Invariant failures, however, are unexpected conditions. Invariants are not supposed to fail. If they do, it's a sign of incorrect program logic or faulty assumptions. Your program will generally not be able to recover from such an error, hence invariant checking functions like assert() typically abort the process on failure (and generate debugging information, such as a core dump). I hope this helps, Peter

Hello Peter, Peter Simons wrote:
Thank you for your explanation Peter. However, I do not see why an assert should be the first choice when a programming error can be detected by the program itself. For example, I would prefer my word processor to announce a programming error instead of producing a core dump. Am I missing some finer point on the nature of invariants? --> Mika Heiskanen

Mika Heiskanen wrote:
If you detect a programming error through violation of an invariant, what can you do? If you know exactly what damage the error has caused and you have enough information to fix all that damage, then you should know the error well enough that you could have fixed it in the first place. If you can't fix all the damage, anything except aborting the program is a major data corruption waiting to happen. Sebastian

Sebastian Redl wrote:
I thought we were comparing plain assert with exception handling. I would count a smart assert closer to the latter.
Precisely. The fact that an invariant is broken does not mean everything else is broken too, and that all the damage cannot be fixed. If it cannot, of course aborting is the best choice. We're probably comparing apples and oranges here. Perhaps it is pointless to continue this thread on how broken invariants should be handled, it will most likely always depend on the details of the case. What matters is that the reviewed library should enable exceptions as an error mechanism, which I think we all agree on, and I do not see why it wouldn't be the most common use case and hence the default action, which we may not agree on. I can live with that. Kind regards, --> Mika Heiskanen

Hi Mika,
apparently, you expect invariants to fail. They won't. Invariants never, ever fail. They are invariant. These assumptions are the cornerstones on which your algorithms are built. Adding code that throws an exception in case of an invariant failure is wasted, because that exception will never be thrown. Invariants are checked only during development. Release binaries typically don't contain those tests at all, i.e. assert() is disabled by compiling with the preprocessor symbol NDEBUG defined. Take care, Peter

Peter Simons wrote: Hello Peter,
No, I do not except them to fail. However, it is possible that they fail due to programming errors, and I would be naive to assume none would make it to release versions. For example, I cannot let my server crash if it cannot fulfill a particular type of request due to a programming error. Instead it should log the error and fail that particular request. Clearly the matter should have been resolved in advance by sufficient testing, but is any amount of testing really ever sufficient? I guess the problem is that I believe invariant failures may be recoverable, but it does not seem to be the concensus. Perhaps I am thinking too much about servers which must not crash. Kind Regards, --> Mika Heiskanen

From: Mika Heiskanen
If one invariant fails, this means your program is not reliable anymore (also when processing subsequent requests). Other invariants may become invalid too and any assumptions made during writing of your program may not hold anymore. This means that the program will most probably start behaving in an unexpected way. I think in most cases it is preferred to crash the program than allow it to produce invalid output and, for example, store it in a database.

Robert Kawulak wrote:
Wikipedia definition for an invariant: "In computer science, a predicate that, if true, will remain true throughout a specific sequence of operations, is called (an) invariant to that sequence." So it seems all the confusion is due to my misunderstanding of what an invariant is. The simple definition I knew until now was that an invariant is something that does not change value. What I was missing was that it also has to satisfy the constraints before you can actually call it an invariant, and hence it makes no sense to test the constraints again once you can call something an invariant. Note for example that the page http://en.wikipedia.org/wiki/Invariant says "Invariant (computer science), an expression whose value doesn't change during program execution." IMO that definition means something completely different from the definition with the predicate included. I believe the confusion is thus resolved, sorry for the noise. Kind regards, --> Mika Heiskanen

on Sun Dec 21 2008, Mika Heiskanen <mika.heiskanen-AT-fmi.fi> wrote:
I guess the problem is that I believe invariant failures may be recoverable, but it does not seem to be the concensus.
They may be, but they may not be, and you can't know which, since your program wasn't designed to behave the way it has behaved. Trying to recover can do more damage than good in such a scenario.
Perhaps I am thinking too much about servers which must not crash.
For such servers there are other approaches, like starting up a fresh process and shutting down the current one. But all this is covered in that thread I referred to: http://groups.google.com/group/comp.lang.c++.moderated/browse_frm/thread/800... -- Dave Abrahams BoostPro Computing http://www.boostpro.com

Mika Heiskanen skrev:
That choice is also perfectly valid, and can be easily achieved with your own assert macro/error handler function. However, checking many preconditions/postconditions and invariants at runtime can be very expensive. http://www.artima.com/cppsource/deepspace.html might be interesting to read. -Thorsten

From: Mika Heiskanen
There seems to be confusion between broken invariant and assignment of invalid value. The latter is handled by the error policy and it is its duty to prevent breaking the invariant. One of the ways to prevent it is throwing an exception, other ones are adjusting the value to be valid or ignoring the assignment. If the invariant gets broken, then it means there's something wrong either with your error policy, the constraint policy or the value type, because they do not fulfil the requirements of constrained class. So, as David has pointed out, broken invariant means that the programmer has done something wrong. Best regards, Robert

Robert Kawulak wrote:
The confusion is all mine, I'm sure. However, I still do not see why a programming error should not be handled by an exception if it is possible. Of course some situations are so severe that the error is unrecoverable, but is that really the most common case with *detectable* programming errors? Regards, --> Mika Heiskanen

on Sun Dec 21 2008, Mika Heiskanen <mika.heiskanen-AT-fmi.fi> wrote:
No. In general libraries, invalid user input should be reported via a different mechanism, because there's no reason to believe that the condition should be handled far from the site where validation is requested. If you decide that throwing is an appropriate response for your application, you can do that in a layer outside the validation.
Stop the program immediately. If appropriate, execute emergency shutdown measures. Do not unwind stack, do not pass "Go," do not collect $200. ;-) -- Dave Abrahams BoostPro Computing http://www.boostpro.com

on Sat Dec 20 2008, Mika Heiskanen <mika.heiskanen-AT-fmi.fi> wrote:
I suggest reading through my posts in http://groups.google.com/group/comp.lang.c++.moderated/browse_frm/thread/800... which give the arguments in detail. Regards, -- Dave Abrahams BoostPro Computing http://www.boostpro.com

David Abrahams wrote:
Thank you for the link, I read the thread from start to finish. It was not clear to me at the end that any agreement was actually reached, in particular with respect to terminology. Wikipedia says: a) In computer science, a predicate that, if true, will remain true throughout a specific sequence of operations, is called (an) invariant to that sequence. b) In computer programming, a precondition is a condition or predicate that must always be true just prior to the execution of some section of code or before an operation in a formal specification. So, an invariant is a value which has already satified a predicate, and if it no longer satisfies one, a logic error must have occurred. On the other hand a precondition is something that must be true before being able to execute some section of code. I can se why an invariant failure should abort, but not why a precondition failure should. In particular, Wikipedia says: If a precondition is violated, the effect of the section of code becomes undefined and thus may or may not carry out its intended work. So, if were never to execute the section of code, the state could remain perfectly well defined. The definition does not seem to imply that the state must already be undefined. Is this the correct terminology? In the link you gave you seem to have the opinion that a precondition failure is generally fatal, but according to the Wikipedia definition it may not be if you never execute the code. These are separate quotes from your posts in the link you gave: If preconditions are broken, your program state is broken, by definition. Trying to recover is generally ill-advised. Go back to the definition of precondition from Wikipedia: the consequence is undefined behavior. Which is correct? Does a precondition violation always cause undefined behaviour, or only if the preconditioned code is executed? Should I substitute "invariant" for "precondition" in the above quotes? Sorry if I am being a nuisance. I thought I had already understood the terminology, but reading the thread you mentioned made it a fuzzy again. --> Mika Heiskanen

Mika Heiskanen wrote: there was a bug in a calculation following that? Where was that bug, and where might the bad value have spread since then? Is the current piece of code really the first place to encounter the bad value, or was there another place that already used it because it doesn't check preconditions as thoroughly? If you can answer all these questions at the time you decide what to do on a broken precondition (i.e. while writing the program), you should be able to pinpoint the actual error and fix it. If you can't answer them, then you can't trust the state of your program and should abort. Sebastian

on Tue Dec 23 2008, Mika Heiskanen <mika.heiskanen-AT-fmi.fi> wrote:
I don't care to aargue about terminology, but those two definitions are certainly compatible.
Detecting either a broken supposed-invariant or a failed precondition means there's a logic error in the program and state may already be arbitrarily corrupt.
The state set up by the calling code must in general be assumed to be already broken. Precondition violations don't just come about because the immediate caller isn't obeying the rules. The immediate caller may have done everything correctly.
The definition does not seem to imply that the state must already be undefined.
There's no such thing as undefined state. The behavior of the code is undefined.
Both
Does a precondition violation always cause undefined behaviour,
Yes
or only if the preconditioned code is executed?
If the code isn't executed, the precondition doesn't apply. There's a precondition on integer division that the divisor be nonzero. If you call a variable "divisor" and never actually use it as a divisor, there's no precondition violation. If you set preconditions on your function, they apply to your callers, and thus are true or not at its interface, not to some point during the execution after you've done some checking.
Should I substitute "invariant" for "precondition" in the above quotes?
No
Hope this helped. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

Mika Heiskanen wrote:
I also read the thread from start to finish. About halfway through the thread, I suddenly could follow the arguments given by David Abrahams, and he seems to be right. But now std::logic_error suddenly seems to make no sense any longer. Is this a valid conclusion from reading that thread? Then I reviewed my past issues with using exceptions in the light of this new insight. My worst issues with exceptions occured in connection with OpenMP and MPI. Because I used exceptions as a way to report errors to the user, I wanted them to propagate upwards out of the parallel regions. But because the exceptions don't occur synchronous in all workers, this just messed up the control flow. And neither OpenMP nor MPI try to help the programmer with the task of synchronising exceptions and propagating them upwards. However, as soon as I issue exceptions only in exceptional circumstances, the whole mess becomes much more managable again. So a manageable solution would be to handle "valid" user errors before entering the parallel sections of the programm, handle the few remaining "valid" exceptions (like out of memory conditions) by explicit communication between the workers, and don't try to recover from "logic errors" inside the parallel sections. This may force me to structure my (parallel) algorithms such that they have explicit resource allocation phases (during which no communication occurs, but exceptions are allowed), and running phases (during which no exceptions are allowed, but communication may occur). Does this solution makes sense? Is there a better solution for this problem?
Sorry for misusing this thread. But reading through the above thread took me quite some time (yet the new insight was worth it), so now I'm eager to get some answers to the questions and thoughts that this thread induced. Regards, Thomas

Thomas Klimpel wrote:
I usually try to follow the structure you described. However, previously I had seen no serious harm in throwing inside the running section even if I knew I had done the same test in the first phase. David Abrahams wrote:
Thank you, this pretty much nails it for me. I do believe http://en.wikipedia.org/wiki/Precondition is more lenient in the definition. In particular this sentence If a precondition is violated, the effect of the section of code becomes undefined and thus may or may not carry out its intended work. seems to allow more for a chance of recovery. David's "state may already be arbitrarily corrupt" would make a big difference in conveying the seriousness of the situation. Regards, --> Mika Heiskanen

on Tue Dec 23 2008, "Thomas Klimpel" <Thomas.Klimpel-AT-synopsys.com> wrote:
IMO std::logic_error never made much sense for C++.
"in exceptional circumstances" has a nice ring to it, but it doesn't mean very much in actuality. I suggest you phrase your criterion moree precisely so you know what you've actually discovered. My criterion is, "issue an exception when a function cannot meet its postcondition."
Don't try to recover from logic errors, period, unless you have some kind of "state firewall" like a process boundary that allows you to be very sure that the rest of the system is OK. In the case of MPI, I even a process boundary might not be enough.
I haven't thought about this very much in the context of MPI, but I've been doing some MPI programming myself recently, and IIUC it's important for all the processes to be following roughly the same code path. So if you get an exception in one process but not the others, it could be easy to confuse your parallel programs unless you do something to account for the problem.
Well, this is a good point; the whole thing is a bit off-topic for Boost, unless we make it about how Boost.MPI might better support exception handling. -- Dave Abrahams BoostPro Computing http://www.boostpro.com
participants (7)
-
David Abrahams
-
Mika Heiskanen
-
Peter Simons
-
Robert Kawulak
-
Sebastian Redl
-
Thomas Klimpel
-
Thorsten Ottosen