Re: [PATCH 12/22] Add exception handling classes

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




> On 2 Mar 2018, at 17:16, Jonathon Jongsma <jjongsma@xxxxxxxxxx> wrote:
> 
> On Fri, 2018-03-02 at 11:53 +0100, Lukáš Hrázký wrote:
>> On Fri, 2018-03-02 at 03:03 -0500, Frediano Ziglio wrote:
>>>> 
>>>>> On 1 Mar 2018, at 13:13, Frediano Ziglio <fziglio@xxxxxxxxxx>
>>>>> wrote:
>>>>> 
>>>>>> 
>>>>>> From: Christophe de Dinechin <dinechin@xxxxxxxxxx>
>>>>>> 
>>>>>> Throwing 'runtime_error' directly should be reserved for the
>>>>>> support
>>>>>> library.  Add an 'Error' class as a base class for all errors
>>>>>> thrown
>>>>>> by the streaming agent, as well as subclasses used to
>>>>>> discriminate
>>>>>> between categories of error.
>>>>>> 
>>>>>> The Error class offers:
>>>>>> - a 'format_message' member function that formats a message
>>>>>> without
>>>>>> allocating memory, so that there is no risk of throwing
>>>>>> another
>>>>>> exception at throw time.
>>>>>> 
>>>>> 
>>>>> why not formatting the message constructing the object so
>>>>> before the throw?
>>>> 
>>>> That requires dynamic allocation, which is entirely unnecessary
>>>> and
>>>> potentially dangerous (replacing one exception kind with
>>>> another).
>>>> 
>>> 
>>> As explained by my string.c_str example your interface is prone to
>>> errors. In the rare case that on small allocations we are replacing
>>> the error with a std::bad_alloc but introducing other problems.
>>> I prefer to reuse standard classes and support better the not rare
>>> cases.
>> 
>> This is a very good point by Frediano and I also don't find it
>> acceptable, no matter how much you stress it in the docstring. The
>> interface is inviting to make this error...
> 
> FWIW, I also find myself in agreement with Frediano and Lukas on this point.

OK. Maybe I misunderstood something.

Do we agree that the case Frediano raised is someone trying:

	throw Error(“The value of “ + std::to_string(2) + “ does not match “ + std::to_string(1));

which yields the following error:

	spice-streaming-agent.cpp:165:93: error: no matching function for call to ‘spice::streaming_agent::Error::Error(std::__cxx11::basic_string<char>)’

So far, I think the interface was not “error prone”. It caught the error correctly.

Therefore, in order to find the interface “error prone”, one has to accept a second hypothesis, which is that whoever attempted that, after seeing that error message, looks at the interface, which has a ‘const char *’ and a comment that specifically states "It is recommended to only use static C strings, since formatting of any dynamic argument is done by ‘format_message’”, and after reading that comment, has a big “Aha” moment and writes:

	throw Error((“The value of “ + std::to_string(2) + “ does not match “ + std::to_string(1)).c_str());

I’m sorry, but I cannot accept that step of the reasoning as being even remotely valid. Just looking at the code makes me want to barf.

So either I misunderstood Frediano’s point, or the reasoning is deeply flawed in a not very subtle way.

I’ll let it stew over the week-end, I’m tired of arguing what appears as an evidence to me.

Also, it’s not a vote, it’s a matter of Frediano’s reasoning being correct or not.

> 
>> 
>> (And as I stated before, I agree with the rest of Frediano's
>> reasoning
>> on the delayed formatting)
>> 
>>>> You can still format the message at throw point for the purpose
>>>> of sending it
>>>> to the syslog. This only uses the stack, no dynamic allocation.
>>>> 
>>> 
>>> Supposing that you only get the error is a bad design and also if,
>>> as
>>> you are saying, the only purpose is getting the message
>>> runtime_error
>>> is perfectly fine.
>>> 
>>>> It’s also more efficient, since we only format when we need to,
>>>> not earlier.
>>>> 
>>> 
>>> You are always formatting during the exception which being
>>> "exceptional"
>>> is by definition cold path. If in the event of error you don't log
>>> anything
>>> is faster, not a big point.
>>> Also limit the possible output string and is using plain C function
>>> to
>>> achieve this
>>> 
>>>>> 
>>>>>> - a 'syslog' member function that sends the formatted message
>>>>>> to the
>>>>>> syslog.
>>>>>> 
>>>>> 
>>>>> why this is needed?
>>>> 
>>>> 1. DRY principle, avoids repeated (and dispersed) calls to
>>>> syslog.
>>>> 2. Along with Error constructor, offers a convenient breakpoint
>>>> 3. Put all our syslog-for-errors stuff in a single place, easier
>>>> to change.
>>>> 
>>> 
>>> void report_error(const std::exception& exp); resolves all the
>>> above
>>> without introducing design dependencies.
>>> 
>>>> 
>>>>> can't just call what() as a standard exception handling?
>>>> 
>>>> The standard what() is still supported, returns a base message.
>>>> 
>>> 
>>> The question is why not using it instead of rewriting it?
>>> To support rare cases and adding problems and code?
>>> 
>>>> 
>>>>> 
>>>>>> The derived classes are:
>>>>>> 
>>>>>> - IOError encapsulates typical I/O errors that report errors
>>>>>> using
>>>>>> errno.  The format_message in that case prints the original
>>>>>> message,
>>>>>> along with the strerror message.
>>>>>> 
>>>>>> - The ReadError and WriteError are two classes derived from
>>>>>> IOError,
>>>>>> with the sole purpose of making it possible to discriminate
>>>>>> the
>>>>>> error type at catch time, e.g. to retry writes.
>>>>>> 
>>>>>> These classes are used in the subsequent patches
>>>>>> 
>>>>>> Signed-off-by: Christophe de Dinechin <dinechin@xxxxxxxxxx>
>>>>>> ---
>>>>>> include/spice-streaming-agent/Makefile.am |  2 +-
>>>>>> include/spice-streaming-agent/errors.hpp  | 55
>>>>>> ++++++++++++++++++++++++++++++
>>>>>> src/Makefile.am                           |  1 +
>>>>>> src/errors.cpp                            | 56
>>>>>> +++++++++++++++++++++++++++++++
>>>>>> src/spice-streaming-agent.cpp             |  5 +++
>>>>>> src/unittests/Makefile.am                 |  1 +
>>>>>> 6 files changed, 119 insertions(+), 1 deletion(-)
>>>>>> create mode 100644 include/spice-streaming-agent/errors.hpp
>>>>>> create mode 100644 src/errors.cpp
>>>>>> 
>>>>>> diff --git a/include/spice-streaming-agent/Makefile.am
>>>>>> b/include/spice-streaming-agent/Makefile.am
>>>>>> index 844f791..a223d43 100644
>>>>>> --- a/include/spice-streaming-agent/Makefile.am
>>>>>> +++ b/include/spice-streaming-agent/Makefile.am
>>>>>> @@ -3,5 +3,5 @@ public_includedir = $(includedir)/spice-
>>>>>> streaming-agent
>>>>>> public_include_HEADERS = \
>>>>>> 	frame-capture.hpp \
>>>>>> 	plugin.hpp \
>>>>>> +	errors.hpp \
>>>>>> 	$(NULL)
>>>>> 
>>>>> public header? So you are using it on the plugins ?
>>>> 
>>>> Not yet, but want to.
>>>> 
>>> 
>>> If this is the quality level I would reject this patch.
>>> This is mostly replacing a current no-problem with multiple
>>> problems.
>>> 
>>>>> 
>>>>>> -
>>>>>> diff --git a/include/spice-streaming-agent/errors.hpp
>>>>>> b/include/spice-streaming-agent/errors.hpp
>>>>>> new file mode 100644
>>>>>> index 0000000..ca70d2e
>>>>>> --- /dev/null
>>>>>> +++ b/include/spice-streaming-agent/errors.hpp
>>>>>> @@ -0,0 +1,55 @@
>>>>>> +/* Errors that may be thrown by the agent
>>>>>> + *
>>>>>> + * \copyright
>>>>>> + * Copyright 2018 Red Hat Inc. All rights reserved.
>>>>>> + */
>>>>>> +#ifndef SPICE_STREAMING_AGENT_ERRORS_HPP
>>>>>> +#define SPICE_STREAMING_AGENT_ERRORS_HPP
>>>>>> +
>>>>>> +#include <exception>
>>>>>> +#include <stddef.h>
>>>>>> +
>>>>>> +namespace spice {
>>>>>> +namespace streaming_agent {
>>>>>> +
>>>>>> +class Error : public std::exception
>>>>>> +{
>>>>>> +public:
>>>>>> +    Error(const char *message): exception(),
>>>>>> message(message) { }
>>>>> 
>>>>> what if the message came from a std::string::c_str() ?
>>>> 
>>>> There is no need to do that when you can pass arguments and do
>>>> deferred
>>>> formatting.
>>>> 
>>> 
>>> Don't agree, having a base class and passing a "const char*" as a
>>> general message suggests in many cases to have a dynamic message.
>>> If for instance for some cases you want to add line informations
>>> the message will be dynamic. You are basically forcing users to
>>> know this
>>> detail and add another class inheriting from Error and using C code
>>> to format the string, is not that flexible just to add some
>>> information.
>>> 
>>>>> Potentially message will point to an invalid memory location
>>>>> during the
>>>>> format_message/what.
>>>> 
>>>> Yes. Don’t do that. Added it to the documentation.
>>>> 
>>>>> 
>>>>>> +    Error(const Error &error): exception(error),
>>>>>> message(error.message)
>>>>>> {}
>>>>>> +    virtual const char *what() const noexcept override;
>>>>>> +    virtual int format_message(char *buffer, size_t size)
>>>>>> const noexcept;
>>>>>> +    const Error &syslog() const noexcept;
>>>>>> +protected:
>>>>>> +    const char *message;
>>>>>> +};
>>>>>> +
>>>>> 
>>>>> As a public header these definition require more documentation.
>>>> 
>>>> Added.
>>>> 
>>>> 
>>>>> Did you consider visibility?
>>>> 
>>>> Yes. The classes are defined in a a.out.
>>>> 
>>> 
>>> Do you think that this make visible from plugins loaded
>>> dynamically?
>>> a.out? Windows?
>>> 
>>>> 
>>>>> Did you consider Windows platform? These stuff should be in a
>>>>> DLL.
>>>> 
>>>> No, they are in the .exe. Even if we move the agent server side,
>>>> following
>>>> Marc-André’s presentation, I’m inclined to think that this should
>>>> remain a
>>>> separate process. Too dangerous to bring arbitrary proprietary
>>>> plugins in
>>>> the QEMU process space.
>>>> 
>>> 
>>> ?? don't apply here. We are talking about running on the guest and
>>> portability design.
>>> 
>>>>> 
>>>>>> +class IOError : public Error
>>>>>> +{
>>>>>> +public:
>>>>>> +    IOError(const char *msg, int saved_errno) : Error(msg),
>>>>>> saved_errno(saved_errno) {}
>>>>>> +    int format_message(char *buffer, size_t size) const
>>>>>> noexcept
>>>>>> override;
>>>>>> +    int append_strerror(char *buffer, size_t size, int
>>>>>> written) const
>>>>>> noexcept;
>>>>>> +protected:
>>>>>> +    int saved_errno;
>>>>>> +};
>>>>>> +
>>>>>> +class WriteError : public IOError
>>>>>> +{
>>>>>> +public:
>>>>>> +    WriteError(const char *msg, const char *operation, int
>>>>>> saved_errno)
>>>>>> +        : IOError(msg, saved_errno), operation(operation) {}
>>>>>> +    int format_message(char *buffer, size_t size) const
>>>>>> noexcept
>>>>>> override;
>>>>>> +protected:
>>>>>> +    const char *operation;
>>>>>> +};
>>>>>> +
>>>>>> +class ReadError : public IOError
>>>>>> +{
>>>>>> +public:
>>>>>> +    ReadError(const char *msg, int saved_errno):
>>>>>> IOError(msg,
>>>>>> saved_errno)
>>>>>> {}
>>>>>> +};
>>>>>> +
>>>>>> +}} // namespace spice::streaming_agent
>>>>>> +
>>>>>> +#endif // SPICE_STREAMING_AGENT_ERRORS_HPP
>>>>>> diff --git a/src/Makefile.am b/src/Makefile.am
>>>>>> index 3717b5c..2507844 100644
>>>>>> --- a/src/Makefile.am
>>>>>> +++ b/src/Makefile.am
>>>>>> @@ -55,4 +55,5 @@ spice_streaming_agent_SOURCES = \
>>>>>> 	mjpeg-fallback.hpp \
>>>>>> 	jpeg.cpp \
>>>>>> 	jpeg.hpp \
>>>>>> +	errors.cpp \
>>>>>> 	$(NULL)
>>>>>> diff --git a/src/errors.cpp b/src/errors.cpp
>>>>>> new file mode 100644
>>>>>> index 0000000..01bb162
>>>>>> --- /dev/null
>>>>>> +++ b/src/errors.cpp
>>>>>> @@ -0,0 +1,56 @@
>>>>>> +/* Errors that may be thrown by the agent
>>>>>> + *
>>>>>> + * \copyright
>>>>>> + * Copyright 2018 Red Hat Inc. All rights reserved.
>>>>>> + */
>>>>>> +
>>>>>> +#include <spice-streaming-agent/errors.hpp>
>>>>>> +
>>>>>> +#include <stdio.h>
>>>>>> +#include <syslog.h>
>>>>>> +#include <string.h>
>>>>>> +
>>>>>> +namespace spice {
>>>>>> +namespace streaming_agent {
>>>>>> +
>>>>>> +const char *Error::what() const noexcept
>>>>>> +{
>>>>>> +    return message;
>>>>>> +}
>>>>>> +
>>>>>> +int Error::format_message(char *buffer, size_t size) const
>>>>>> noexcept
>>>>>> +{
>>>>>> +    return snprintf(buffer, size, "%s", message);
>>>>>> +}
>>>>>> +
>>>>>> +const Error &Error::syslog() const noexcept
>>>>>> +{
>>>>>> +    char buffer[256];
>>>>> 
>>>>> I would use 1024 (standard syslog limit).
>>>> 
>>>> Good point. Could not find any obvious SYSLOG_MAX value, though.
>>>> 
>>>>> Still I don't think syslog call should be here.
>>>> 
>>>> You gave no argument against (“I don’t think” being an opinion,
>>>> not an
>>>> argument).
>>>> I have given several arguments in favor of this approach, please
>>>> make sure
>>>> you address them.
>>>> 
>>> 
>>> See above
>>> 
>>>>> 
>>>>>> +    format_message(buffer, sizeof(buffer));
>>>>>> +    ::syslog(LOG_ERR, "%s\n", buffer);
>>>>> 
>>>>> "\n" is not necessary
>>>> 
>>>> Removed.
>>>> 
>>>>> 
>>>>>> +    return *this;
>>>>>> +}
>>>>>> +
>>>>>> +int IOError::format_message(char *buffer, size_t size) const
>>>>>> noexcept
>>>>>> +{
>>>>>> +    int written = snprintf(buffer, size, "%s", what());
>>>>>> +    return append_strerror(buffer, size, written);
>>>>>> +}
>>>>>> +
>>>>>> +int IOError::append_strerror(char *buffer, size_t size, int
>>>>>> written)
>>>>>> const
>>>>>> noexcept
>>>>>> +{
>>>>>> +    // The conversion of written to size_t also deals with
>>>>>> the case where
>>>>>> written < 0
>>>>>> +    if (saved_errno && (size_t) written < size) {
>>>>>> +        written += snprintf(buffer + written, size -
>>>>>> written, ": %s
>>>>>> (errno
>>>>>> %d)",
>>>>>> +                            strerror(saved_errno),
>>>>>> saved_errno);
>>>>>> +    }
>>>>>> +    return written;
>>>>>> +}
>>>>>> +
>>>>>> +int WriteError::format_message(char *buffer, size_t size)
>>>>>> const noexcept
>>>>>> +{
>>>>>> +    int written = snprintf(buffer, size, "%s writing %s",
>>>>>> what(),
>>>>>> operation);
>>>>>> +    return append_strerror(buffer, size, written);
>>>>>> +}
>>>>>> +
>>>>>> +}} // namespace spice::streaming_agent
>>>>>> diff --git a/src/spice-streaming-agent.cpp b/src/spice-
>>>>>> streaming-agent.cpp
>>>>>> index 8494a8b..23ee824 100644
>>>>>> --- a/src/spice-streaming-agent.cpp
>>>>>> +++ b/src/spice-streaming-agent.cpp
>>>>>> @@ -13,6 +13,7 @@
>>>>>> 
>>>>>> #include <spice-streaming-agent/frame-capture.hpp>
>>>>>> #include <spice-streaming-agent/plugin.hpp>
>>>>>> +#include <spice-streaming-agent/errors.hpp>
>>>>>> 
>>>>>> #include <stdio.h>
>>>>>> #include <stdlib.h>
>>>>>> @@ -597,6 +598,10 @@ int main(int argc, char* argv[])
>>>>>>    try {
>>>>>>        do_capture(stream, streamport, f_log);
>>>>>>    }
>>>>>> +    catch (Error &err) {
>>>>>> +        err.syslog();
>>>>>> +        ret = EXIT_FAILURE;
>>>>>> +    }
>>>>>>    catch (std::runtime_error &err) {
>>>>>>        syslog(LOG_ERR, "%s\n", err.what());
>>>>>>        ret = EXIT_FAILURE;
>>>>>> diff --git a/src/unittests/Makefile.am
>>>>>> b/src/unittests/Makefile.am
>>>>>> index 0fc6d50..ce81b56 100644
>>>>>> --- a/src/unittests/Makefile.am
>>>>>> +++ b/src/unittests/Makefile.am
>>>>>> @@ -39,6 +39,7 @@ test_mjpeg_fallback_SOURCES = \
>>>>>> 	test-mjpeg-fallback.cpp \
>>>>>> 	../jpeg.cpp \
>>>>>> 	../mjpeg-fallback.cpp \
>>>>>> +	../errors.cpp \
>>>>>> 	$(NULL)
>>>>>> 
>>>>>> test_mjpeg_fallback_LDADD = \
>>>>> 
>>>>> Frediano
>>>> 
>>>> 
>>> 
>>> _______________________________________________
>>> Spice-devel mailing list
>>> Spice-devel@xxxxxxxxxxxxxxxxxxxxx
>>> https://lists.freedesktop.org/mailman/listinfo/spice-devel
>> 
>> _______________________________________________
>> Spice-devel mailing list
>> Spice-devel@xxxxxxxxxxxxxxxxxxxxx
>> https://lists.freedesktop.org/mailman/listinfo/spice-devel
> _______________________________________________
> Spice-devel mailing list
> Spice-devel@xxxxxxxxxxxxxxxxxxxxx
> https://lists.freedesktop.org/mailman/listinfo/spice-devel

_______________________________________________
Spice-devel mailing list
Spice-devel@xxxxxxxxxxxxxxxxxxxxx
https://lists.freedesktop.org/mailman/listinfo/spice-devel




[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux]     [Linux OMAP]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]     [Monitors]