Skip to content

Commit 9263c2a

Browse files
committed
Cache process_group_map() to fix high CPU when tab_title_template uses active_exe
Add a TTL-based caching option to cached_process_data() and use it in update_tab_bar_data() so the expensive /proc scan happens at most once per second across all OS windows and tabs. See #9862
1 parent a9e56d5 commit 9263c2a

2 files changed

Lines changed: 45 additions & 14 deletions

File tree

kitty/boss.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1926,7 +1926,8 @@ def on_activity_since_last_focus(self, window: Window) -> None:
19261926
def update_tab_bar_data(self, os_window_id: int) -> None:
19271927
tm = self.os_window_map.get(os_window_id)
19281928
if tm is not None:
1929-
tm.update_tab_bar_data()
1929+
with cached_process_data(ttl=1.0):
1930+
tm.update_tab_bar_data()
19301931

19311932
def on_drop_move(self, os_window_id: int, x: int, y: int, from_self: bool, is_leave: bool) -> None:
19321933
if (tm := self.os_window_map.get(os_window_id)) is None:

kitty/child.py

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
import sys
66
import termios
7+
import time
78
from collections import defaultdict
89
from collections.abc import Generator, Sequence
910
from contextlib import contextmanager, suppress
@@ -96,27 +97,56 @@ def checked_terminfo_dir() -> str | None:
9697
return terminfo_dir if os.path.isdir(terminfo_dir) else None
9798

9899

100+
_pgmap_cache: DefaultDict[int, list[int]] | None = None
101+
_pgmap_cache_at: float = 0.0
102+
_pgmap_ttl: float = 0.0
103+
104+
105+
def _refresh_pgmap_cache() -> DefaultDict[int, list[int]]:
106+
global _pgmap_cache, _pgmap_cache_at
107+
try:
108+
_pgmap_cache = process_group_map()
109+
except Exception:
110+
_pgmap_cache = defaultdict(list)
111+
_pgmap_cache_at = time.monotonic()
112+
return _pgmap_cache
113+
114+
99115
def processes_in_group(grp: int) -> list[int]:
100-
gmap: DefaultDict[int, list[int]] | None = getattr(process_group_map, 'cached_map', None)
101-
if gmap is None:
102-
try:
103-
gmap = process_group_map()
104-
except Exception:
105-
gmap = defaultdict(list)
116+
if _pgmap_cache is not None:
117+
if _pgmap_ttl <= 0 or (time.monotonic() - _pgmap_cache_at) < _pgmap_ttl:
118+
return _pgmap_cache.get(grp, [])
119+
try:
120+
gmap = process_group_map()
121+
except Exception:
122+
gmap = defaultdict(list)
106123
return gmap.get(grp, [])
107124

108125

109126
@contextmanager
110-
def cached_process_data() -> Generator[None, None, None]:
111-
try:
112-
cm = process_group_map()
113-
except Exception:
114-
cm = defaultdict(list)
115-
setattr(process_group_map, 'cached_map', cm)
127+
def cached_process_data(ttl: float = 0.0) -> Generator[None, None, None]:
128+
"""Cache process_group_map() results within this context.
129+
130+
With ttl=0 (default), a fresh snapshot is taken on entry and cleared
131+
on exit. Suitable for short-lived operations like listing windows.
132+
With ttl>0, the snapshot is reused across calls if still within the
133+
TTL window. Suitable for high-frequency callers like tab bar rendering.
134+
"""
135+
global _pgmap_cache, _pgmap_cache_at, _pgmap_ttl
136+
prev_cache, prev_at, prev_ttl = _pgmap_cache, _pgmap_cache_at, _pgmap_ttl
137+
_pgmap_ttl = ttl
138+
if ttl > 0 and _pgmap_cache is not None and (time.monotonic() - _pgmap_cache_at) < ttl:
139+
pass # reuse existing cache
140+
else:
141+
_refresh_pgmap_cache()
116142
try:
117143
yield
118144
finally:
119-
delattr(process_group_map, 'cached_map')
145+
if ttl <= 0:
146+
# no TTL, restore previous state so cache doesn't leak
147+
_pgmap_cache, _pgmap_cache_at, _pgmap_ttl = prev_cache, prev_at, prev_ttl
148+
else:
149+
_pgmap_ttl = prev_ttl
120150

121151

122152
def session_id(pids: Iterable[int]) -> int:

0 commit comments

Comments
 (0)