Operating System API Error Reporting Guidelines

Each Boost library that calls operating system API's seems to deal with API error reporting in its own way. That's hard on users, and doubly so if a particular library's approach doesn't easily support a needed use case. Boost.Filesystem certainly had that problem, and users reported having to litter their code with try/catch blocks to cope with it only supporting error-reporting-by-exception. I think the two-pronged approach Boost.Filesystem finally settled on might work well for other library's too. I've written it up as a general guideline. See attached. Do other Boosters like the idea of an overall guideline for dealing with operating system API error reporting? Do the specific guidelines make sense? How can they be improved? Comments welcome, --Beman Operating System API Error Reporting Guidelines Introduction Operating system application program interface (API) functions may encounter errors. How should these errors be reported by Boost Library functions that use the operating system API? Guidelines Unless otherwise specified, the default versions of all Boost library functions, except destructors, that call operating system API functions should check for API errors and report the occurrence of such errors by throwing an exception of type boost::system_error. Such functions should also have an overload that takes an additional argument of type boost::system_error_code& ec. The behavior of this overload is the same as the non-overloaded version, except that it does not throw an exception on an API error, and it sets ec to the error code reported by the operating system, or to 0 if no error is reported. For functions where the non-overloaded version returns void, the overloaded version returns an object of type boost::system_error_code with the same value that ec was set to. The problem The traditional C approach to error reporting followed by many operating systems is for the API function to indicate failure by returning an out-of-range value, and to set a global variable with an error code indicating the particular error condition. API functions that do no return an out-of-range value just set the error code global. These C error reporting idioms are viewed with distaste by C++ programmers: * It is far too easy and common for programmers to ignore error returns. * Global variables are dangerous. * It is difficult in reading code that ignores errors to know if lack of checking was done for a well-founded reason or simply by mistake. * Checking for errors obscures otherwise clean code. * Requires error checking be done immediately even though the most logical place to deal with errors may be higher up in the call chain. * Because the mechanism for reporting errors isn't uniform, coding errors are a concern as is the need to refer excessively to documentation. One possible C++ approach is to throw an exception whenever an operating system API error occurs. The exception can capture the particular error code from the operating system, or a fine-grained exception hierarchy can provide a type for each possible error code. This approach solves all of the problems of the tradition C approach, but suffers from the usual problems associated with exceptions: * Very time inefficient (factor of 1000 not uncommon) compared to error returning codes via return statement. * Clutters code with try/catch blocks when errors must be dealt with immediately. * May be totally inappropriate in some contexts (particularly destructors). * Is the wrong idiom when errors are commonplace and unexceptional. Traditionally, when C++ developers don't feel exceptions are appropriate for API error reporting, they take several approaches: * Unconditionally ignore the error. * Provided a nothrow overload that ignores the error. * Fallback to the errno approach. None of these is entirely satisfactory, and the lack of uniformity between libraries is a user irritation. Rationale The default version of library functions, without the additional argument, throws on errors, ensuring that error checking cannot be ignored and covering the important use case where error reporting via exception is the preferred mechanism. The overloaded version of library functions, with the additional boost::system_error_code& argument, covers both the use case where errors are best ignored, and the use case where errors are best reported directly rather than by exception. Making error-reporting-by-exception uniformly the default, without any attempt to prejudge the most commonly usage case, aids learning and avoids endless arguments over which approach is "best". Providing both error-reporting-by-exception and error-reporting-by-error-code ensures common use cases are accommodated, and moves the decision as to which to use from the library designer to the library user. Returning a copy of the error code, in the case of functions that otherwise would otherwise return void, results in cleaner user code. No doing so in the case of functions already returning a value keeps the interface simple. History Experience with the Boost.Filesystem library led to the conclusion that both error-reporting-by-exception and error-reporting-by-error-code are important, and which is best is highly context dependent. _________________________________________________________________ Revised 17 Feb 2006 © Copyright Beman Dawes 2006 Distributed under the Boost Software License, Version 1.0. (See accompanying file [1]LICENSE_1_0.txt or copy at [2]www.boost.org/LICENSE_1_0.txt) References 1. file://localhost/C|/boost/site/LICENSE_1_0.txt 2. http://www.boost.org/LICENSE_1_0.txt

Beman Dawes wrote:
Do other Boosters like the idea of an overall guideline for dealing with operating system API error reporting?
Sure.
Do the specific guidelines make sense? How can they be improved?
What about system calls that fail in a constructor? Providing a no-throw constructor that could leave an object in an invalid state can make it much more difficult to reason about program correctness. What is the recommendation in this case? Some people may want to make the exception-throwing versions unavailable to guarantee they don't accidentally call one. There should be a recommended way of doing that (eg., a standard PP macro). -- Eric Niebler Boost Consulting www.boost-consulting.com

"Eric Niebler" <eric@boost-consulting.com> wrote in message news:43F64354.3010104@boost-consulting.com...
Beman Dawes wrote:
Do other Boosters like the idea of an overall guideline for dealing with operating system API error reporting?
Sure.
Do the specific guidelines make sense? How can they be improved?
What about system calls that fail in a constructor? Providing a no-throw constructor that could leave an object in an invalid state can make it much more difficult to reason about program correctness. What is the recommendation in this case?
Good question. In Boost.Filesystem, the only no-throw constructor added so far was for directory iterators, and those can be left in a valid state (the end iterator). The case you mention is a strong argument for not supplying a non-throwing version 100% of the time. There may be some other cases where the usefulness of either the throwing or non-throwing version is questionable. I'll think about this a bit more and draft some additional wording. I also meant to add wording to the effect that if a destructor does something that might result in an API error, there should be a close-like function with both throwing and non-throwing versions that performs the error-possible API operation. The effect of the destructor is just to call, if needed, the non-throwing version of that function.
Some people may want to make the exception-throwing versions unavailable to guarantee they don't accidentally call one. There should be a recommended way of doing that (eg., a standard PP macro).
Interesting. Such a macro only makes sense if both throwing and non-throwing versions are available for most or all functionality. Thanks for the comments, --Beman

On 2/17/06, Beman Dawes <bdawes@acm.org> wrote:
Do the specific guidelines make sense? How can they be improved?
The proposal appears as one massive paragraph in your email and is quite difficult to read as a result. Can you resend it with text/plain formatting or as an attachment perhaps? -- Caleb Epstein caleb dot epstein at gmail dot com

On 2/17/06, Caleb Epstein <caleb.epstein@gmail.com> wrote:
The proposal appears as one massive paragraph in your email and is quite difficult to read as a result. Can you resend it with text/plain formatting or as an attachment perhaps?
Some more detail: the attachment is called "error_reporting_guidelines.html" but carries Content-Type: text/plain (and indeed appears to be formatted plain-text). This seems to be confusing GMail at least. -- Caleb Epstein caleb dot epstein at gmail dot com

Caleb Epstein <caleb.epstein@gmail.com> writes:
On 2/17/06, Caleb Epstein <caleb.epstein@gmail.com> wrote:
The proposal appears as one massive paragraph in your email and is quite difficult to read as a result. Can you resend it with text/plain formatting or as an attachment perhaps?
Some more detail: the attachment is called "error_reporting_guidelines.html" but carries Content-Type: text/plain (and indeed appears to be formatted plain-text). This seems to be confusing GMail at least.
Looks right to me (Gnus/emacs/Win32). See also http://lists.boost.org/Archives/boost/2006/02/101041.php -- Dave Abrahams Boost Consulting www.boost-consulting.com

Caleb Epstein wrote:
On 2/17/06, Caleb Epstein <caleb.epstein@gmail.com> wrote:
The proposal appears as one massive paragraph in your email and is quite difficult to read as a result. Can you resend it with text/plain formatting or as an attachment perhaps?
Some more detail: the attachment is called "error_reporting_guidelines.html" but carries Content-Type: text/plain (and indeed appears to be formatted plain-text). This seems to be confusing GMail at least.
No idea what happened. I just sent myself a test email with the file attached and it worked fine. I'm trying again, attached to this message. Just in case that doesn't work, you can also look at http://www.esva.net/~beman/error_reporting_guidelines.html Sorry for the inconvenience, --Beman Operating System API Error Reporting Guidelines Introduction Operating system application program interface (API) functions may encounter errors. How should these errors be reported by Boost Library functions that use the operating system API? Guidelines Unless otherwise specified, the default versions of all Boost library functions, except destructors, that call operating system API functions should check for API errors and report the occurrence of such errors by throwing an exception of type boost::system_error. Such functions should also have an overload that takes an additional argument of type boost::system_error_code& ec. The behavior of this overload is the same as the non-overloaded version, except that it does not throw an exception on an API error, and it sets ec to the error code reported by the operating system, or to 0 if no error is reported. For functions where the non-overloaded version returns void, the overloaded version returns an object of type boost::system_error_code with the same value that ec was set to. The problem The traditional C approach to error reporting followed by many operating systems is for the API function to indicate failure by returning an out-of-range value, and to set a global variable with an error code indicating the particular error condition. API functions that do no return an out-of-range value just set the error code global. These C error reporting idioms are viewed with distaste by C++ programmers: * It is far too easy and common for programmers to ignore error returns. * Global variables are dangerous. * It is difficult in reading code that ignores errors to know if lack of checking was done for a well-founded reason or simply by mistake. * Checking for errors obscures otherwise clean code. * Requires error checking be done immediately even though the most logical place to deal with errors may be higher up in the call chain. * Because the mechanism for reporting errors isn't uniform, coding errors are a concern as is the need to refer excessively to documentation. One possible C++ approach is to throw an exception whenever an operating system API error occurs. The exception can capture the particular error code from the operating system, or a fine-grained exception hierarchy can provide a type for each possible error code. This approach solves all of the problems of the tradition C approach, but suffers from the usual problems associated with exceptions: * Very time inefficient (factor of 1000 not uncommon) compared to error returning codes via return statement. * Clutters code with try/catch blocks when errors must be dealt with immediately. * May be totally inappropriate in some contexts (particularly destructors). * Is the wrong idiom when errors are commonplace and unexceptional. Traditionally, when C++ developers don't feel exceptions are appropriate for API error reporting, they take several approaches: * Unconditionally ignore the error. * Provided a nothrow overload that ignores the error. * Fallback to the errno approach. None of these is entirely satisfactory, and the lack of uniformity between libraries is a user irritation. Rationale The default version of library functions, without the additional argument, throws on errors, ensuring that error checking cannot be ignored and covering the important use case where error reporting via exception is the preferred mechanism. The overloaded version of library functions, with the additional boost::system_error_code& argument, covers both the use case where errors are best ignored, and the use case where errors are best reported directly rather than by exception. Making error-reporting-by-exception uniformly the default, without any attempt to prejudge the most commonly usage case, aids learning and avoids endless arguments over which approach is "best". Providing both error-reporting-by-exception and error-reporting-by-error-code ensures common use cases are accommodated, and moves the decision as to which to use from the library designer to the library user. Returning a copy of the error code, in the case of functions that otherwise would otherwise return void, results in cleaner user code. No doing so in the case of functions already returning a value keeps the interface simple. History Experience with the Boost.Filesystem library led to the conclusion that both error-reporting-by-exception and error-reporting-by-error-code are important, and which is best is highly context dependent. _________________________________________________________________ Revised 17 Feb 2006 © Copyright Beman Dawes 2006 Distributed under the Boost Software License, Version 1.0. (See accompanying file [1]LICENSE_1_0.txt or copy at [2]www.boost.org/LICENSE_1_0.txt) References 1. file://localhost/C|/boost/site/LICENSE_1_0.txt 2. http://www.boost.org/LICENSE_1_0.txt

Beman Dawes <bdawes@acm.org> writes:
Do other Boosters like the idea of an overall guideline for dealing with operating system API error reporting?
Yep.
Guidelines
Unless otherwise specified, the default versions of all Boost library functions, except destructors, that call operating system API functions should check for API errors and report the occurrence of such errors by throwing an exception of type boost::system_error.
or a class derived therefrom.
Such functions should also have an overload that takes an additional argument of type boost::system_error_code& ec. The behavior of this overload is the same as the non-overloaded version, except that it does not throw an exception on an API error, and it sets ec to the error code reported by the operating system, or to 0 if no error is reported.
For functions where the non-overloaded version returns void, the overloaded version returns an object of type boost::system_error_code with the same value that ec was set to.
Does it worry you that this will potentially expose an implementation detail of the functions (i.e. that they use a system call) which may even differ from platform to platform, making code nonportable? If we do this, system_error_code should probably be a type that checks at runtime that its value has been inspected, and asserts otherwise. -- Dave Abrahams Boost Consulting www.boost-consulting.com

"David Abrahams" <dave@boost-consulting.com> wrote in message news:u1wy1wipv.fsf@boost-consulting.com...
Beman Dawes <bdawes@acm.org> writes:
Guidelines
Unless otherwise specified, the default versions of all Boost library functions, except destructors, that call operating system API functions should check for API errors and report the occurrence of such errors by throwing an exception of type boost::system_error.
or a class derived therefrom.
Yep, fixed.
Such functions should also have an overload that takes an additional argument of type boost::system_error_code& ec. The behavior of this overload is the same as the non-overloaded version, except that it does not throw an exception on an API error, and it sets ec to the error code reported by the operating system, or to 0 if no error is reported.
For functions where the non-overloaded version returns void, the overloaded version returns an object of type boost::system_error_code with the same value that ec was set to.
Does it worry you that this will potentially expose an implementation detail of the functions (i.e. that they use a system call) which may even differ from platform to platform, making code nonportable?
Some libraries (Boost.Filesystem for example) really shouldn't try to hide the fact that there is an operating system underneath. Programmers have to be defensive when using them because of things like race-conditions with other processes. But with some libraries, yes, use of the OS API is very much an implementation detail that shouldn't be exposed. The current draft tries to be less dogmatic, and explictly acknowledges that there are cases when the guidelines shouldn't be applied. I hope that addresses this concern.
If we do this, system_error_code should probably be a type that checks at runtime that its value has been inspected, and asserts otherwise.
I thought about that quite a bit, and even started to implement such an approach for Boost.Filesystem. But then I decided that it was too nannyish and also complicated the case where the user very deliberately wishes to ignore errors. If someone uses the non-throwing version of a function, that is an explicit choice they made and presumably know what they are doing. (I was burned by being excessively nannyish with native versus portable path names; it is sometimes a fine line between trying to protect users from themselves and over constraining them.) Thanks, --Beman

Beman Dawes wrote:
"David Abrahams" <dave@boost-consulting.com> wrote in message news:u1wy1wipv.fsf@boost-consulting.com...
Does it worry you that this will potentially expose an implementation detail of the functions (i.e. that they use a system call) which may even differ from platform to platform, making code nonportable?
Some libraries (Boost.Filesystem for example) really shouldn't try to hide the fact that there is an operating system underneath.
What worries me is that with a single blessed system_error_code_type, the Filesystem approach (always implement directly on top of the OS) is imposed upon other libraries that may have other options. A threading library, for example, may sit on top of a pthread_* layer instead of talking directly to the OS; this means that it will get errno error codes back and not GetLastError. Even Boost.Filesystem is possible to implement on top on Microsoft's POSIX-like API and never talk to the OS directly.

David Abrahams wrote:
or a class derived therefrom.
If we want to be picky ... ;?) "or a class unambiguously and publicly derived therefrom". Assuming boost::system_error is derived from std::runtime_error, I would suggest that derived exceptions are unambiguously derived from std::runtime_error as well. Of course, this kills the flow of the text, so should be placed into a footnote <g> -- AlisdairM

Beman Dawes <bdawes@acm.org> writes:
Such functions should also have an overload that takes an additional argument of type boost::system_error_code& ec. The behavior of this overload is the same as the non-overloaded version, except that it does not throw an exception on an API error, and it sets ec to the error code reported by the operating system, or to 0 if no error is reported.
Why are system-error-reporting functions special in this regard? The rationale doesn't give me any reason not to think we need to do this with every function that could otherwise throw, regardless of the reason. If that's not the conclusion you want to reach, you should refine the rationale so it describes why system errors are more special than all the other ones. -- Dave Abrahams Boost Consulting www.boost-consulting.com

"David Abrahams" <dave@boost-consulting.com> wrote in message news:uslqhv41e.fsf@boost-consulting.com...
Beman Dawes <bdawes@acm.org> writes:
Such functions should also have an overload that takes an additional argument of type boost::system_error_code& ec. The behavior of this overload is the same as the non-overloaded version, except that it does not throw an exception on an API error, and it sets ec to the error code reported by the operating system, or to 0 if no error is reported.
Why are system-error-reporting functions special in this regard? The rationale doesn't give me any reason not to think we need to do this with every function that could otherwise throw, regardless of the reason. If that's not the conclusion you want to reach, you should refine the rationale so it describes why system errors are more special than all the other ones.
Good points. I'm going to think about the differences or lack thereof before trying to answer. Thanks, --Beman

For many non-system functions, the careful developer can be guaranteed not to encounter an error if he or she follows the library's specifications. E.g., if we so choose, we can ignore the fact that Boost.Any has a bad_any_cast, because we can ensure we will never see it, and we can likewise ignore many of the possible errors returned by Boost.Regex. For system errors, even design-by-contract will not save us from errors; it's for this reason we need the flexibility. On 2/19/2006 11:08, Beman Dawes wrote:
"David Abrahams" <dave@boost-consulting.com> wrote in message news:uslqhv41e.fsf@boost-consulting.com...
Beman Dawes <bdawes@acm.org> writes:
Such functions should also have an overload that takes an additional argument of type boost::system_error_code& ec. The behavior of this overload is the same as the non-overloaded version, except that it does not throw an exception on an API error, and it sets ec to the error code reported by the operating system, or to 0 if no error is reported. Why are system-error-reporting functions special in this regard? The rationale doesn't give me any reason not to think we need to do this with every function that could otherwise throw, regardless of the reason. If that's not the conclusion you want to reach, you should refine the rationale so it describes why system errors are more special than all the other ones.
Good points. I'm going to think about the differences or lack thereof before trying to answer.

Do other Boosters like the idea of an overall guideline for dealing with operating system API error reporting?
Yes.
Unless otherwise specified, the default versions of all Boost library functions, except destructors, that call operating system API functions should check for API errors and report the occurrence of such errors by throwing an exception of type boost::system_error.
OK, but what about "critical" errors: OS API failures that mean the library can't function at all, or even place it's object in a consistent state? Sometimes a for truely *exceptional* conditions a throw (or abort if no exceptions are available) makes more sense. I guess the problem is that the filesystem lib necessarily generates errors rather often, compare to most other libs, so the error conditions aren't "exceptional" in the traditional sense.
Such functions should also have an overload that takes an additional argument of type boost::system_error_code& ec. The behavior of this overload is the same as the non-overloaded version, except that it does not throw an exception on an API error, and it sets ec to the error code reported by the operating system, or to 0 if no error is reported.
Hmm, not so sure about this: certainly as far as regex is concerned, most OS API calls are critical for correct functioning of the library, but they're also very *very* rare to fail, and platform dependent whether OS API's are even called at all. What about other exceptional conditions that may arise: std lib errors, math errors etc? Should these have optional error code reporting as well? If so do we apply this to every API in Boost? I don't have any good answers to these questions, but I'd like to :-) BTW the rest of the paper sums up the issues very nicely, but I don't think we have the right resolution quite yet, or at least we should think very carefully before we leap in :-) John.

"John Maddock" <john@johnmaddock.co.uk> wrote in message news:01f301c6347a$22ed7680$f9540e52@fuji... (I tried to address all John's other comments in the current draft.)
What about other exceptional conditions that may arise: std lib errors, math errors etc? Should these have optional error code reporting as well? If so do we apply this to every API in Boost?
I don't know. I've been thinking entirely about operating system API errors, but as you and Dave point out, we need to thing more about if and why these errors are different from any other error. I need to mull that over for awhile. Thanks for your comments, --Beman

"Beman Dawes" <bdawes@acm.org> wrote in message news:43F61C1A.5060803@acm.org...
Each Boost library that calls operating system API's seems to deal with API error reporting in its own way. That's hard on users, and doubly so if a particular library's approach doesn't easily support a needed use case.
Boost.Filesystem certainly had that problem, and users reported having to litter their code with try/catch blocks to cope with it only supporting error-reporting-by-exception.
I think the two-pronged approach Boost.Filesystem finally settled on might work well for other library's too. I've written it up as a general guideline. See attached.
Do other Boosters like the idea of an overall guideline for dealing with operating system API error reporting?
Do the specific guidelines make sense? How can they be improved?
Comments welcome,
Fresh draft up at www.esva.net/~beman/error_reporting_guidelines.html. This tries to incorporate concerns, comments, and suggestions from Eric Niebler, Dave Abrahams, and John Maddock. Further comments welcome, --Beman

Beman Dawes wrote:
Fresh draft up at www.esva.net/~beman/error_reporting_guidelines.html.
This tries to incorporate concerns, comments, and suggestions from Eric Niebler, Dave Abrahams, and John Maddock.
Further comments welcome,
1. system_error 'obviously' needs to derive from std::exception, but whether it needs to derive from std::runtime_error isn't clear. 2. A constructor is provided for users to be able to construct a system_error, but this constructor requires a string and doesn't require a system error code. This is backwards; it is precisely the system error code that makes a system_error distinct from a mere runtime_error. Take it away, and it's not a system_error anymore. A system_error should be constructible from a system error code, with the library supplying an appropriate what() string. 3. lookup_errno assumes that there is a context-free mapping from system error codes to errno. The alternative approach of class system_error { public: int errno() const; system_error_code_type system_error_code() const; }; does not. 4. The proposed mechanism does not allow me to signal failure when all I have is an errno value, since I have no way of translating that back to a system error code. (Even if I did have such a way, it might not be desirable to use it because the roundtrip translation may or may not result in the original errno.) 5. Some system_errors can (and in some cases, ought to) be mapped to existing exceptions, the most prominent example being ENOMEM, which should be reported as std::bad_alloc. HTH :-)

Peter Dimov wrote:
1. system_error 'obviously' needs to derive from std::exception, but whether it needs to derive from std::runtime_error isn't clear.
Hum... I assumed that a system_error was 'obviously' a runtime error, and thus should be derived from std::runtime_error. What am I missing?
2. A constructor is provided for users to be able to construct a system_error, but this constructor requires a string and doesn't require a system error code. This is backwards; it is precisely the system error code that makes a system_error distinct from a mere runtime_error. Take it away, and it's not a system_error anymore. A system_error should be constructible from a system error code, with the library supplying an appropriate what() string.
Yep. Fixed.
3. lookup_errno assumes that there is a context-free mapping from system error codes to errno. The alternative approach of class system_error { public:
int errno() const; system_error_code_type system_error_code() const; };
does not.
Yes, that is a better packaging. See below.
4. The proposed mechanism does not allow me to signal failure when all I have is an errno value, since I have no way of translating that back to a system error code.
namespace boost { typedef int errno_type; enum errno_tag { errno_value }; typedef implementation-defined system_error_code_type; class system_error : public std::runtime_error { public: system_error( std::string & what_arg, system_error_code_type ec ); system_error( std::string & what_arg, errno_tag tag, errno_type en ); system_error( std::string & what_arg, system_error_code_type ec, errno_type en ); system_error_code_type system_error_code() const; errno_type errno_code() const; }; } With the first constructor, errno_code() is looked up from ec. With the second constructor, system_error_code() is looked up from en. With the third constructor, both are explicitly given.
(Even if I did have such a way, it might not be desirable to use it because the roundtrip translation may or may not result in the original errno.)
Use the third constructor if tight control over the mapping is needed.
5. Some system_errors can (and in some cases, ought to) be mapped to existing exceptions, the most prominent example being ENOMEM, which should be reported as std::bad_alloc.
Good point. Fixed.
HTH :-)
Yes, very much so. Thanks! --Beman

Beman Dawes <bdawes@acm.org> writes:
Peter Dimov wrote:
1. system_error 'obviously' needs to derive from std::exception, but whether it needs to derive from std::runtime_error isn't clear.
Hum... I assumed that a system_error was 'obviously' a runtime error, and thus should be derived from std::runtime_error. What am I missing?
std::runtime_error loses meaning pretty quickly when you look at it closely, since everything reported by an exception is a runtime error, by definition :) -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams wrote:
std::runtime_error loses meaning pretty quickly when you look at it closely, since everything reported by an exception is a runtime error, by definition :)
IIUC though, there remains a distinction between std::runtime_error and std::logic_error. The former is intended to report exceptional conditions that cannot be detected until runtime, such as losing a network connection halfway through a read. The latter is for conditions that can (potentially) be avoided by better coding, such as failing consistency checks and dividing by zero, where the violating arguments can be detected and re-routed up front. Ultimately, we (typically) throw a runtime_error after performing a check on some return value at run-time, so you could argue that program logic can handle recovery here too. I guess it is the difference between signalling 'help! stuff happened' and 'you shouldn't have done that!' System API failures are generally external to the component/library author's control, and fall into the 'stuff happened' category. -- AlisdairM

"AlisdairM" <alisdair.meredith@uk.renaultf1.com> writes:
David Abrahams wrote:
std::runtime_error loses meaning pretty quickly when you look at it closely, since everything reported by an exception is a runtime error, by definition :)
IIUC though, there remains a distinction between std::runtime_error and std::logic_error. The former is intended to report exceptional conditions that cannot be detected until runtime, such as losing a network connection halfway through a read. The latter is for conditions that can (potentially) be avoided by better coding, such as failing consistency checks and dividing by zero, where the violating arguments can be detected and re-routed up front.
And the latter should almost never be used :). IMO standardizing a logic_error exception was a big mistake. -- Dave Abrahams Boost Consulting www.boost-consulting.com

AlisdairM wrote:
David Abrahams wrote:
std::runtime_error loses meaning pretty quickly when you look at it closely, since everything reported by an exception is a runtime error, by definition :)
IIUC though, there remains a distinction between std::runtime_error and std::logic_error. The former is intended to report exceptional conditions that cannot be detected until runtime, such as losing a network connection halfway through a read. The latter is for conditions that can (potentially) be avoided by better coding, such as failing consistency checks and dividing by zero, where the violating arguments can be detected and re-routed up front.
We know. Think about it from a client PoV. Under what circumstances would you write a catch clause for runtime_error? (Or, to be more exact, under what circumstances would you catch a system_error using the runtime_error base?)

Beman Dawes wrote:
Peter Dimov wrote:
1. system_error 'obviously' needs to derive from std::exception, but whether it needs to derive from std::runtime_error isn't clear.
Hum... I assumed that a system_error was 'obviously' a runtime error, and thus should be derived from std::runtime_error. What am I missing?
I'm thinking from client point of view here. What catch clause do I need in order to handle system errors? A. catch( system_error ) ... if I want a system_error_code B. catch( ??? ) ... if I only want an errno code C. catch( runtime_error ) ... if I want ??? D. catch( exception ) ... if I only want a what() string. As you can see, there is one missing base, and derivation from runtime_error doesn't add any value from client PoV.
2. A constructor is provided for users to be able to construct a system_error, but this constructor requires a string and doesn't require a system error code. This is backwards; it is precisely the system error code that makes a system_error distinct from a mere runtime_error. Take it away, and it's not a system_error anymore. A system_error should be constructible from a system error code, with the library supplying an appropriate what() string.
Yep. Fixed.
I think that explicit system_error( system_error_code_type ec ); should also work if I don't need to override the what() string. system_error should be able to call FormatMessage or strerror for me. Maybe explicit system_error( system_error_code_type ec, errno_type en = 0, std::string const & what_arg = "" ); ? If we add the missing errno base: class errno_exception: public std::exception { public: explicit errno_exception( errno_type en, std::string const & what_arg = "" ); errno_type errno_code() const; }; and derive system_error from it, this would solve
4. The proposed mechanism does not allow me to signal failure when all I have is an errno value, since I have no way of translating that back to a system error code.
Finally,
5. Some system_errors can (and in some cases, ought to) be mapped to existing exceptions, the most prominent example being ENOMEM, which should be reported as std::bad_alloc.
this should probably be addressed via a convenience function: void throw_system_error( system_error_code_type ec, errno_type en = 0, std::string const & what_arg = "" ); that knows whether to throw bad_alloc or system_error to avoid the same logic being replicated in every library. I wonder whether it's possible to throw an exception on ENOMEM (OUT_OF_MEMORY) that can be caught as either bad_alloc or system_error. Perhaps not. std::exception would be an ambiguous base.

"Beman Dawes" <bdawes@acm.org> writes:
Fresh draft up at www.esva.net/~beman/error_reporting_guidelines.html.
This tries to incorporate concerns, comments, and suggestions from Eric Niebler, Dave Abrahams, and John Maddock.
Further comments welcome,
Did you consider making system_error_code_type a wrapper that will assert upon destruction if its value hasn't been inspected? That will detect one of the most common problems with non-exception error reporing: forgetting to check the error code. -- Dave Abrahams Boost Consulting www.boost-consulting.com

"Beman Dawes" <bdawes@acm.org> wrote
Do other Boosters like the idea of an overall guideline for dealing with operating system API error reporting?
Absolutely.
Do the specific guidelines make sense? How can they be improved?
I don't quite understand the part with void returns and overloaded and non-overloaded functions. Obviously, overloaded function declarations can't differ by return type only. Do you suggest to supply a non-throwing function with a different name/scope or a function template? If so, how should its interface look like? FWIW, I've often used a scheme of: void foo() { check(try_foo() ); } error_code_t try_foo() { ... } int bar() { int ret; check(try_bar(ret)); return ret; } error_code_t try_bar( int& i ) { ... } -hg

Holger Grund wrote:
"Beman Dawes" <bdawes@acm.org> wrote
Do other Boosters like the idea of an overall guideline for dealing with operating system API error reporting?
Absolutely.
Do the specific guidelines make sense? How can they be improved?
I don't quite understand the part with void returns and overloaded and non-overloaded functions. Obviously, overloaded function declarations can't differ by return type only. Do you suggest to supply a non-throwing function with a different name/scope or a function template? If so, how should its interface look like?
// function returning a value: int foo(); // throws on API error int foo( system_error_code_type& ec ); // does not throw // function not normally returning a value: void bar(); // throws on API error system_error_code_type foo(system_error_code_type& ec); // doesn't throw --Beman
participants (9)
-
AlisdairM
-
Beman Dawes
-
Caleb Epstein
-
David Abrahams
-
Eric Niebler
-
Eugene Talagrand
-
Holger Grund
-
John Maddock
-
Peter Dimov