aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFabian Groffen <grobian@gentoo.org>2024-02-22 08:27:11 +0100
committerFabian Groffen <grobian@gentoo.org>2024-02-22 08:27:11 +0100
commit07e60cd2a4f67f0b4207fb8150f9d7a1689cb295 (patch)
tree4f659d4e960c4292bfbad6d98a87c684100a686d /lib/portage/dbapi/porttree.py
parentbuildsys: avoid overuse of hprefixify (diff)
parentNEWS, meson.build: prepare for portage-3.0.62 (diff)
downloadportage-07e60cd2a4f67f0b4207fb8150f9d7a1689cb295.tar.gz
portage-07e60cd2a4f67f0b4207fb8150f9d7a1689cb295.tar.bz2
portage-07e60cd2a4f67f0b4207fb8150f9d7a1689cb295.zip
Merge remote-tracking branch 'origin/master' into prefix
Signed-off-by: Fabian Groffen <grobian@gentoo.org>
Diffstat (limited to 'lib/portage/dbapi/porttree.py')
-rw-r--r--lib/portage/dbapi/porttree.py130
1 files changed, 87 insertions, 43 deletions
diff --git a/lib/portage/dbapi/porttree.py b/lib/portage/dbapi/porttree.py
index eabf2d0a2..4eebe1183 100644
--- a/lib/portage/dbapi/porttree.py
+++ b/lib/portage/dbapi/porttree.py
@@ -1,4 +1,4 @@
-# Copyright 1998-2021 Gentoo Authors
+# Copyright 1998-2024 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
__all__ = ["close_portdbapi_caches", "FetchlistDict", "portagetree", "portdbapi"]
@@ -41,7 +41,9 @@ from portage.util.futures import asyncio
from portage.util.futures.iter_completed import iter_gather
from _emerge.EbuildMetadataPhase import EbuildMetadataPhase
+import contextlib
import os as _os
+import threading
import traceback
import warnings
import errno
@@ -106,7 +108,6 @@ class _dummy_list(list):
class _better_cache:
-
"""
The purpose of better_cache is to locate catpkgs in repositories using ``os.listdir()`` as much as possible, which
is less expensive IO-wise than exhaustively doing a stat on each repo for a particular catpkg. better_cache stores a
@@ -240,6 +241,7 @@ class portdbapi(dbapi):
# this purpose because doebuild makes many changes to the config
# instance that is passed in.
self.doebuild_settings = config(clone=self.settings)
+ self._doebuild_settings_lock = asyncio.Lock()
self.depcachedir = os.path.realpath(self.settings.depcachedir)
if os.environ.get("SANDBOX_ON") == "1":
@@ -357,6 +359,17 @@ class portdbapi(dbapi):
self._better_cache = None
self._broken_ebuilds = set()
+ def __getstate__(self):
+ state = self.__dict__.copy()
+ # These attributes are not picklable, so they are automatically
+ # regenerated after unpickling.
+ state["_doebuild_settings_lock"] = None
+ return state
+
+ def __setstate__(self, state):
+ self.__dict__.update(state)
+ self._doebuild_settings_lock = asyncio.Lock()
+
def _set_porttrees(self, porttrees):
"""
Consumers, such as emirrordist, may modify the porttrees attribute in
@@ -670,7 +683,7 @@ class portdbapi(dbapi):
self.async_aux_get(mycpv, mylist, mytree=mytree, myrepo=myrepo, loop=loop)
)
- def async_aux_get(self, mycpv, mylist, mytree=None, myrepo=None, loop=None):
+ async def async_aux_get(self, mycpv, mylist, mytree=None, myrepo=None, loop=None):
"""
Asynchronous form form of aux_get.
@@ -695,13 +708,11 @@ class portdbapi(dbapi):
# Callers of this method certainly want the same event loop to
# be used for all calls.
loop = asyncio._wrap_loop(loop)
- future = loop.create_future()
cache_me = False
if myrepo is not None:
mytree = self.treemap.get(myrepo)
if mytree is None:
- future.set_exception(PortageKeyError(myrepo))
- return future
+ raise PortageKeyError(myrepo)
if (
mytree is not None
@@ -720,16 +731,14 @@ class portdbapi(dbapi):
):
aux_cache = self._aux_cache.get(mycpv)
if aux_cache is not None:
- future.set_result([aux_cache.get(x, "") for x in mylist])
- return future
+ return [aux_cache.get(x, "") for x in mylist]
cache_me = True
try:
cat, pkg = mycpv.split("/", 1)
except ValueError:
# Missing slash. Can't find ebuild so raise PortageKeyError.
- future.set_exception(PortageKeyError(mycpv))
- return future
+ raise PortageKeyError(mycpv)
myebuild, mylocation = self.findname2(mycpv, mytree)
@@ -738,12 +747,12 @@ class portdbapi(dbapi):
"!!! aux_get(): %s\n" % _("ebuild not found for '%s'") % mycpv,
noiselevel=1,
)
- future.set_exception(PortageKeyError(mycpv))
- return future
+ raise PortageKeyError(mycpv)
mydata, ebuild_hash = self._pull_valid_cache(mycpv, myebuild, mylocation)
if mydata is not None:
+ future = loop.create_future()
self._aux_get_return(
future,
mycpv,
@@ -755,37 +764,71 @@ class portdbapi(dbapi):
cache_me,
None,
)
- return future
+ return future.result()
if myebuild in self._broken_ebuilds:
- future.set_exception(PortageKeyError(mycpv))
- return future
-
- proc = EbuildMetadataPhase(
- cpv=mycpv,
- ebuild_hash=ebuild_hash,
- portdb=self,
- repo_path=mylocation,
- scheduler=loop,
- settings=self.doebuild_settings,
- )
+ raise PortageKeyError(mycpv)
- proc.addExitListener(
- functools.partial(
- self._aux_get_return,
- future,
- mycpv,
- mylist,
- myebuild,
- ebuild_hash,
- mydata,
- mylocation,
- cache_me,
- )
- )
- future.add_done_callback(functools.partial(self._aux_get_cancel, proc))
- proc.start()
- return future
+ proc = None
+ deallocate_config = None
+ async with contextlib.AsyncExitStack() as stack:
+ try:
+ if (
+ threading.current_thread() is threading.main_thread()
+ and loop is asyncio._safe_loop()
+ ):
+ # In this case use self._doebuild_settings_lock to manage concurrency.
+ deallocate_config = loop.create_future()
+ await stack.enter_async_context(self._doebuild_settings_lock)
+ settings = self.doebuild_settings
+ else:
+ if portage._internal_caller:
+ raise AssertionError(
+ f"async_aux_get called from thread {threading.current_thread()} with loop {loop}"
+ )
+ # Clone a config instance since we do not have a thread-safe config pool.
+ settings = portage.config(clone=self.settings)
+
+ proc = EbuildMetadataPhase(
+ cpv=mycpv,
+ ebuild_hash=ebuild_hash,
+ portdb=self,
+ repo_path=mylocation,
+ scheduler=loop,
+ settings=settings,
+ deallocate_config=deallocate_config,
+ )
+
+ future = loop.create_future()
+ proc.addExitListener(
+ functools.partial(
+ self._aux_get_return,
+ future,
+ mycpv,
+ mylist,
+ myebuild,
+ ebuild_hash,
+ mydata,
+ mylocation,
+ cache_me,
+ )
+ )
+ future.add_done_callback(functools.partial(self._aux_get_cancel, proc))
+ proc.start()
+
+ finally:
+ # Wait for deallocate_config before releasing
+ # self._doebuild_settings_lock if needed.
+ if deallocate_config is not None:
+ if proc is None or not proc.isAlive():
+ deallocate_config.done() or deallocate_config.cancel()
+ else:
+ await deallocate_config
+
+ # After deallocate_config is done, release self._doebuild_settings_lock
+ # by leaving the stack context, and wait for proc to finish and
+ # trigger a call to self._aux_get_return.
+ return await future
@staticmethod
def _aux_get_cancel(proc, future):
@@ -890,7 +933,7 @@ class portdbapi(dbapi):
)
)
else:
- result.set_exception(future.exception())
+ result.set_exception(aux_get_future.exception())
return
eapi, myuris = aux_get_future.result()
@@ -914,8 +957,9 @@ class portdbapi(dbapi):
except Exception as e:
result.set_exception(e)
- aux_get_future = self.async_aux_get(
- mypkg, ["EAPI", "SRC_URI"], mytree=mytree, loop=loop
+ aux_get_future = asyncio.ensure_future(
+ self.async_aux_get(mypkg, ["EAPI", "SRC_URI"], mytree=mytree, loop=loop),
+ loop,
)
result.add_done_callback(
lambda result: aux_get_future.cancel() if result.cancelled() else None