[Spice-devel] [PATCH 12/22] Add exception handling classes

Christophe de Dinechin christophe.de.dinechin at gmail.com
Thu Mar 1 18:48:18 UTC 2018



> On 1 Mar 2018, at 15:46, Lukáš Hrázký <lhrazky at redhat.com> wrote:
> 
> On Thu, 2018-03-01 at 12:40 +0100, Lukáš Hrázký wrote:
>> On Wed, 2018-02-28 at 16:43 +0100, Christophe de Dinechin wrote:
>>> From: Christophe de Dinechin <dinechin at redhat.com>
>>> 
>>> 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.
>>> 
>>> - a 'syslog' member function that sends the formatted message to the syslog.
>>> 
>>> 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 at redhat.com>
>>> ---
>>> 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)
>>> -
>>> 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) { }
>> 
>> No need to call exception() explicitely. Space before colon?
>> 
>>> +    Error(const Error &error): exception(error), message(error.message) {}
>> 
>> Isn't the generated copy constructor the same?
>> 
>>> +    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;
>>> +};
>>> +
>>> +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;
>> 
>> append_strerror probably should be protected?
>> 
>>> +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;
>> 
>> I find "operation" a misleading name, you later pass strings like
>> "frame" or "cursor pixels" to it. So:
>> 
>> payload_name
>> payload_description
>> data_name
>> data_description
>> 
>> Something along those lines?
>> 
>>> +};
>>> +
>>> +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 \
>> 
>> Frediano pointed out to me you need to add the header file here too so
>> that it gets included in the tarball when you `make dist`.
> 
> I've completely missed that you put the header into the public headers,
> so disregard this...
> 
> On the public headers, some (atm. most, if not all) errors are meant to
> be private to the agent, right? So we should probably split the errors
> into a public and private header?

No. IMO, a plugin may encounter practically all of them.

> 
>>> 	$(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];
>>> +    format_message(buffer, sizeof(buffer));
>>> +    ::syslog(LOG_ERR, "%s\n", buffer);
>>> +    return *this;
>>> +}
>> 
>> This is actually pretty neat, I like it! Log and throw in one line :)
>> 
>>> +
>>> +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;
>>> +}
>> 
>> I have to say I find this a bit too involved just to handle the out of
>> memory state (which, as Christophe F pointed out, most probably means
>> we're going down anyway and don't care much if there was a write
>> error?). And I don't want to reimplement variants of this for the other
>> exception classes we are bound to introduce if we want to stick with
>> this concept...
>> 
>> Cheers,
>> Lukas
>> 
>>> +
>>> +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 = \



More information about the Spice-devel mailing list