Skip to content

fix(core): stop key in cache falling back to the legacy sequence protocol - #2558

Open
LeSingh1 wants to merge 1 commit into
NVIDIA:mainfrom
LeSingh1:core-program-cache-abc-contract
Open

fix(core): stop key in cache falling back to the legacy sequence protocol#2558
LeSingh1 wants to merge 1 commit into
NVIDIA:mainfrom
LeSingh1:core-program-cache-abc-contract

Conversation

@LeSingh1

@LeSingh1 LeSingh1 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Problem

ProgramCacheResource documents, at length, that it deliberately has no __contains__:

There is intentionally no __contains__: the obvious if key in cache: data = cache[key] idiom is racy across processes (another writer can os.replace over the entry, or eviction can unlink it, between the check and the read), and exposing __contains__ invites that pattern. get answers both questions in one filesystem-level operation…

The class defines __getitem__ and neither __iter__ nor __contains__. CPython then falls back to the legacy sequence-iteration protocol: key in cache becomes cache[0], cache[1], … compared against the values.

On the shipped backends that surfaces as an error about a lookup the user never wrote:

>>> b"k" in InMemoryProgramCache()
TypeError: cache keys must be bytes or str, got int
>>> list(cache)
TypeError: cache keys must be bytes or str, got int
>>> iter(cache)          # succeeds, and only blows up on the first next()
<iterator object at ...>

Worse: ProgramCacheResource is public (exported from cuda.core.utils) and exists to be subclassed. For a backend whose __getitem__ tolerates integers — a list-backed cache, say — the fallback answers silently and inverted. Verified against a minimal subclass of the real ABC:

b"v" in cache   ->  True     # b"v" is a VALUE
b"k" in cache   ->  False    # b"k" IS a key

So the one idiom the docstring set out to prevent is available, returns wrong answers, and does so without any error.

Fix

__iter__ = None — the standard way to opt a class out of the legacy protocol. in and iteration now raise:

TypeError: argument of type 'InMemoryProgramCache' is not iterable

which points at the real mistake, and the documented design actually holds. The docstring note gains one sentence saying so.

Everything else is untouched — __getitem__, get(), len(), clear(), update() with a mapping and with pairs, and the context-manager form all behave exactly as before. update() iterates its argument, not self, so it is unaffected. __contains__ remains non-abstract, as the existing test_program_cache_resource_requires_core_methods asserts.

Tests

Added test_program_cache_is_not_iterable_and_rejects_in, parametrized over both shipped backends: in, iter(), and list() each raise TypeError, while cache[k], get() (hit and miss) and len() keep working.

Verification I could and could not do

  • _abc.py / _in_memory.py / _file_stream.py import only stdlib plus cuda.core._module.ObjectCode, so I loaded them by path with a three-line stub for that symbol and ran the behaviour before and after:
--- upstream/main ---
  b'k' in cache -> TypeError: cache keys must be bytes or str, got int
  list(cache)   -> TypeError: cache keys must be bytes or str, got int
  iter(cache)   -> <iterator>            # no error at all
  subclass: b'v' in cache -> True (a VALUE);  b'k' in cache -> False (a KEY)

--- with the fix ---
  b'k' in cache / iter / list / dict -> TypeError: ... is not iterable
  cache[b'k'], get(hit), get(miss), len(), update(mapping), update(pairs),
  clear(), context manager, FileStream backend -> all unchanged
  • ruff check compared against an upstream/main baseline of _abc.py: no new findings (2 pre-existing UP038 under my local ruff 0.12.11, unchanged; the repo pins v0.15.9, where that rule no longer exists). ruff format --check and python -m py_compile clean.
  • Not run: pytest cuda_core/tests/test_program_cache.py itself — it imports cuda.core, which is not importable here (no CUDA driver, no built extension modules). The stub run exercises the same code path. Please treat CI as the first real run.

Related

Touches _abc.py only; independent of #2553 (_in_memory.py / _file_stream.py constructors) and #2555 (_keys.py).

…otocol

ProgramCacheResource documents, at length, that it deliberately has no
__contains__:

    There is intentionally no ``__contains__``: the obvious
    ``if key in cache: data = cache[key]`` idiom is racy across processes
    ... and exposing ``__contains__`` invites that pattern.

But the class defines __getitem__ and neither __iter__ nor __contains__, so
CPython falls back to the legacy sequence-iteration protocol: `key in cache`
becomes `cache[0], cache[1], ...` compared against the *values*.

On the shipped backends that surfaces as a baffling error from a lookup the
user never wrote:

    b"k" in InMemoryProgramCache()
    TypeError: cache keys must be bytes or str, got int

and `list(cache)` / `dict(cache)` fail the same way. Worse, ProgramCacheResource
is public and meant to be subclassed. For a backend whose __getitem__ accepts
integers -- a list-backed cache, say -- the fallback answers silently and
inverted:

    b"v" in cache   ->  True    # b"v" is a VALUE
    b"k" in cache   ->  False   # b"k" IS a key

Set ``__iter__ = None``, the standard way to opt out of that protocol. `in`
and iteration now raise the plain
``TypeError: argument of type 'InMemoryProgramCache' is not iterable``,
which points at the real mistake, and the documented design actually holds.

Everything else is untouched: __getitem__, get(), len(), clear(),
update() with a mapping or with pairs, and the context-manager form all
behave exactly as before. update() iterates its argument, not self.
@copy-pr-bot

copy-pr-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added the cuda.core Everything related to the cuda.core module label Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cuda.core Everything related to the cuda.core module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant