All of lore.kernel.org
 help / color / mirror / Atom feed
From: John Snow <jsnow@redhat.com>
To: qemu-devel@nongnu.org
Cc: crosa@redhat.com, John Snow <jsnow@redhat.com>,
	ehabkost@redhat.com, stefanha@redhat.com, armbru@redhat.com
Subject: [PATCH RFC 2/7] error: Error classes and so on.
Date: Tue, 13 Apr 2021 11:55:48 -0400	[thread overview]
Message-ID: <20210413155553.2660523-3-jsnow@redhat.com> (raw)
In-Reply-To: <20210413155553.2660523-1-jsnow@redhat.com>

May be somewhat hard to make sense of until you see how these classes
are used later on. Notably, although I have split QMP's functionality
into a "protocol" class and a "QMP" class, representing a separation of
the loop mechanisms and the QMP protocol itself, this file was written
prior to that split and contains both "generic" and "QMP-specific" error
classes.

It will have to be split out later, but for the purposes of an RFC where
I wanted a quick eyeball on design, I thought it wasn't necessary to
clean that up just yet.

The MultiException class might warrant a closer inspection, it's the
"weirdest" thing here. It's intended to be used internally by the
module, but as with all best laid plans, there is always the ability it
will somehow leak out into the caller's space through some unforseen
mechanism.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 error.py | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 163 insertions(+)
 create mode 100644 error.py

diff --git a/error.py b/error.py
new file mode 100644
index 0000000..f19f8e0
--- /dev/null
+++ b/error.py
@@ -0,0 +1,163 @@
+"""Generic error classes.
+
+This module seeks to provide semantic error classes that are intended to
+be used directly by clients when they would like to handle particular
+semantic failures (e.g. "failed to connect") without needing to know the
+enumeration of possible reasons for that failure.
+
+AQMPError serves as the ancestor for almost all exceptions raised by
+this package, and is suitable for use in handling semantic errors from
+this library. In most cases, individual methods will attempt to catch
+and re-encapsulate various exceptions to provide a semantic
+error-handling interface, though this is not necessarily true of
+internal interfaces.
+
+Some errors are not defined here in this module, but exist alongside
+more specific error domains in other modules. They are listed here for
+convenience anyway.
+
+The error inheritance tree is as follows::
+
+  MultiException
+  AQMPError
+    ProtocolError
+      RawProtocolError
+        DeserializationError
+        UnexpectedTypeError
+      GreetingError
+      NegotiationError
+      MsgProtocolError   (message.py)
+        ObjectTypeError  (message.py)
+        OrphanedError    (message.py)
+        ServerParseError (message.py)
+    ConnectError
+    DisconnectedError
+    StateError
+
+The only exception that is not an `AQMPError` is `MultiException`. It is
+special, and used to encapsulate one-or-more exceptions of an arbitrary
+kind; this exception MAY be raised on disconnect() when there are two or
+more exceptions from the AQMP event loop to report back to the caller.
+
+(The bottom half is designed in such a way that exceptions are attempted
+to be handled internally, but in cases of catastrophic failure, it may
+still occur.)
+
+See `MultiException` and `AsyncProtocol.disconnect()` for more details.
+
+"""
+
+from typing import Iterable, Iterator
+
+
+class AQMPError(Exception):
+    # Don't use this directly: create a subclass.
+    """Base failure for all errors raised by AQMP."""
+
+
+class ProtocolError(AQMPError):
+    """Abstract error class for protocol failures."""
+    def __init__(self, error_message: str):
+        super().__init__()
+        self.error_message = error_message
+
+    def __str__(self) -> str:
+        return f"QMP protocol error: {self.error_message}"
+
+
+class RawProtocolError(ProtocolError):
+    """
+    Abstract error class for low-level parsing failures.
+    """
+    def __init__(self, error_message: str, raw: bytes):
+        super().__init__(error_message)
+        self.raw = raw
+
+    def __str__(self) -> str:
+        return "\n".join([
+            super().__str__(),
+            f"  raw bytes were: {str(self.raw)}",
+        ])
+
+
+class DeserializationError(RawProtocolError):
+    """Incoming message was not understood as JSON."""
+
+
+class UnexpectedTypeError(RawProtocolError):
+    """Incoming message was JSON, but not a JSON object."""
+
+
+class ConnectError(AQMPError):
+    """
+    Initial connection process failed.
+    Always wraps a "root cause" exception that can be interrogated for info.
+    """
+
+
+class GreetingError(ProtocolError):
+    """An exception occurred during the Greeting phase."""
+    def __init__(self, error_message: str, exc: Exception):
+        super().__init__(error_message)
+        self.exc = exc
+
+    def __str__(self) -> str:
+        return (
+            f"QMP protocol error: {self.error_message}\n"
+            f"  Cause: {self.exc!s}\n"
+        )
+
+
+class NegotiationError(ProtocolError):
+    """An exception occurred during the Negotiation phase."""
+    def __init__(self, error_message: str, exc: Exception):
+        super().__init__(error_message)
+        self.exc = exc
+
+    def __str__(self) -> str:
+        return (
+            f"QMP protocol error: {self.error_message}\n"
+            f"  Cause: {self.exc!s}\n"
+        )
+
+
+class DisconnectedError(AQMPError):
+    """
+    Command was not able to be completed; we have been Disconnected.
+
+    This error is raised in response to a pending execution when the
+    back-end is unable to process responses any more.
+    """
+
+
+class StateError(AQMPError):
+    """
+    An API command (connect, execute, etc) was issued at an inappropriate time.
+
+    (e.g. execute() while disconnected; connect() while connected; etc.)
+    """
+
+
+class MultiException(Exception):
+    """
+    Used for multiplexing exceptions.
+
+    This exception is used in the case that errors were encountered in both the
+    Reader and Writer tasks, and we must raise more than one.
+    """
+    def __init__(self, exceptions: Iterable[BaseException]):
+        super().__init__(exceptions)
+        self.exceptions = list(exceptions)
+
+    def __str__(self) -> str:
+        ret = "------------------------------\n"
+        ret += "Multiple Exceptions occurred:\n"
+        ret += "\n"
+        for i, exc in enumerate(self.exceptions):
+            ret += f"{i}) {str(exc)}\n"
+            ret += "\n"
+        ret += "-----------------------------\n"
+        return ret
+
+    def __iter__(self) -> Iterator[BaseException]:
+        return iter(self.exceptions)
-- 
2.30.2



  parent reply	other threads:[~2021-04-13 15:59 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-04-13 15:55 [PATCH RFC 0/7] RFC: Asynchronous QMP Draft John Snow
2021-04-13 15:55 ` [PATCH RFC 1/7] util: asyncio-related helpers John Snow
2021-04-13 15:55 ` John Snow [this message]
2021-04-13 15:55 ` [PATCH RFC 3/7] protocol: generic async message-based protocol loop John Snow
2021-04-13 20:00   ` Stefan Hajnoczi
2021-04-14 17:29     ` John Snow
2021-04-15  9:14       ` Stefan Hajnoczi
2021-04-13 15:55 ` [PATCH RFC 4/7] message: add QMP Message type John Snow
2021-04-13 20:07   ` Stefan Hajnoczi
2021-04-14 17:39     ` John Snow
2021-04-13 15:55 ` [PATCH RFC 5/7] models: Add well-known QMP objects John Snow
2021-04-13 15:55 ` [PATCH RFC 6/7] qmp_protocol: add QMP client implementation John Snow
2021-04-14  5:44   ` Stefan Hajnoczi
2021-04-14 17:50     ` John Snow
2021-04-15  9:23       ` Stefan Hajnoczi
2021-04-13 15:55 ` [PATCH RFC 7/7] linter config John Snow
2021-04-14  6:38 ` [PATCH RFC 0/7] RFC: Asynchronous QMP Draft Stefan Hajnoczi
2021-04-14 19:17   ` John Snow
2021-04-15  9:52     ` Stefan Hajnoczi
2021-04-20  2:26       ` John Snow
2021-04-20  2:47         ` John Snow

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210413155553.2660523-3-jsnow@redhat.com \
    --to=jsnow@redhat.com \
    --cc=armbru@redhat.com \
    --cc=crosa@redhat.com \
    --cc=ehabkost@redhat.com \
    --cc=qemu-devel@nongnu.org \
    --cc=stefanha@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.