[master/f20-branch][PATCH] Handle packaging exits in a better way
Vratislav Podzimek
vpodzime at redhat.com
Tue Oct 15 10:56:02 UTC 2013
For various reasons we shouldn't call sys.exit() from a non-main thread. But
since we need some packaging threads to stop right in the middle and deep in the
stack, the only way that works the same as sys.exit() is raising an exception.
A special exception that is caught and "translated" into a sys.exit() call from
the main thread.
This way, packaging doesn't need to have a clue about our queues and exit
handling.
Signed-off-by: Vratislav Podzimek <vpodzime at redhat.com>
---
pyanaconda/install.py | 11 ++++++++--
pyanaconda/packaging/__init__.py | 39 ++++++++++++++++++++++++++++++++++--
pyanaconda/packaging/dnfpayload.py | 14 +++----------
pyanaconda/packaging/yumpayload.py | 18 +++++------------
pyanaconda/ui/communication.py | 1 +
pyanaconda/ui/gui/spokes/software.py | 12 +++++++++--
pyanaconda/ui/tui/__init__.py | 3 +++
pyanaconda/ui/tui/spokes/software.py | 8 +++++++-
8 files changed, 75 insertions(+), 31 deletions(-)
diff --git a/pyanaconda/install.py b/pyanaconda/install.py
index 0742d6f..8f05629 100644
--- a/pyanaconda/install.py
+++ b/pyanaconda/install.py
@@ -23,6 +23,7 @@
from pyanaconda.constants import ROOT_PATH
from blivet import turnOnFilesystems
from pyanaconda.bootloader import writeBootLoader
+from pyanaconda.packaging import CannotContinueError
from pyanaconda.progress import progress_report, progressQ
from pyanaconda.users import createLuserConf, getPassAlgo, Users
from pyanaconda import flags
@@ -158,8 +159,14 @@ def doInstall(storage, payload, ksdata, instClass):
# don't try to install packages from the install class' ignored list
packages = [p for p in packages if p not in instClass.ignoredPackages]
- payload.preInstall(packages=packages, groups=payload.languageGroups())
- payload.install()
+ try:
+ payload.preInstall(packages=packages, groups=payload.languageGroups())
+ payload.install()
+ except CannotContinueError:
+ # payload cannot continue, send quit message and return from the
+ # function so that the thread it runs in exits
+ progressQ.send_quit(1)
+ return
if flags.flags.livecdInstall:
storage.write()
diff --git a/pyanaconda/packaging/__init__.py b/pyanaconda/packaging/__init__.py
index 74adf7f..6117dba 100644
--- a/pyanaconda/packaging/__init__.py
+++ b/pyanaconda/packaging/__init__.py
@@ -70,6 +70,15 @@ urlgrabber.grabber.default_grabber.opts.user_agent = "%s (anaconda)/%s" %(produc
class PayloadError(Exception):
pass
+class CannotContinueError(PayloadError):
+ """
+ Exception that is raised when payload cannot continue and just wants to wait
+ for the process to exit.
+
+ """
+
+ pass
+
class MetadataError(PayloadError):
pass
@@ -390,6 +399,18 @@ class Payload(object):
return self._kernelVersionList
+ def checkSoftwareSelection(self):
+ """
+ Check if selected software can be installed or not.
+
+ :raise CannotContinueError: if there is some fatal error and payload
+ cannot continue (e.g. user chooses not to
+ continue with some package missing/broken)
+
+ """
+
+ pass
+
##
## METHODS FOR TREE VERIFICATION
##
@@ -535,11 +556,25 @@ class Payload(object):
### METHODS FOR INSTALLING THE PAYLOAD
###
def preInstall(self, packages=None, groups=None):
- """ Perform pre-installation tasks. """
+ """
+ Perform pre-installation tasks.
+
+ :raise CannotContinueError: if there is some fatal error and payload
+ cannot continue
+
+ """
+
iutil.mkdirChain(ROOT_PATH + "/root")
def install(self):
- """ Install the payload. """
+ """
+ Install the payload.
+
+ :raise CannotContinueError: if there is some fatal error and payload
+ cannot continue
+
+ """
+
raise NotImplementedError()
def _copyDriverDiskFiles(self):
diff --git a/pyanaconda/packaging/dnfpayload.py b/pyanaconda/packaging/dnfpayload.py
index d0bda57..f89306d 100644
--- a/pyanaconda/packaging/dnfpayload.py
+++ b/pyanaconda/packaging/dnfpayload.py
@@ -53,11 +53,6 @@ REPO_DIRS = ['/etc/yum.repos.d',
'/tmp/updates/anaconda.repos.d',
'/tmp/product/anaconda.repos.d']
-def _failure_limbo():
- progressQ.send_quit(1)
- while True:
- time.sleep(10000)
-
class PayloadRPMDisplay(dnf.output.LoggingTransactionDisplay):
def __init__(self, queue):
super(PayloadRPMDisplay, self).__init__()
@@ -177,10 +172,7 @@ class DNFPayload(packaging.PackagePayload):
if errors.errorHandler.cb(exn, str(exn)) == errors.ERROR_RAISE:
# The progress bar polls kind of slowly, thus installation could
# still continue for a bit before the quit message is processed.
- # Doing a sys.exit also ensures the running thread quits before
- # it can do anything else.
- progressQ.send_quit(1)
- sys.exit(1)
+ raise packaging.CannotContinueError()
def _select_group(self, group_id, default=True, optional=False):
grp = self._base.comps.group_by_pattern(group_id)
@@ -339,7 +331,7 @@ class DNFPayload(packaging.PackagePayload):
self.checkSoftwareSelection()
except packaging.DependencyError:
if errors.errorHandler.cb(e) == errors.ERROR_RAISE:
- _failure_limbo()
+ raise packaging.CannotContinueError()
pkgs_to_download = self._base.transaction.install_set
log.info('Downloading pacakges.')
@@ -362,7 +354,7 @@ class DNFPayload(packaging.PackagePayload):
(token, msg) = queue.get()
if token == 'quit':
- _failure_limbo()
+ raise packaging.CannotContinueError()
post_msg = _("Performing post-installation setup tasks")
progressQ.send_message(post_msg)
diff --git a/pyanaconda/packaging/yumpayload.py b/pyanaconda/packaging/yumpayload.py
index 0eb5cf1..5a13310 100644
--- a/pyanaconda/packaging/yumpayload.py
+++ b/pyanaconda/packaging/yumpayload.py
@@ -76,7 +76,7 @@ import blivet.arch
from pyanaconda.errors import ERROR_RAISE, errorHandler
from pyanaconda.packaging import DependencyError, MetadataError, NoNetworkError, NoSuchGroup, \
NoSuchPackage, PackagePayload, PayloadError, PayloadInstallError, \
- PayloadSetupError
+ PayloadSetupError, CannotContinueError
from pyanaconda.progress import progressQ
from pyanaconda.localization import langcode_matches_locale
@@ -1137,11 +1137,7 @@ reposdir=%s
if errorHandler.cb(exn, str(exn)) == ERROR_RAISE:
# The progress bar polls kind of slowly, thus installation could
# still continue for a bit before the quit message is processed.
- # Let's sleep forever to prevent any further actions and wait for
- # the main thread to quit the process.
- progressQ.send_quit(1)
- while True:
- time.sleep(100000)
+ raise CannotContinueError()
def _applyYumSelections(self):
""" Apply the selections in ksdata to yum.
@@ -1311,9 +1307,7 @@ reposdir=%s
self.checkSoftwareSelection()
except DependencyError as e:
if errorHandler.cb(e) == ERROR_RAISE:
- progressQ.send_quit(1)
- while True:
- time.sleep(100000)
+ raise CannotContinueError()
# doPreInstall
# create mountpoints for protected device mountpoints (?)
@@ -1423,8 +1417,7 @@ reposdir=%s
log.error("Error running anaconda-yum: %s", e)
exn = PayloadInstallError(str(e))
if errorHandler.cb(exn) == ERROR_RAISE:
- progressQ.send_quit(1)
- sys.exit(1)
+ raise CannotContinueError()
finally:
# log the contents of the scriptlet logfile if any
if os.path.exists(script_log):
@@ -1438,8 +1431,7 @@ reposdir=%s
if install_errors:
exn = PayloadInstallError("\n".join(install_errors))
if errorHandler.cb(exn) == ERROR_RAISE:
- progressQ.send_quit(1)
- sys.exit(1)
+ raise CannotContinueError()
def writeMultiLibConfig(self):
if not self.data.packages.multiLib:
diff --git a/pyanaconda/ui/communication.py b/pyanaconda/ui/communication.py
index 6b819c5..9474829 100644
--- a/pyanaconda/ui/communication.py
+++ b/pyanaconda/ui/communication.py
@@ -38,3 +38,4 @@ hubQ.addMessage("message", 2) # spoke_name, string
hubQ.addMessage("input", 1) # string
hubQ.addMessage("exception", 1) # exception
hubQ.addMessage("show_message", 3) # show_message_function, args, result_queue
+hubQ.addMessage("quit", 1) # exit_code
diff --git a/pyanaconda/ui/gui/spokes/software.py b/pyanaconda/ui/gui/spokes/software.py
index f4b7995..f39edd2 100644
--- a/pyanaconda/ui/gui/spokes/software.py
+++ b/pyanaconda/ui/gui/spokes/software.py
@@ -19,11 +19,11 @@
# Red Hat Author(s): Chris Lumens <clumens at redhat.com>
#
-from gi.repository import Gdk
+from gi.repository import Gdk, GLib
from pyanaconda.flags import flags
from pyanaconda.i18n import _, N_
-from pyanaconda.packaging import MetadataError
+from pyanaconda.packaging import MetadataError, CannotContinueError
from pyanaconda.threads import threadMgr, AnacondaThread
from pyanaconda import constants
@@ -37,6 +37,9 @@ import sys
__all__ = ["SoftwareSelectionSpoke"]
+import logging
+log = logging.getLogger("anaconda")
+
class SoftwareSelectionSpoke(NormalSpoke):
builderObjects = ["addonStore", "environmentStore", "softwareWindow"]
mainWidgetName = "softwareWindow"
@@ -106,6 +109,11 @@ class SoftwareSelectionSpoke(NormalSpoke):
self._errorMsgs = "\n".join(sorted(e.message))
hubQ.send_message(self.__class__.__name__, _("Error checking software dependencies"))
self._tx_id = None
+ except CannotContinueError:
+ # cannot continue (as per user request, exit the process from the
+ # main thread)
+ log.info("Payload cannot continue, exitting.")
+ GLib.idle_add(sys.exit, 1)
else:
self._errorMsgs = None
self._tx_id = self.payload.txID
diff --git a/pyanaconda/ui/tui/__init__.py b/pyanaconda/ui/tui/__init__.py
index 1b12fb9..1a91c1e 100644
--- a/pyanaconda/ui/tui/__init__.py
+++ b/pyanaconda/ui/tui/__init__.py
@@ -143,6 +143,9 @@ class TextUserInterface(ui.UserInterface):
self._app.register_event_handler(hubQ.HUB_CODE_EXCEPTION, exception_msg_handler)
self._app.register_event_handler(hubQ.HUB_CODE_SHOW_MESSAGE, self._handle_show_message)
+ # exit on exit request with exit code specified as the first item in event data (ev[1][0])
+ self._app.register_event_handler(hubQ.HUB_CODE_QUIT, lambda ev, d: sys.exit(ev[1][0]))
+
_hubs = self._list_hubs()
# First, grab a list of all the standalone spokes.
diff --git a/pyanaconda/ui/tui/spokes/software.py b/pyanaconda/ui/tui/spokes/software.py
index 9b37f55..47e5d70 100644
--- a/pyanaconda/ui/tui/spokes/software.py
+++ b/pyanaconda/ui/tui/spokes/software.py
@@ -20,10 +20,11 @@
#
from pyanaconda.flags import flags
+from pyanaconda.ui.communication import hubQ
from pyanaconda.ui.tui.spokes import NormalTUISpoke
from pyanaconda.ui.tui.simpleline import TextWidget, ColumnWidget, CheckboxWidget
from pyanaconda.threads import threadMgr, AnacondaThread
-from pyanaconda.packaging import MetadataError, DependencyError
+from pyanaconda.packaging import MetadataError, DependencyError, CannotContinueError
from pyanaconda.i18n import _
from pyanaconda.constants import THREAD_PAYLOAD, THREAD_PAYLOAD_MD
@@ -32,6 +33,8 @@ from pyanaconda.constants_text import INPUT_PROCESSED
__all__ = ["SoftwareSpoke"]
+import logging
+log = logging.getLogger("anaconda")
class SoftwareSpoke(NormalTUISpoke):
""" Spoke used to read new value of text to represent source repo. """
@@ -200,6 +203,9 @@ class SoftwareSpoke(NormalTUISpoke):
self.payload.checkSoftwareSelection()
except DependencyError:
self._tx_id = None
+ except CannotContinueError:
+ log.info("Payload cannot continue, exitting.")
+ hubQ.send_quit(1)
else:
self._tx_id = self.payload.txID
--
1.7.11.7
More information about the anaconda-patches
mailing list