Repository: pantsbuild/pants

Issue:
Pants resolves plugins twice during pantsd startup
This was caused by #14058. One resolve happens before the `Initializing scheduler.../Scheduler initialized.` log pair, and another after.

There are two issues here:

1) Why are two resolves happening in the first place? (maybe this is expected?)
2) Why the second one isn't using the cached result of the first one.

Regarding 2), my debugging shows that this is because the working set is different in each case. In the first resolve it contains just the lockfile, and in the second one it contains the union of the lockfile and (the transitive closure of) the plugins.


Discussion:

Resolving patch:
diff --git a/src/python/pants/init/plugin_resolver.py b/src/python/pants/init/plugin_resolver.py
--- a/src/python/pants/init/plugin_resolver.py
+++ b/src/python/pants/init/plugin_resolver.py
@@ -102,27 +102,39 @@ async def resolve_plugins(
 
 
 class PluginResolver:
-    def __init__(self, scheduler: BootstrapScheduler) -> None:
+    """Encapsulates the state of plugin loading for the given WorkingSet.
+
+    Plugin loading is inherently stateful, and so this class captures the state of the WorkingSet at
+    creation time, even though it will be mutated by each call to `PluginResolver.resolve`. This
+    makes the inputs to each `resolve(..)` call idempotent, even if the output is not.
+    """
+
+    def __init__(
+        self,
+        scheduler: BootstrapScheduler,
+        interpreter_constraints: Optional[InterpreterConstraints] = None,
+        working_set: Optional[WorkingSet] = None,
+    ) -> None:
         self._scheduler = scheduler
+        self._working_set = working_set or global_working_set
+        self._request = PluginsRequest(
+            interpreter_constraints, tuple(dist.as_requirement() for dist in self._working_set)
+        )
 
     def resolve(
         self,
         options_bootstrapper: OptionsBootstrapper,
         env: CompleteEnvironment,
-        interpreter_constraints: Optional[InterpreterConstraints] = None,
-        working_set: Optional[WorkingSet] = None,
     ) -> WorkingSet:
         """Resolves any configured plugins and adds them to the working_set."""
-        working_set = working_set or global_working_set
-        request = PluginsRequest(
-            interpreter_constraints, tuple(dist.as_requirement() for dist in working_set)
-        )
-        for resolved_plugin_location in self._resolve_plugins(options_bootstrapper, env, request):
+        for resolved_plugin_location in self._resolve_plugins(
+            options_bootstrapper, env, self._request
+        ):
             site.addsitedir(
                 resolved_plugin_location
             )  # Activate any .pth files plugin wheels may have.
-            working_set.add_entry(resolved_plugin_location)
-        return working_set
+            self._working_set.add_entry(resolved_plugin_location)
+        return self._working_set
 
     def _resolve_plugins(
         self,


Test patch:
diff --git a/tests/python/pants_test/init/test_plugin_resolver.py b/tests/python/pants_test/init/test_plugin_resolver.py
--- a/tests/python/pants_test/init/test_plugin_resolver.py
+++ b/tests/python/pants_test/init/test_plugin_resolver.py
@@ -185,17 +185,17 @@ def provide_chroot(existing):
             {**{k: os.environ[k] for k in ["PATH", "HOME", "PYENV_ROOT"] if k in os.environ}, **env}
         )
         bootstrap_scheduler = create_bootstrap_scheduler(options_bootstrapper)
-        plugin_resolver = PluginResolver(bootstrap_scheduler)
         cache_dir = options_bootstrapper.bootstrap_options.for_global_scope().named_caches_dir
 
         input_working_set = WorkingSet(entries=[])
         for dist in working_set_entries:
             input_working_set.add(dist)
+        plugin_resolver = PluginResolver(
+            bootstrap_scheduler, interpreter_constraints, input_working_set
+        )
         working_set = plugin_resolver.resolve(
             options_bootstrapper,
             complete_env,
-            interpreter_constraints,
-            input_working_set,
         )
         for dist in working_set:
             assert (