Skip to content

Prevent cache entry timeout overrides from leaking - #9433

Merged
kuzeyron merged 2 commits into
kivy:masterfrom
charan-rathore:fix/cache-timeout-leak
Oct 19, 2026
Merged

kuzeyron merged 2 commits into
kivy:masterfrom
charan-rathore:fix/cache-timeout-leak

Conversation

@charan-rathore

@charan-rathore charan-rathore commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Closes #9431

Cache._purge_by_timeout decides each entry's expiry from a single timeout local that is seeded once per category and overwritten by any entry's own timeout, but never reset for the next entry. Because dict iteration is insertion order, a per-entry timeout leaks sideways onto whatever was stored after it in the same category: an entry with timeout=10 makes every later entry in that category expire on the same 10 second schedule, even when the entry and the category asked for no timeout at all. The leak only occurs for categories registered with timeout=None - entries in a timed category always carry their own timeout (Cache.append() stores timeout or cat['timeout']), so that scenario is not affected (corrected after review, see #9431).

The fix resets the effective timeout per entry, falling back to the category's current timeout (which the lag-compensation branch may have doubled), instead of carrying the previous entry's value forward.

Test evidence: a stub-harness regression test (monkeypatched clock) shows RED before the patch and GREEN after. The test covers the failing insertion order (timed entry before the untimed one); the reverse order never leaked, on master or with the fix. The full Kivy test suite needs the graphics build, so CI coverage applies here.

Cache._purge_by_timeout decided each entry's expiry from a single
local variable seeded once per category. An entry's own timeout
overwrote it and the value was never reset for the next entry, so a
per-entry timeout leaked sideways: entries stored after an overridden
entry in the same category expired on the wrong schedule, including
entries with no timeout at all.

Reset the effective timeout per entry, falling back to the category's
current timeout (which the lag-compensation branch may have doubled).

Signed-off-by: Charan Rathore <[email protected]>
@ElliotGarbus

Copy link
Copy Markdown
Contributor

Thanks for the PR. I confirmed the new test fails on master and passes with the fix.

A few things need fixing:

  • CI: PEP8_test fails with E501 Line too long (106 > 88) on the changed line in cache.py.

  • Scope of the bug: Cache._purge_by_timeout: a per-entry timeout leaks onto the next entry in the same category #9431 and the description say the leak also happens when the category has a timeout. It doesn't: Cache.append() stores timeout or cat['timeout'] on every entry, so entries added to a category with a timeout always carry their own. I ran that scenario (category timeout 100, an earlier entry overridden to 10) on master and the later entry survives. The leak only occurs when the category was registered with timeout=None. Please correct the issue and description.

  • Test coverage claim: the description says both insertion orders are covered, but the test only covers one (timed entry first). That is the only failing order, so the test is fine; the description should match.

  • Conflict with Fix cache purge removing an extra entry #9423: both PRs add kivy/tests/test_cache.py, so whichever merges second will conflict.

Also, none of the 12 Cache.append() calls in Kivy pass a per-entry timeout, so this only affects third-party code, and #9431 was opened by you the same day as this PR, so I don't see an independent report. For future PRs I'd suggest picking up user-reported issues or items from the Kivy 3.0 project list.

@charan-rathore

Copy link
Copy Markdown
Contributor Author

Thanks @ElliotGarbus - all four addressed:

And understood on selection: user-reported issues or the 3.0 list from here.

@ElliotGarbus
ElliotGarbus requested a review from kuzeyron October 19, 2026 00:46
@ElliotGarbus

Copy link
Copy Markdown
Contributor

@kuzeyron Ready to merge

Comment thread kivy/cache.py Resolved
@charan-rathore

Copy link
Copy Markdown
Contributor Author

@kuzeyron I'd keep the explicit is not None here: 0 is a meaningful timeout in Cache (curtime - lastaccess > timeout in _purge_by_timeout purges anything the instant it ages at all), so objtimeout or ... would silently swap a caller's explicit timeout=0 for the category default. The falsy-but-valid 0 is exactly the case the leak fix has to preserve.

@kuzeyron
kuzeyron merged commit db3846a into kivy:master Oct 19, 2026
33 checks passed
@charan-rathore

Copy link
Copy Markdown
Contributor Author

Thanks @kuzeyron for the merge, and @ElliotGarbus for the careful review.

Signed-off-by: Charan Rathore [email protected]

@kivybot

kivybot commented Nov 12, 2026

Copy link
Copy Markdown

Successfully created backport PR for devel-2.3.x:

@AndreMiras AndreMiras added this to the 3.0.0 milestone Nov 12, 2026
@AndreMiras AndreMiras added the Component: Misc compat, cache, tools, network, etc. label Nov 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport devel-2.3.x Component: Misc compat, cache, tools, network, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cache._purge_by_timeout: a per-entry timeout leaks onto the next entry in the same category

5 participants