From 345256c2d439c5ab580e4226f227db2819883d40 Mon Sep 17 00:00:00 2001
From: Zac Medico <zmedico@gentoo.org>
Date: Mon, 4 Jun 2018 16:32:46 -0700
Subject: [PATCH] emerge -pf: spawn pkg_nofetch asynchronously (bug 657360)

For pretend mode, fix doebuild to skip the spawn_nofetch call
that would trigger event loop recursion, and spawn pkg_nofetch
asynchronously.

Bug: https://bugs.gentoo.org/657360
---
 pym/_emerge/EbuildBuild.py             | 16 ++++++++++++++--
 pym/_emerge/EbuildFetchonly.py         |  5 ++++-
 pym/portage/package/ebuild/doebuild.py | 15 +++++++--------
 3 files changed, 25 insertions(+), 11 deletions(-)

diff --git a/pym/_emerge/EbuildBuild.py b/pym/_emerge/EbuildBuild.py
index 00d4680f5a..d9f7f6da77 100644
--- a/pym/_emerge/EbuildBuild.py
+++ b/pym/_emerge/EbuildBuild.py
@@ -142,8 +142,20 @@ def _prefetch_exit(self, prefetcher):
 					pkg=pkg, pretend=opts.pretend,
 					settings=settings)
 				retval = fetcher.execute()
-				self.returncode = retval
-				self.wait()
+				if retval == os.EX_OK:
+					self._current_task = None
+					self.returncode = os.EX_OK
+					self._async_wait()
+				else:
+					# For pretend mode, the convention it to execute
+					# pkg_nofetch and return a successful exitcode.
+					self._start_task(SpawnNofetchWithoutBuilddir(
+						background=self.background,
+						portdb=self.pkg.root_config.trees[self._tree].dbapi,
+						ebuild_path=self._ebuild_path,
+						scheduler=self.scheduler,
+						settings=self.settings),
+						self._default_final_exit)
 				return
 			else:
 				fetcher = EbuildFetcher(
diff --git a/pym/_emerge/EbuildFetchonly.py b/pym/_emerge/EbuildFetchonly.py
index f88ea96ef2..eec2ad208d 100644
--- a/pym/_emerge/EbuildFetchonly.py
+++ b/pym/_emerge/EbuildFetchonly.py
@@ -25,7 +25,10 @@ def execute(self):
 			listonly=self.pretend, fetchonly=1, fetchall=self.fetch_all,
 			mydbapi=portdb, tree="porttree")
 
-		if rval != os.EX_OK:
+		# For pretend mode, this error message is suppressed,
+		# and the unsuccessful return value is used to trigger
+		# a call to the pkg_nofetch phase.
+		if rval != os.EX_OK and not self.pretend:
 			msg = "Fetch failed for '%s'" % (pkg.cpv,)
 			eerror(msg, phase="unpack", key=pkg.cpv)
 
diff --git a/pym/portage/package/ebuild/doebuild.py b/pym/portage/package/ebuild/doebuild.py
index dc443df003..0e94de805b 100644
--- a/pym/portage/package/ebuild/doebuild.py
+++ b/pym/portage/package/ebuild/doebuild.py
@@ -1079,14 +1079,13 @@ def doebuild(myebuild, mydo, _unused=DeprecationWarning, settings=None, debug=0,
 			if not fetch(fetchme, mysettings, listonly=listonly,
 				fetchonly=fetchonly, allow_missing_digests=False,
 				digests=dist_digests):
-				spawn_nofetch(mydbapi, myebuild, settings=mysettings,
-					fd_pipes=fd_pipes)
-				if listonly:
-					# The convention for listonly mode is to report
-					# success in any case, even though fetch() may
-					# return unsuccessfully in order to trigger the
-					# nofetch phase.
-					return 0
+				# Since listonly mode is called by emerge --pretend in an
+				# asynchronous context, spawn_nofetch would trigger event loop
+				# recursion here, therefore delegate execution of pkg_nofetch
+				# to the caller (bug 657360).
+				if not listonly:
+					spawn_nofetch(mydbapi, myebuild, settings=mysettings,
+						fd_pipes=fd_pipes)
 				return 1
 
 		if need_distfiles:
