From 78dba20a597bbeda06b7a2b768e5d7401929df32 Mon Sep 17 00:00:00 2001 From: tonycpsu Date: Sun, 12 Jul 2026 02:12:30 -0400 Subject: [PATCH] Bound SessionStore session cache to prevent unbounded memory growth (#2087) The cachedSessions HashMap grows with every unique (address, deviceId) pair seen during message processing and is never evicted. In a long-running daemon handling group messages, this causes linear memory growth (~47 MB/hour observed) as SessionRecord objects accumulate for every contact/device the daemon has ever communicated with. Replace the unbounded HashMap with an LRU-bounded LinkedHashMap (access order, max 1000 entries). Evicted sessions are reloaded from SQLite on next access, so correctness is preserved. Claude-Session: https://claude.ai/code/session_01HHzM2XLKQoX9iraEdhoh3h Co-authored-by: Claude Opus 4.6 --- .../signal/manager/storage/sessions/SessionStore.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java index fdc59e0c..4f3251e0 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java @@ -18,7 +18,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -28,8 +28,15 @@ public class SessionStore implements SignalServiceSessionStore { private static final String TABLE_SESSION = "session"; private static final Logger logger = LoggerFactory.getLogger(SessionStore.class); + private static final int MAX_CACHE_SIZE = 1000; - private final Map cachedSessions = new HashMap<>(); + private final Map cachedSessions = new LinkedHashMap<>(16, 0.75f, true) { + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > MAX_CACHE_SIZE; + } + }; private final Database database; private final int accountIdType;