qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 0/3] python/machine.py: refactor shutdown
@ 2020-06-26 20:23 John Snow
  2020-06-26 20:23 ` [PATCH v4 1/3] python/machine.py: consolidate _post_shutdown() John Snow
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: John Snow @ 2020-06-26 20:23 UTC (permalink / raw)
  To: qemu-devel
  Cc: kwolf, Eduardo Habkost, philmd, Markus Armbruster, Cleber Rosa,
	John Snow

v4:

001/3:[----] [--] 'python/machine.py: consolidate _post_shutdown()'
002/3:[0010] [FC] 'python/machine.py: refactor shutdown'
003/3:[----] [--] 'python/machine.py: re-add sigkill warning suppression'

- Rebased
- Fixed exception handler to actually handle exception :(

v3:
 - Split _post_shutdown refactor into own patch (now 1/3)
 - Re-add sigkill warning squelch (now 3/3)

Applies straight to origin/master, ought to pass pylint and flake8:

> cd ~/src/qemu/python/qemu
> pylint *.py
> flake8 *.py

John Snow (3):
  python/machine.py: consolidate _post_shutdown()
  python/machine.py: refactor shutdown
  python/machine.py: re-add sigkill warning suppression

 python/qemu/machine.py | 108 ++++++++++++++++++++++++++++++-----------
 1 file changed, 79 insertions(+), 29 deletions(-)

-- 
2.21.3



^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v4 1/3] python/machine.py: consolidate _post_shutdown()
  2020-06-26 20:23 [PATCH v4 0/3] python/machine.py: refactor shutdown John Snow
@ 2020-06-26 20:23 ` John Snow
  2020-06-26 20:23 ` [PATCH v4 2/3] python/machine.py: refactor shutdown John Snow
  2020-06-26 20:23 ` [PATCH v4 3/3] python/machine.py: re-add sigkill warning suppression John Snow
  2 siblings, 0 replies; 4+ messages in thread
From: John Snow @ 2020-06-26 20:23 UTC (permalink / raw)
  To: qemu-devel
  Cc: kwolf, Eduardo Habkost, philmd, Markus Armbruster, Cleber Rosa,
	John Snow

Move more cleanup actions into _post_shutdown. As a change, if QEMU
should so happen to be terminated during a call to wait(), that event
will now be logged.

This is not likely to occur during normative use.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 python/qemu/machine.py | 27 +++++++++++++--------------
 1 file changed, 13 insertions(+), 14 deletions(-)

diff --git a/python/qemu/machine.py b/python/qemu/machine.py
index 041c615052..f7e68191c2 100644
--- a/python/qemu/machine.py
+++ b/python/qemu/machine.py
@@ -283,6 +283,8 @@ def _post_launch(self):
             self._qmp.accept()
 
     def _post_shutdown(self):
+        self._load_io_log()
+
         if self._qemu_log_file is not None:
             self._qemu_log_file.close()
             self._qemu_log_file = None
@@ -296,6 +298,17 @@ def _post_shutdown(self):
         while len(self._remove_files) > 0:
             self._remove_if_exists(self._remove_files.pop())
 
+        exitcode = self.exitcode()
+        if exitcode is not None and exitcode < 0:
+            msg = 'qemu received signal %i; command: "%s"'
+            if self._qemu_full_args:
+                command = ' '.join(self._qemu_full_args)
+            else:
+                command = ''
+            LOG.warning(msg, -int(exitcode), command)
+
+        self._launched = False
+
     def launch(self):
         """
         Launch the VM and make sure we cleanup and expose the
@@ -344,7 +357,6 @@ def wait(self):
         self._popen.wait()
         if self._qmp:
             self._qmp.close()
-        self._load_io_log()
         self._post_shutdown()
 
     def shutdown(self, has_quit=False, hard=False):
@@ -371,21 +383,8 @@ def shutdown(self, has_quit=False, hard=False):
                     self._popen.kill()
             self._popen.wait()
 
-        self._load_io_log()
         self._post_shutdown()
 
-        exitcode = self.exitcode()
-        if exitcode is not None and exitcode < 0 and \
-                not (exitcode == -9 and hard):
-            msg = 'qemu received signal %i: %s'
-            if self._qemu_full_args:
-                command = ' '.join(self._qemu_full_args)
-            else:
-                command = ''
-            LOG.warning(msg, -int(exitcode), command)
-
-        self._launched = False
-
     def kill(self):
         self.shutdown(hard=True)
 
-- 
2.21.3



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH v4 2/3] python/machine.py: refactor shutdown
  2020-06-26 20:23 [PATCH v4 0/3] python/machine.py: refactor shutdown John Snow
  2020-06-26 20:23 ` [PATCH v4 1/3] python/machine.py: consolidate _post_shutdown() John Snow
@ 2020-06-26 20:23 ` John Snow
  2020-06-26 20:23 ` [PATCH v4 3/3] python/machine.py: re-add sigkill warning suppression John Snow
  2 siblings, 0 replies; 4+ messages in thread
From: John Snow @ 2020-06-26 20:23 UTC (permalink / raw)
  To: qemu-devel
  Cc: kwolf, Eduardo Habkost, philmd, Markus Armbruster, Cleber Rosa,
	John Snow

This is done primarily to avoid the 'bare except' pattern, which
suppresses ALL exceptions and not just ones that we are anticipating to
see.

Replace this with a pattern that isolates the different kind of shutdown
paradigms and a new fallback shutdown handler that gracefully attempts
one before the other.

Ensure that the main shutdown() function ALWAYS calls the post_shutdown
logic, no matter what kind of error we encountered. Subprocess wait
timeouts are considered expected, everything else is unexpected.

In cases where we encounter an expected error in the graceful shutdown
timeout, we will not re-raise an exception above shutdown(). Otherwise,
after post_shutdown cleanup, we will.

I anticipate that this WILL lead to additional bug reports filed against
this module, but that is unfortunately somewhat the point: This code
shouldn't be hiding failures that exist elsewhere within the python
code.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 python/qemu/machine.py | 76 +++++++++++++++++++++++++++++++++---------
 1 file changed, 61 insertions(+), 15 deletions(-)

diff --git a/python/qemu/machine.py b/python/qemu/machine.py
index f7e68191c2..66a9d4204c 100644
--- a/python/qemu/machine.py
+++ b/python/qemu/machine.py
@@ -359,9 +359,59 @@ def wait(self):
             self._qmp.close()
         self._post_shutdown()
 
-    def shutdown(self, has_quit=False, hard=False):
+    def _hard_shutdown(self) -> None:
         """
-        Terminate the VM and clean up
+        Kill the VM if it is running.
+        """
+        if not self.is_running():
+            return
+
+        self._popen.kill()
+        self._popen.wait(timeout=60)
+
+    def _soft_shutdown(self, has_quit: bool = False, timeout: int = 3) -> None:
+        """
+        Attempt to shutdown the VM gracefully if it is running.
+
+        :param has_quit: When True, don't attempt to issue 'quit' QMP command
+        :param timeout: Timeout for graceful shutdown. Default 3 seconds.
+        """
+        if not self.is_running():
+            return
+
+        if self._qmp is not None:
+            if not has_quit:
+                try:
+                    self._qmp.cmd('quit')
+                except ConnectionResetError:
+                    # QMP went away just before or just after sending 'quit'.
+                    # Covers EPIPE, ECONNABORTED, ECONNREFUSED, and ECONNRESET.
+                    if self.is_running():
+                        # Process is running, but the control channel is lost.
+                        # No remaining way to shut it down 'gracefully'.
+                        raise
+            self._qmp.close()
+
+        self._popen.wait(timeout=timeout)
+
+    def _do_shutdown(self, has_quit: bool = False, timeout: int = 3) -> None:
+        """
+        Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
+
+        :param has_quit: When True, don't attempt to issue 'quit' QMP command
+        :param timeout: Timeout for graceful shutdown. Default 3 seconds.
+        """
+        try:
+            self._soft_shutdown(has_quit, timeout)
+        except subprocess.TimeoutExpired:
+            self._hard_shutdown()
+        except:
+            self._hard_shutdown()
+            raise
+
+    def shutdown(self, has_quit: bool = False, hard: bool = False) -> None:
+        """
+        Terminate the VM (gracefully if possible) and perform cleanup.
         """
         # If we keep the console socket open, we may deadlock waiting
         # for QEMU to exit, while QEMU is waiting for the socket to
@@ -370,22 +420,18 @@ def shutdown(self, has_quit=False, hard=False):
             self._console_socket.close()
             self._console_socket = None
 
-        if self.is_running():
+        try:
             if hard:
-                self._popen.kill()
-            elif self._qmp:
-                try:
-                    if not has_quit:
-                        self._qmp.cmd('quit')
-                    self._qmp.close()
-                    self._popen.wait(timeout=3)
-                except:
-                    self._popen.kill()
-            self._popen.wait()
-
-        self._post_shutdown()
+                self._hard_shutdown()
+            else:
+                self._do_shutdown(has_quit)
+        finally:
+            self._post_shutdown()
 
     def kill(self):
+        """
+        Terminate the VM forcefully and perform cleanup.
+        """
         self.shutdown(hard=True)
 
     def set_qmp_monitor(self, enabled=True):
-- 
2.21.3



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH v4 3/3] python/machine.py: re-add sigkill warning suppression
  2020-06-26 20:23 [PATCH v4 0/3] python/machine.py: refactor shutdown John Snow
  2020-06-26 20:23 ` [PATCH v4 1/3] python/machine.py: consolidate _post_shutdown() John Snow
  2020-06-26 20:23 ` [PATCH v4 2/3] python/machine.py: refactor shutdown John Snow
@ 2020-06-26 20:23 ` John Snow
  2 siblings, 0 replies; 4+ messages in thread
From: John Snow @ 2020-06-26 20:23 UTC (permalink / raw)
  To: qemu-devel
  Cc: kwolf, Eduardo Habkost, philmd, Markus Armbruster, Cleber Rosa,
	John Snow

If the user kills QEMU on purpose, we don't need to warn them about that
having happened: they know already.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 python/qemu/machine.py | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/python/qemu/machine.py b/python/qemu/machine.py
index 66a9d4204c..8c9050af5d 100644
--- a/python/qemu/machine.py
+++ b/python/qemu/machine.py
@@ -22,6 +22,7 @@
 import os
 import subprocess
 import shutil
+import signal
 import socket
 import tempfile
 from typing import Optional, Type
@@ -122,6 +123,7 @@ def __init__(self, binary, args=None, wrapper=None, name=None,
         self._console_address = None
         self._console_socket = None
         self._remove_files = []
+        self._killed = False
 
     def __enter__(self):
         return self
@@ -282,7 +284,7 @@ def _post_launch(self):
         if self._qmp:
             self._qmp.accept()
 
-    def _post_shutdown(self):
+    def _post_shutdown(self) -> None:
         self._load_io_log()
 
         if self._qemu_log_file is not None:
@@ -299,7 +301,8 @@ def _post_shutdown(self):
             self._remove_if_exists(self._remove_files.pop())
 
         exitcode = self.exitcode()
-        if exitcode is not None and exitcode < 0:
+        if (exitcode is not None and exitcode < 0
+                and not (self._killed and exitcode == -signal.SIGKILL)):
             msg = 'qemu received signal %i; command: "%s"'
             if self._qemu_full_args:
                 command = ' '.join(self._qemu_full_args)
@@ -307,6 +310,7 @@ def _post_shutdown(self):
                 command = ''
             LOG.warning(msg, -int(exitcode), command)
 
+        self._killed = False
         self._launched = False
 
     def launch(self):
@@ -422,6 +426,7 @@ def shutdown(self, has_quit: bool = False, hard: bool = False) -> None:
 
         try:
             if hard:
+                self._killed = True
                 self._hard_shutdown()
             else:
                 self._do_shutdown(has_quit)
-- 
2.21.3



^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2020-06-26 20:25 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-06-26 20:23 [PATCH v4 0/3] python/machine.py: refactor shutdown John Snow
2020-06-26 20:23 ` [PATCH v4 1/3] python/machine.py: consolidate _post_shutdown() John Snow
2020-06-26 20:23 ` [PATCH v4 2/3] python/machine.py: refactor shutdown John Snow
2020-06-26 20:23 ` [PATCH v4 3/3] python/machine.py: re-add sigkill warning suppression John Snow

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).