xref: /openbmc/qemu/python/qemu/qmp/util.py (revision 7c9d65f9e48fe5ff27563d5415a3ec5aade7a12b)
1"""
2Miscellaneous Utilities
3
4This module provides asyncio and various logging and debugging
5utilities, such as `exception_summary()` and `pretty_traceback()`, used
6primarily for adding information into the logging stream.
7"""
8
9import asyncio
10import sys
11import traceback
12from typing import TypeVar, cast
13import warnings
14
15
16T = TypeVar('T')
17
18
19# --------------------------
20# Section: Utility Functions
21# --------------------------
22
23
24def get_or_create_event_loop() -> asyncio.AbstractEventLoop:
25    """
26    Return this thread's current event loop, or create a new one.
27
28    This function behaves similarly to asyncio.get_event_loop() in
29    Python<=3.13, where if there is no event loop currently associated
30    with the current context, it will create and register one. It should
31    generally not be used in any asyncio-native applications.
32    """
33    try:
34        with warnings.catch_warnings():
35            # Python <= 3.13 will trigger deprecation warnings if no
36            # event loop is set, but will create and set a new loop.
37            warnings.simplefilter("ignore")
38            loop = asyncio.get_event_loop()
39    except RuntimeError:
40        # Python 3.14+: No event loop set for this thread,
41        # create and set one.
42        loop = asyncio.new_event_loop()
43        # Set this loop as the current thread's loop, to be returned
44        # by calls to get_event_loop() in the future.
45        asyncio.set_event_loop(loop)
46
47    return loop
48
49
50async def flush(writer: asyncio.StreamWriter) -> None:
51    """
52    Utility function to ensure a StreamWriter is *fully* drained.
53
54    `asyncio.StreamWriter.drain` only promises we will return to below
55    the "high-water mark". This function ensures we flush the entire
56    buffer -- by setting the high water mark to 0 and then calling
57    drain. The flow control limits are restored after the call is
58    completed.
59    """
60    transport = cast(  # type: ignore[redundant-cast]
61        asyncio.WriteTransport, writer.transport
62    )
63
64    # https://github.com/python/typeshed/issues/5779
65    low, high = transport.get_write_buffer_limits()  # type: ignore
66    transport.set_write_buffer_limits(0, 0)
67    try:
68        await writer.drain()
69    finally:
70        transport.set_write_buffer_limits(high, low)
71
72
73def upper_half(func: T) -> T:
74    """
75    Do-nothing decorator that annotates a method as an "upper-half" method.
76
77    These methods must not call bottom-half functions directly, but can
78    schedule them to run.
79    """
80    return func
81
82
83def bottom_half(func: T) -> T:
84    """
85    Do-nothing decorator that annotates a method as a "bottom-half" method.
86
87    These methods must take great care to handle their own exceptions whenever
88    possible. If they go unhandled, they will cause termination of the loop.
89
90    These methods do not, in general, have the ability to directly
91    report information to a caller’s context and will usually be
92    collected as a Task result instead.
93
94    They must not call upper-half functions directly.
95    """
96    return func
97
98
99# ----------------------------
100# Section: Logging & Debugging
101# ----------------------------
102
103
104def exception_summary(exc: BaseException) -> str:
105    """
106    Return a summary string of an arbitrary exception.
107
108    It will be of the form "ExceptionType: Error Message", if the error
109    string is non-empty, and just "ExceptionType" otherwise.
110    """
111    name = type(exc).__qualname__
112    smod = type(exc).__module__
113    if smod not in ("__main__", "builtins"):
114        name = smod + '.' + name
115
116    error = str(exc)
117    if error:
118        return f"{name}: {error}"
119    return name
120
121
122def pretty_traceback(prefix: str = "  | ") -> str:
123    """
124    Formats the current traceback, indented to provide visual distinction.
125
126    This is useful for printing a traceback within a traceback for
127    debugging purposes when encapsulating errors to deliver them up the
128    stack; when those errors are printed, this helps provide a nice
129    visual grouping to quickly identify the parts of the error that
130    belong to the inner exception.
131
132    :param prefix: The prefix to append to each line of the traceback.
133    :return: A string, formatted something like the following::
134
135      | Traceback (most recent call last):
136      |   File "foobar.py", line 42, in arbitrary_example
137      |     foo.baz()
138      | ArbitraryError: [Errno 42] Something bad happened!
139    """
140    output = "".join(traceback.format_exception(*sys.exc_info()))
141
142    exc_lines = []
143    for line in output.split('\n'):
144        exc_lines.append(prefix + line)
145
146    # The last line is always empty, omit it
147    return "\n".join(exc_lines[:-1])
148