summaryrefslogtreecommitdiffstats
path: root/pym
diff options
context:
space:
mode:
authorZac Medico <zmedico@gentoo.org>2011-05-23 22:31:20 -0700
committerZac Medico <zmedico@gentoo.org>2011-05-23 22:31:20 -0700
commitf0f1bbe8fa9d3f698cbe529d2a11eec1ce437119 (patch)
tree0a8298a7bc680c26557fb64a92564a3fc40d992f /pym
parent2610641d07aa6af21c132d438995838c516a67f2 (diff)
downloadportage-f0f1bbe8fa9d3f698cbe529d2a11eec1ce437119.tar.gz
portage-f0f1bbe8fa9d3f698cbe529d2a11eec1ce437119.tar.bz2
portage-f0f1bbe8fa9d3f698cbe529d2a11eec1ce437119.zip
PackageUninstall: make async with MergeProcess
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.
Diffstat (limited to 'pym')
-rw-r--r--pym/_emerge/PackageUninstall.py57
-rw-r--r--pym/portage/dbapi/_MergeProcess.py30
-rw-r--r--pym/portage/dbapi/vartree.py36
3 files changed, 93 insertions, 30 deletions
diff --git a/pym/_emerge/PackageUninstall.py b/pym/_emerge/PackageUninstall.py
index 0e9130713..0829f50b7 100644
--- a/pym/_emerge/PackageUninstall.py
+++ b/pym/_emerge/PackageUninstall.py
@@ -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):
diff --git a/pym/portage/dbapi/_MergeProcess.py b/pym/portage/dbapi/_MergeProcess.py
index c2286067c..43bec7294 100644
--- a/pym/portage/dbapi/_MergeProcess.py
+++ b/pym/portage/dbapi/_MergeProcess.py
@@ -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:
diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py
index 1867cf299..a9d2d147c 100644
--- a/pym/portage/dbapi/vartree.py
+++ b/pym/portage/dbapi/vartree.py
@@ -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.