From 051a5b72e06b0c783e8c51b76db651f9934b767f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 06:48:39 +0000 Subject: [PATCH] Preferentially evict transient images before non-transient ones under storage pressure --- kitty/graphics.c | 9 ++++++--- kitty_tests/graphics.py | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/kitty/graphics.c b/kitty/graphics.c index 9705ea8a4..43d23acf5 100644 --- a/kitty/graphics.c +++ b/kitty/graphics.c @@ -287,9 +287,12 @@ apply_storage_quota(GraphicsManager *self, size_t storage_limit, id_type current if (!sorted) fatal("Out of memory"); Image **p = sorted; iter_images(self) { *p++ = i.data->val; } -#define oldest_img_first(a, b) ((*a)->atime < (*b)->atime) - QSORT(Image*, sorted, num_images, oldest_img_first); -#undef oldest_img_first +#define transient_or_older_first(a, b) ( \ + (*a)->root_frame.transient != (*b)->root_frame.transient ? \ + (*a)->root_frame.transient > (*b)->root_frame.transient : \ + (*a)->atime < (*b)->atime) + QSORT(Image*, sorted, num_images, transient_or_older_first); +#undef transient_or_older_first for (p = sorted; self->used_storage > storage_limit && num_images; p++, num_images--) remove_image(self, *p); if (!num_images || !vt_size(&self->images_by_internal_id)) self->used_storage = 0; // sanity check diff --git a/kitty_tests/graphics.py b/kitty_tests/graphics.py index 74cab1c00..ec6a0444d 100644 --- a/kitty_tests/graphics.py +++ b/kitty_tests/graphics.py @@ -1319,6 +1319,25 @@ class TestGraphics(BaseTest): self.ae(g.image_count, 0) self.assertEqual(g.disk_cache.total_size, 0) + def test_transient_image_preferential_eviction(self): + # Transient images should be evicted before non-transient ones when + # the storage quota is exceeded, regardless of insertion order. + s = self.create_screen() + g = s.grman + g.storage_limit = 36 * 2 + li = make_send_command(s) + # Load a non-transient image first (older atime) and a transient image second. + self.assertEqual(li(a='T', i=1).code, 'OK') + self.assertEqual(li(a='T', i=2, N=1).code, 'OK') + self.assertEqual(g.image_count, 2) + # Adding a third image triggers the quota; the transient image (i=2) must be + # evicted first even though the non-transient image (i=1) is older. + self.assertEqual(li(a='T', i=3).code, 'OK') + self.assertEqual(g.image_count, 2) + self.assertIsNone(g.image_for_client_id(2), 'transient image should have been evicted') + self.assertIsNotNone(g.image_for_client_id(1), 'non-transient image should survive') + self.assertIsNotNone(g.image_for_client_id(3), 'newly added image should survive') + @unittest.skipIf(Image is None, 'PIL not available, skipping PNG tests') def test_cached_rgba_conversion(self): from kitty.render_cache import ImageRenderCacheForTesting