PackageUninstall: make async with MergeProcess
authorZac Medico <zmedico@gentoo.org>
Tue, 24 May 2011 05:31:20 +0000 (22:31 -0700)
committerZac Medico <zmedico@gentoo.org>
Tue, 24 May 2011 05:31:20 +0000 (22:31 -0700)
This fixes another ebuild-locks issue like the one fixed in commit
a81460175a441897282b0540cefff8060f2b92dc, but this time we use a
subprocess to ensure that the ebuild-locks for pkg_prerm and
pkg_postrm do not interfere with pkg_setup ebuild-locks held by
the main process.

pym/_emerge/PackageUninstall.py
pym/portage/dbapi/_MergeProcess.py
pym/portage/dbapi/vartree.py

index 0e913071398e155136fdb978dfaf11ed02d9ce06..0829f50b7f70e5fb20da2be9530b47828f8048e3 100644 (file)
@@ -4,17 +4,45 @@
 import logging
 import portage
 from portage import os
+from portage.dbapi._MergeProcess import MergeProcess
+from portage.exception import UnsupportedAPIException
+from _emerge.EbuildBuildDir import EbuildBuildDir
 from _emerge.emergelog import emergelog
 from _emerge.CompositeTask import CompositeTask
 from _emerge.unmerge import _unmerge_display
 
 class PackageUninstall(CompositeTask):
+       """
+       Uninstall a package asynchronously in a subprocess. When
+       both parallel-install and ebuild-locks FEATURES are enabled,
+       it is essential for the ebuild-locks code to execute in a
+       subprocess, since the portage.locks module does not behave
+       as desired if we try to lock the same file multiple times
+       concurrently from the same process.
+       """
 
        __slots__ = ("world_atom", "ldpath_mtimes", "opts",
-                       "pkg", "settings")
+                       "pkg", "settings", "_builddir_lock")
 
        def _start(self):
 
+               self.settings.setcpv(self.pkg)
+               vardb = self.pkg.root_config.trees["vartree"].dbapi
+               dbdir = vardb.getpath(self.pkg.cpv)
+               cat, pf = portage.catsplit(self.pkg.cpv)
+               myebuildpath = os.path.join(dbdir, pf + ".ebuild")
+
+               try:
+                       portage.doebuild_environment(myebuildpath, "prerm",
+                               settings=self.settings, db=vardb)
+               except UnsupportedAPIException:
+                       # This is safe to ignore since this function is
+                       # guaranteed to set PORTAGE_BUILDDIR even though
+                       # it raises UnsupportedAPIException. The error
+                       # will be logged when it prevents the pkg_prerm
+                       # and pkg_postrm phases from executing.
+                       pass
+
                retval, pkgmap = _unmerge_display(self.pkg.root_config,
                        self.opts, "unmerge", [self.pkg.cpv], clean_delay=0,
                        writemsg_level=self._writemsg_level)
@@ -29,18 +57,31 @@ class PackageUninstall(CompositeTask):
                self._emergelog("=== Unmerging... (%s)" % (self.pkg.cpv,))
 
                cat, pf = portage.catsplit(self.pkg.cpv)
-               retval = portage.unmerge(cat, pf, settings=self.settings,
-                       vartree=self.pkg.root_config.trees["vartree"],
-                       ldpath_mtimes=self.ldpath_mtimes,
-                       scheduler=self.scheduler)
 
-               if retval != os.EX_OK:
+               self._builddir_lock = EbuildBuildDir(
+                       scheduler=self.scheduler, settings=self.settings)
+               self._builddir_lock.lock()
+
+               portage.prepare_build_dirs(
+                       settings=self.settings, cleanup=True)
+
+               unmerge_task = MergeProcess(
+                       mycat=cat, mypkg=pf, settings=self.settings,
+                       treetype="vartree", vartree=self.pkg.root_config.trees["vartree"],
+                       scheduler=self.scheduler, background=self.background,
+                       mydbapi=self.pkg.root_config.trees["vartree"].dbapi,
+                       prev_mtimes=self.ldpath_mtimes,
+                       logfile=self.settings.get("PORTAGE_LOG_FILE"), unmerge=True)
+
+               self._start_task(unmerge_task, self._unmerge_exit)
+
+       def _unmerge_exit(self, unmerge_task):
+               if self._final_exit(unmerge_task) != os.EX_OK:
                        self._emergelog(" !!! unmerge FAILURE: %s" % (self.pkg.cpv,))
                else:
                        self._emergelog(" >>> unmerge success: %s" % (self.pkg.cpv,))
                        self.world_atom(self.pkg)
-
-               self.returncode = retval
+               self._builddir_lock.unlock()
                self.wait()
 
        def _emergelog(self, msg):
index c2286067c2949b1bac3433e552923f6892ddd372..43bec72947e52e9c7f51c705b18f53ee2fdb02bb 100644 (file)
@@ -3,6 +3,7 @@
 
 import shutil
 import signal
+import sys
 import tempfile
 import traceback
 
@@ -26,7 +27,7 @@ class MergeProcess(SpawnProcess):
 
        __slots__ = ('mycat', 'mypkg', 'settings', 'treetype',
                'vartree', 'scheduler', 'blockers', 'pkgloc', 'infloc', 'myebuild',
-               'mydbapi', 'prev_mtimes', '_elog_reader_fd', '_elog_reg_id',
+               'mydbapi', 'prev_mtimes', 'unmerge', '_elog_reader_fd', '_elog_reg_id',
                '_buf', '_elog_keys', '_locked_vdb')
 
        def _start(self):
@@ -45,7 +46,8 @@ class MergeProcess(SpawnProcess):
                        settings.reset()
                        settings.setcpv(cpv, mydb=self.mydbapi)
 
-               self._handle_self_reinstall()
+               if not self.unmerge:
+                       self._handle_self_reinstall()
                super(MergeProcess, self)._start()
 
        def _lock_vdb(self):
@@ -170,7 +172,9 @@ class MergeProcess(SpawnProcess):
                # FEATURES=parallel-install skips this lock in order to
                # improve performance, and the risk is practically negligible.
                self._lock_vdb()
-               counter = self.vartree.dbapi.counter_tick()
+               counter = None
+               if not self.unmerge:
+                       counter = self.vartree.dbapi.counter_tick()
 
                pid = os.fork()
                if pid != 0:
@@ -213,7 +217,7 @@ class MergeProcess(SpawnProcess):
                # already be opened by the parent process, so we set the
                # "subprocess" value for use in conditional logging code
                # involving PORTAGE_LOG_FILE.
-               if self.settings.get("PORTAGE_BACKGROUND") == "1":
+               if not self.unmerge and self.settings.get("PORTAGE_BACKGROUND") == "1":
                        # unmerge phases have separate logs
                        self.settings["PORTAGE_BACKGROUND_UNMERGE"] = "1"
                        self.settings.backup_changes("PORTAGE_BACKGROUND_UNMERGE")
@@ -222,9 +226,21 @@ class MergeProcess(SpawnProcess):
 
                rval = 1
                try:
-                       rval = mylink.merge(self.pkgloc, self.infloc,
-                               myebuild=self.myebuild, mydbapi=self.mydbapi,
-                               prev_mtimes=self.prev_mtimes, counter=counter)
+                       if self.unmerge:
+                               if not mylink.exists():
+                                       rval = os.EX_OK
+                               elif mylink.unmerge(
+                                       ldpath_mtimes=self.prev_mtimes) == os.EX_OK:
+                                       mylink.lockdb()
+                                       try:
+                                               mylink.delete()
+                                       finally:
+                                               mylink.unlockdb()
+                                       rval = os.EX_OK
+                       else:
+                               rval = mylink.merge(self.pkgloc, self.infloc,
+                                       myebuild=self.myebuild, mydbapi=self.mydbapi,
+                                       prev_mtimes=self.prev_mtimes, counter=counter)
                except SystemExit:
                        raise
                except:
index 1867cf299fde8d2cf9019ce9ba76f9fd2bf76078..a9d2d147cbcdd825fe9b57f4a61a47f841effe28 100644 (file)
@@ -1604,6 +1604,7 @@ class dblink(object):
                                DeprecationWarning, stacklevel=2)
 
                background = False
+               log_path = None
                if self._scheduler is None:
                        # We create a scheduler instance and use it to
                        # log unmerge output separately from merge output.
@@ -1614,7 +1615,8 @@ class dblink(object):
                                self.settings.backup_changes("PORTAGE_BACKGROUND")
                                background = True
                        else:
-                               self.settings.pop("PORTAGE_BACKGROUND", None)
+                               # Our output is redirected and logged by the parent process.
+                               log_path = self.settings.pop("PORTAGE_LOG_FILE", None)
                elif self.settings.get("PORTAGE_BACKGROUND") == "1":
                        background = True
 
@@ -1647,7 +1649,6 @@ class dblink(object):
                myebuildpath = None
                failures = 0
                ebuild_phase = "prerm"
-               log_path = None
                mystuff = os.listdir(self.dbdir)
                for x in mystuff:
                        if x.endswith(".ebuild"):
@@ -1659,7 +1660,11 @@ class dblink(object):
                                        write_atomic(os.path.join(self.dbdir, "PF"), self.pkg+"\n")
                                break
 
-               self.settings.setcpv(self.mycpv, mydb=self.vartree.dbapi)
+               if self.mycpv != self.settings.mycpv or \
+                       "SLOT" not in self.settings.configdict["pkg"]:
+                       # We avoid a redundant setcpv call here when
+                       # the caller has already taken care of it.
+                       self.settings.setcpv(self.mycpv, mydb=self.vartree.dbapi)
                if myebuildpath:
                        try:
                                doebuild_environment(myebuildpath, "prerm",
@@ -1677,21 +1682,22 @@ class dblink(object):
                self._prune_plib_registry(unmerge=True, needed=needed,
                        preserve_paths=preserve_paths)
 
+               builddir_locked = "PORTAGE_BUILDIR_LOCKED" in self.settings
                builddir_lock = None
-               log_path = None
                scheduler = self._scheduler
                retval = os.EX_OK
                try:
                        if myebuildpath:
-                               # Only create builddir_lock if doebuild_environment
-                               # succeeded, since that's needed to initialize
-                               # PORTAGE_BUILDDIR.
-                               builddir_lock = EbuildBuildDir(
-                                       scheduler=scheduler,
-                                       settings=self.settings)
-                               builddir_lock.lock()
-                               prepare_build_dirs(settings=self.settings, cleanup=True)
-                               log_path = self.settings.get("PORTAGE_LOG_FILE")
+                               # Only create builddir_lock if the caller
+                               # has not already acquired the lock.
+                               if not builddir_locked:
+                                       builddir_lock = EbuildBuildDir(
+                                               scheduler=scheduler,
+                                               settings=self.settings)
+                                       builddir_lock.lock()
+                                       builddir_locked = True
+                                       prepare_build_dirs(settings=self.settings, cleanup=True)
+                                       log_path = self.settings.get("PORTAGE_LOG_FILE")
 
                                phase = EbuildPhase(background=background,
                                        phase=ebuild_phase, scheduler=scheduler,
@@ -1728,7 +1734,7 @@ class dblink(object):
 
                finally:
                        self.vartree.dbapi._bump_mtime(self.mycpv)
-                       if builddir_lock:
+                       if builddir_locked:
                                try:
                                        if myebuildpath:
                                                if retval != os.EX_OK:
@@ -1771,7 +1777,7 @@ class dblink(object):
 
                                        self._elog_process(phasefilter=("prerm", "postrm"))
 
-                                       if retval == os.EX_OK and builddir_lock is not None:
+                                       if retval == os.EX_OK and builddir_locked:
                                                # myebuildpath might be None, so ensure
                                                # it has a sane value for the clean phase,
                                                # even though it won't really be sourced.