Looping qemu-devel back in: I removed them by accident by not hitting reply-all :(
On Wed, Jul 21, 2021 at 2:06 PM Niteesh G. S. <niteesh...@gmail.com> wrote: > > > On Wed, Jul 21, 2021 at 11:03 PM John Snow <js...@redhat.com> wrote: > >> >> >> On Wed, Jul 21, 2021 at 1:04 PM Niteesh G. S. <niteesh...@gmail.com> >> wrote: >> >>> Hello all, >>> >>> I recently rebased(incrementally) my TUI on this V2 patch and faced an >>> issue. >>> https://gitlab.com/niteesh.gs/qemu/-/commits/aqmp-tui-prototype-v3 >>> I decided to rebase incrementally so that I can address some of the >>> comments posted >>> in my patch series. While testing out, the initial draft of TUI >>> which worked fine in the V1 >>> version of AQMP failed in this version. >>> >>> Disconnecting from a fully connected state doesn't exit cleanly. >>> >>> --------------------------------------------------------------------------------- >>> To reproduce the issue: >>> 1) Initiate a QMP server >>> >> >> Please provide the command line. >> > qemu-system-x86_64 -qmp tcp:localhost:1234,server,wait=on > >> >> >>> 2) Connect the TUI to the server using aqmp-tui localhost:1234 >>> --log-file log.txt >>> >> >> The entry point isn't defined yet in your series, so I will assume >> "python3 -m qemu.aqmp.aqmp_tui localhost:1234" should work here. >> > Yup, sorry about that. I realized this later when recreated the venv. > >> >> >>> 3) Once the TUI is connected and running, press 'Esc' to exit the app. >>> This should result >>> in the following exception. >>> >>> -------------------------------------------------------------------------------------------------------------------------------------------- >>> Transitioning from 'Runstate.IDLE' to 'Runstate.CONNECTING'. >>> Connecting to ('localhost', 1234) ... >>> Connected. >>> Awaiting greeting ... >>> Response: { >>> "QMP": { >>> .......... Skipping >>> } >>> } >>> Negotiating capabilities ... >>> Request: { >>> "execute": "qmp_capabilities", >>> .......... Skipping >>> } >>> } >>> Response: { >>> "return": {} >>> } >>> Transitioning from 'Runstate.CONNECTING' to 'Runstate.RUNNING'. >>> Transitioning from 'Runstate.RUNNING' to 'Runstate.DISCONNECTING'. >>> Scheduling disconnect. >>> Draining the outbound queue ... >>> Flushing the StreamWriter ... >>> Cancelling writer task ... >>> Task.Writer: cancelled. >>> Task.Writer: exiting. >>> Cancelling reader task ... >>> Task.Reader: cancelled. >>> Task.Reader: exiting. >>> Closing StreamWriter. >>> Waiting for StreamWriter to close ... >>> QMP Disconnected. >>> Transitioning from 'Runstate.DISCONNECTING' to 'Runstate.IDLE'. >>> _kill_app: Connection lost >>> Connection lost >>> | Traceback (most recent call last): >>> | File >>> "/home/niteesh/development/qemu/python/qemu/aqmp/aqmp_tui.py", line 246, in >>> run >>> | main_loop.run() >>> | File >>> "/home/niteesh/development/qemu/python/.venv/lib/python3.6/site-packages/urwid/main_loop.py", >>> line 287, in run >>> | self._run() >>> | File >>> "/home/niteesh/development/qemu/python/.venv/lib/python3.6/site-packages/urwid/main_loop.py", >>> line 385, in _run >>> | self.event_loop.run() >>> | File >>> "/home/niteesh/development/qemu/python/.venv/lib/python3.6/site-packages/urwid/main_loop.py", >>> line 1494, in run >>> | reraise(*exc_info) >>> | File >>> "/home/niteesh/development/qemu/python/.venv/lib/python3.6/site-packages/urwid/compat.py", >>> line 58, in reraise >>> | raise value >>> | File >>> "/home/niteesh/development/qemu/python/qemu/aqmp/aqmp_tui.py", line 206, in >>> _kill_app >>> | raise err >>> | File >>> "/home/niteesh/development/qemu/python/qemu/aqmp/aqmp_tui.py", line 201, in >>> _kill_app >>> | await self.disconnect() >>> | File >>> "/home/niteesh/development/qemu/python/qemu/aqmp/protocol.py", line 303, in >>> disconnect >>> | await self._wait_disconnect() >>> | File >>> "/home/niteesh/development/qemu/python/qemu/aqmp/protocol.py", line 573, in >>> _wait_disconnect >>> | await self._dc_task >>> | File >>> "/home/niteesh/development/qemu/python/qemu/aqmp/qmp_client.py", line 316, >>> in _bh_disconnect >>> | await super()._bh_disconnect() >>> | File >>> "/home/niteesh/development/qemu/python/qemu/aqmp/protocol.py", line 644, in >>> _bh_disconnect >>> | await wait_closed(self._writer) >>> | File "/home/niteesh/development/qemu/python/qemu/aqmp/util.py", >>> line 137, in wait_closed >>> | await flush(writer) >>> | File "/home/niteesh/development/qemu/python/qemu/aqmp/util.py", >>> line 49, in flush >>> | await writer.drain() >>> | File "/usr/lib/python3.6/asyncio/streams.py", line 339, in drain >>> | yield from self._protocol._drain_helper() >>> | File "/usr/lib/python3.6/asyncio/streams.py", line 210, in >>> _drain_helper >>> | raise ConnectionResetError('Connection lost') >>> | ConnectionResetError: Connection lost >>> >>> -------------------------------------------------------------------------------------------------------------------------------------------- >>> >>> >> I can't reproduce in Python 3.9, but I *can* reproduce in python 3.6 >> using the pipenv environment; i.e. >> >> > make check-pipenv >> > pipenv shell >> > python3 -m qemu.aqmp.aqmp_tui 127.0.0.1:1234 >> >> What python version are you using to see this failure? Is it 3.6 ? >> > Yes, I was using python 3.6. I just tried it on 3.8 and I don't face this > issue. > >> >> It seems like the wait_closed() wrapper I wrote isn't quite compatible >> with Python 3.6, it looks like it's not really safe to try and flush a >> closing socket. I was doing so in an attempt to tell when the socket had >> finished closing out its buffer (expecting it to normally be a no-op) but >> in this case even a no-op drain in 3.6 seems to raise an error if we >> attempt it after we've asked for the socket to close. >> > > >> wait_closed() was added in Python 3.7 and we just don't have access to it >> here ... I'm not sure if there's something else we can do here to serve as >> a workaround for not having this function. >> >> --js >> >> I can't find a *nice* workaround, but I found one that should probably work in most universes. We can remove this ugly code when we support 3.7 as a minimum. However, please try this patch as a fixup: diff --git a/python/qemu/aqmp/util.py b/python/qemu/aqmp/util.py index de0df44cbd7..eaa5fc7d5f9 100644 --- a/python/qemu/aqmp/util.py +++ b/python/qemu/aqmp/util.py @@ -134,7 +134,17 @@ async def wait_closed(writer: asyncio.StreamWriter) -> None: while not transport.is_closing(): await asyncio.sleep(0) - await flush(writer) + + # This is an ugly workaround, but it's the best I can come up with. + sock = transport.get_extra_info('socket') + + if sock is None: + # Our transport doesn't have a socket? ... + # Nothing we can reasonably do. + return + + while sock.fileno() != -1: + await asyncio.sleep(0)