[Spice-devel] [PATCH 18/22] Convert existing std::runtime_error to the new Error classes

Lukáš Hrázký lhrazky at redhat.com
Thu Mar 1 14:42:13 UTC 2018


On Wed, 2018-02-28 at 16:43 +0100, Christophe de Dinechin wrote:
> From: Christophe de Dinechin <dinechin at redhat.com>
> 
> Appropriate error classes been created for the existing messages:
> - ProtocolError: handles the various kinds of protocol-related errors
> - MessageDataError: a form of protocol error where we want to report
>   what was read and what was expected (e.g. message length error,
>   version error, etc)
> 
> Signed-off-by: Christophe de Dinechin <dinechin at redhat.com>
> ---
>  include/spice-streaming-agent/errors.hpp | 28 ++++++++++++++++++++++++++++
>  src/errors.cpp                           | 12 ++++++++++++
>  src/mjpeg-fallback.cpp                   | 10 ++++++----
>  src/spice-streaming-agent.cpp            | 25 ++++++++++---------------
>  src/unittests/test-mjpeg-fallback.cpp    |  4 ++--
>  5 files changed, 58 insertions(+), 21 deletions(-)
> 
> diff --git a/include/spice-streaming-agent/errors.hpp b/include/spice-streaming-agent/errors.hpp
> index 72e9910..870a0fd 100644
> --- a/include/spice-streaming-agent/errors.hpp
> +++ b/include/spice-streaming-agent/errors.hpp
> @@ -60,6 +60,34 @@ public:
>      ReadError(const char *msg, int saved_errno): IOError(msg, saved_errno) {}
>  };
>  
> +class ProtocolError : public ReadError
> +{
> +public:
> +    ProtocolError(const char *msg, int saved_errno = 0): ReadError(msg, saved_errno) {}
> +};
> +
> +class MessageDataError : public ProtocolError
> +{
> +public:
> +    MessageDataError(const char *msg, size_t received, size_t expected, int saved_errno = 0)
> +        : ProtocolError(msg, saved_errno), received(received), expected(expected) {}
> +    int format_message(char *buffer, size_t size) const noexcept override;
> +protected:
> +    size_t received;
> +    size_t expected;
> +};
> +
> +class OptionError : public Error
> +{
> +public:
> +    OptionError(const char *msg, const char *option, const char *value)
> +        : Error(msg), option(option), value(value) {}
> +    int format_message(char *buffer, size_t size) const noexcept override;
> +protected:
> +    const char *option;
> +    const char *value;
> +};
> +
>  }} // namespace spice::streaming_agent
>  
>  #endif // SPICE_STREAMING_AGENT_ERRORS_HPP
> diff --git a/src/errors.cpp b/src/errors.cpp
> index ff25142..4dc45e4 100644
> --- a/src/errors.cpp
> +++ b/src/errors.cpp
> @@ -59,4 +59,16 @@ int WriteError::format_message(char *buffer, size_t size) const noexcept
>      return append_strerror(buffer, size, written);
>  }
>  
> +int MessageDataError::format_message(char *buffer, size_t size) const noexcept
> +{
> +    int written = snprintf(buffer, size, "%s (received %zu, expected %zu)",
> +                           what(), received, expected);
> +    return append_strerror(buffer, size, written);
> +}
> +
> +int OptionError::format_message(char *buffer, size_t size) const noexcept
> +{
> +    return snprintf(buffer, size, "%s ('%s' is not a valid %s)", what(), value, option);
> +}
> +
>  }} // namespace spice::streaming_agent
> diff --git a/src/mjpeg-fallback.cpp b/src/mjpeg-fallback.cpp
> index 5758893..1a98535 100644
> --- a/src/mjpeg-fallback.cpp
> +++ b/src/mjpeg-fallback.cpp
> @@ -7,6 +7,8 @@
>  #include <config.h>
>  #include "mjpeg-fallback.hpp"
>  
> +#include <spice-streaming-agent/errors.hpp>
> +
>  #include <cstring>
>  #include <exception>
>  #include <stdexcept>
> @@ -59,7 +61,7 @@ MjpegFrameCapture::MjpegFrameCapture(const MjpegSettings& settings):
>  {
>      dpy = XOpenDisplay(NULL);
>      if (!dpy)
> -        throw std::runtime_error("Unable to initialize X11");
> +        throw Error("unable to initialize X11");
>  }
>  
>  MjpegFrameCapture::~MjpegFrameCapture()
> @@ -157,16 +159,16 @@ void MjpegPlugin::ParseOptions(const ConfigureOption *options)
>              try {
>                  settings.fps = stoi(value);
>              } catch (const std::exception &e) {
> -                throw std::runtime_error("Invalid value '" + value + "' for option 'framerate'.");
> +                throw OptionError("invalid mjpeg framerate", options->value, "mjpeg framerate");

You should put the exact option name here, which is "mjpeg.framerate".

Also, the error message that comes out of this is not that great:

"invalid mjpeg framerate ('XXX' is not a valid mjpeg framerate)"

It should state "'XXX' is not a valid value for plugin option
'mjpeg.framerate'" to be clear.

>              }
>          } else if (name == "mjpeg.quality") {
>              try {
>                  settings.quality = stoi(value);
>              } catch (const std::exception &e) {
> -                throw std::runtime_error("Invalid value '" + value + "' for option 'mjpeg.quality'.");
> +                throw OptionError("invalid mjpeg quality", options->value, "mjpeg quality");

Ditto, "mjpeg.quality".

>              }
>          } else {
> -            throw std::runtime_error("Invalid option '" + name + "'.");
> +            throw OptionError("invalid option name", options->name, "mjpeg option name");

No intention to be stingy, but this should probably be a different
error class :) (i.e. there are two distinct errors,
InvalidOptionValueError and UnknownOptionError) You've managed to make
the message formatting somewhat work, though... :)

>          }
>      }
>  }
> diff --git a/src/spice-streaming-agent.cpp b/src/spice-streaming-agent.cpp
> index 9048935..35e65bb 100644
> --- a/src/spice-streaming-agent.cpp
> +++ b/src/spice-streaming-agent.cpp
> @@ -67,7 +67,7 @@ public:
>      {
>          streamfd = open(name, O_RDWR);
>          if (streamfd < 0) {
> -            throw std::runtime_error("failed to open streaming device");
> +            throw IOError("failed to open streaming device", errno);
>          }
>      }
>      ~Stream()
> @@ -352,13 +352,11 @@ void Stream::handle_stream_start_stop(uint32_t len)
>      uint8_t msg[256];
>  
>      if (len >= sizeof(msg)) {
> -        throw std::runtime_error("msg size (" + std::to_string(len) + ") is too long "
> -                                 "(longer than " + std::to_string(sizeof(msg)) + ")");
> +        throw MessageDataError("message is too long", len, sizeof(msg));
>      }
>      int n = read(streamfd, &msg, len);
>      if (n != (int) len) {
> -        throw std::runtime_error("read command from device FAILED -- read " + std::to_string(n) +
> -                                 " expected " + std::to_string(len));
> +        throw MessageDataError("read start/stop command from device failed", n, len, errno);
>      }
>      is_streaming = (msg[0] != 0); /* num_codecs */
>      syslog(LOG_INFO, "GOT START_STOP message -- request to %s streaming\n",
> @@ -374,12 +372,11 @@ void Stream::handle_stream_capabilities(uint32_t len)
>      uint8_t caps[STREAM_MSG_CAPABILITIES_MAX_BYTES];
>  
>      if (len > sizeof(caps)) {
> -        throw std::runtime_error("capability message too long");
> +        throw MessageDataError("capability message too long", len, sizeof(caps));
>      }
>      int n = read(streamfd, caps, len);
>      if (n != (int) len) {
> -        throw std::runtime_error("read command from device FAILED -- read " + std::to_string(n) +
> -                                 " expected " + std::to_string(len));
> +        throw MessageDataError("read capabilities from device failed", n, len, errno);
>      }
>  
>      // we currently do not support extensions so just reply so
> @@ -389,7 +386,7 @@ void Stream::handle_stream_capabilities(uint32_t len)
>  void Stream::handle_stream_error(uint32_t len)
>  {
>      // TODO read message and use it
> -    throw std::runtime_error("got an error message from server");
> +    throw ProtocolError("got an error message from server");
>  }
>  
>  void Stream::read_command_from_device()
> @@ -400,12 +397,10 @@ void Stream::read_command_from_device()
>      std::lock_guard<std::mutex> stream_guard(mutex);
>      n = read(streamfd, &hdr, sizeof(hdr));
>      if (n != sizeof(hdr)) {
> -        throw std::runtime_error("read command from device FAILED -- read " + std::to_string(n) +
> -                                 " expected " + std::to_string(sizeof(hdr)));
> +        throw MessageDataError("read command from device failed", n, sizeof(hdr), errno);
>      }
>      if (hdr.protocol_version != STREAM_DEVICE_PROTOCOL) {
> -        throw std::runtime_error("BAD VERSION " + std::to_string(hdr.protocol_version) +
> -                                 " (expected is " + std::to_string(STREAM_DEVICE_PROTOCOL) + ")");
> +        throw MessageDataError("bad protocol version", hdr.protocol_version, STREAM_DEVICE_PROTOCOL);

I don't think this is a ReadError (from which MessageDataError
inherits), also the constructor arguments have a completely different
meaning. It just matches the "expected: X received: Y" template by
accident.

>      }
>  
>      switch (hdr.type) {
> @@ -416,7 +411,7 @@ void Stream::read_command_from_device()
>      case STREAM_TYPE_START_STOP:
>          return handle_stream_start_stop(hdr.size);
>      }
> -    throw std::runtime_error("UNKNOWN msg of type " + std::to_string(hdr.type));
> +    throw MessageDataError("unknown message type", hdr.type, 0);

Same as above.

>  }
>  
>  int Stream::read_command(bool blocking)
> @@ -507,7 +502,7 @@ void ConcreteAgent::CaptureLoop(Stream &stream, FrameLog &frame_log)
>  
>          std::unique_ptr<FrameCapture> capture(GetBestFrameCapture(stream.client_codecs()));
>          if (!capture) {
> -            throw std::runtime_error("cannot find a suitable capture system");
> +            throw Error("cannot find a suitable capture system");

>From the point of view of the argument "throwing specific error classes
for specific errors", this is basically equivalent to throwing a
runtime_error.

>          }
>  
>          while (!quit_requested && stream.streaming_requested()) {
> diff --git a/src/unittests/test-mjpeg-fallback.cpp b/src/unittests/test-mjpeg-fallback.cpp
> index 4a152fe..704decd 100644
> --- a/src/unittests/test-mjpeg-fallback.cpp
> +++ b/src/unittests/test-mjpeg-fallback.cpp
> @@ -35,7 +35,7 @@ SCENARIO("test parsing mjpeg plugin options", "[mjpeg][options]") {
>              THEN("ParseOptions throws an exception") {
>                  REQUIRE_THROWS_WITH(
>                      plugin.ParseOptions(options.data()),
> -                    "Invalid option 'wakaka'."
> +                    "invalid option name"

This does not seem to match the OptionError::format_message above, are
you sure this is correct?

If it would be correct, it would be clearly a step back, as the message
doesn't contain the faulty value.

>                  );
>              }
>          }
> @@ -50,7 +50,7 @@ SCENARIO("test parsing mjpeg plugin options", "[mjpeg][options]") {
>              THEN("ParseOptions throws an exception") {
>                  REQUIRE_THROWS_WITH(
>                      plugin.ParseOptions(options.data()),
> -                    "Invalid value 'toot' for option 'mjpeg.quality'."
> +                    "invalid mjpeg quality"

Same here.

Lukas

>                  );
>              }
>          }


More information about the Spice-devel mailing list