Skip to content

BaseProvider.resolver_cache is not thread-safe and can corrupt candidate lists during parallel builds #1024

@LalatenduMohanty

Description

@LalatenduMohanty

Summary

BaseProvider.resolver_cache in resolver.py is a class-level dict with no thread safety. When build_parallel() launches multiple threads via ThreadPoolExecutor, concurrent access to the cache can silently corrupt dependency resolution results.

Problem

  1. resolver_cache (line 425) is a class-level dict with no locking.
  2. _get_cached_candidates() (lines 555-565) returns a direct reference to the internal cached list — its docstring even states "The caller can mutate the list in place to update the cache."
  3. _find_cached_candidates() (line 582) mutates the cached list in-place with cached_candidates[:] = candidates.
  4. build_parallel() in commands/build.py (line 645) uses ThreadPoolExecutor to run builds concurrently.

When multiple threads resolve the same identifier simultaneously, they all see an empty cache, all call find_candidates(), and all write back via in-place slice assignment — silently clobbering each other's results. This can produce wrong dependency resolution without any error or warning.

The codebase already has threading_utils.with_thread_lock() which could be applied here.

How to reproduce

Run a parallel build where multiple packages share a common dependency. Under thread contention, find_candidates() will be called multiple times for the same identifier instead of once, and the cached candidate list may contain results from a different thread's resolution.

Testing suggestions

  • Defensive copy: Call _get_cached_candidates(), mutate the returned list, call again — assert the mutation didn't leak back into the cache.
  • Thread safety: Stub find_candidates() with a brief sleep, launch 4 threads synchronized via a Barrier, assert find_candidates() was called exactly once. Without locking, all 4 threads bypass the cache.

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions