Make the `BuildDispatch` interpreter method async (#14956)

This is a bit of a weird request, but in [pixi](https://pixi.sh) we are
making use of this function to lazily instantiate a conda environment.
Well, in actuality we are using a shim to the `BuildDispatch` to
actually to only create a conda prefix, if some package needs to be
built during the resolution phase. Otherwise we can resolve everything
without an enviroment containing a python intepreter.

We are using a method now - that uses the runtime to run async code
inside this function, as `interpreter` is the first method called on a
`BuildContext` when running a source build - using
`tokio::Handle::block_on`.
However was causing a deadlock in very specific situations, me and
@baszalmstra + @wolfv have investigated this thoroughly, but have not
been able to find the root cause. It would hang in a part of the uv code
that hits the index, but that is **after** all of our initialization
*and the blocking call* was completed.
Changing this to be fully async fixes the problem, this requires this
method to be async though.

We get that this is not necessarily required, and we might find a
workaround, but I wanted to try it this way first.

Thanks!
This commit is contained in:
Tim de Jager 2025-07-31 13:42:27 +02:00 committed by GitHub
parent 3df972f18a
commit fc0f637406
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 9 additions and 3 deletions

View File

@ -175,7 +175,7 @@ impl<'a> BuildDispatch<'a> {
impl BuildContext for BuildDispatch<'_> { impl BuildContext for BuildDispatch<'_> {
type SourceDistBuilder = SourceBuild; type SourceDistBuilder = SourceBuild;
fn interpreter(&self) -> &Interpreter { async fn interpreter(&self) -> &Interpreter {
self.interpreter self.interpreter
} }

View File

@ -2386,11 +2386,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
let base_python = if cfg!(unix) { let base_python = if cfg!(unix) {
self.build_context self.build_context
.interpreter() .interpreter()
.await
.find_base_python() .find_base_python()
.map_err(Error::BaseInterpreter)? .map_err(Error::BaseInterpreter)?
} else { } else {
self.build_context self.build_context
.interpreter() .interpreter()
.await
.to_base_python() .to_base_python()
.map_err(Error::BaseInterpreter)? .map_err(Error::BaseInterpreter)?
}; };
@ -2485,7 +2487,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
// Ensure that the _installed_ Python version is compatible with the `requires-python` // Ensure that the _installed_ Python version is compatible with the `requires-python`
// specifier. // specifier.
if let Some(requires_python) = source.requires_python() { if let Some(requires_python) = source.requires_python() {
let installed = self.build_context.interpreter().python_version(); let installed = self.build_context.interpreter().await.python_version();
let target = release_specifiers_to_ranges(requires_python.clone()) let target = release_specifiers_to_ranges(requires_python.clone())
.bounding_range() .bounding_range()
.map(|bounding_range| bounding_range.0.cloned()) .map(|bounding_range| bounding_range.0.cloned())
@ -2507,11 +2509,13 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
let base_python = if cfg!(unix) { let base_python = if cfg!(unix) {
self.build_context self.build_context
.interpreter() .interpreter()
.await
.find_base_python() .find_base_python()
.map_err(Error::BaseInterpreter)? .map_err(Error::BaseInterpreter)?
} else { } else {
self.build_context self.build_context
.interpreter() .interpreter()
.await
.to_base_python() .to_base_python()
.map_err(Error::BaseInterpreter)? .map_err(Error::BaseInterpreter)?
}; };

View File

@ -62,8 +62,10 @@ use crate::BuildArena;
pub trait BuildContext { pub trait BuildContext {
type SourceDistBuilder: SourceBuildTrait; type SourceDistBuilder: SourceBuildTrait;
// Note: this function is async deliberately, because downstream code may need to
// run async code to get the interpreter, to resolve the Python version.
/// Return a reference to the interpreter. /// Return a reference to the interpreter.
fn interpreter(&self) -> &Interpreter; fn interpreter(&self) -> impl Future<Output = &Interpreter> + '_;
/// Return a reference to the cache. /// Return a reference to the cache.
fn cache(&self) -> &Cache; fn cache(&self) -> &Cache;