You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Three lock maps are keyed by file path and never evict entries. In an agent deployment the paths are chosen by the model rather than by a fixed application config, so the key space is effectively unbounded.
Related to #2261 (ReActAgent.stateCache unbounded growth), but these are different maps and one of them is static.
Affected code (current main)
Location
Declaration
Key
Lifetime
agentscope-core/.../skill/SkillBox.java:57
private static final ConcurrentHashMap<String, Object> FILE_LOCKS
private final ConcurrentHashMap<String, ReentrantLock> fileLocks
lock key (line 359)
instance
Each is populated exclusively through computeIfAbsent, e.g.:
// SkillBox.java:834Objectlock = FILE_LOCKS.computeIfAbsent(targetPath.toString(), k -> newObject());
Grepping each file for remove / clear on these maps returns 0 matches — entries are only ever added.
Why it matters here
FILE_LOCKS being static is the notable one: every distinct path any skill has ever written to retains an entry for the lifetime of the process, across all SkillBox instances.
The usual justification for never evicting a per-key lock map is that the key space is bounded by application configuration. That does not hold for an agent runtime — file paths come from model output, so a long-running process accumulates one entry per distinct path the model ever produced.
Scale, stated honestly
The leaked values are small (Object, ReentrantLock), so this is a slow leak rather than a fast one — unlike #2261, which retains AgentState objects. Reaching memory pressure needs a large number of distinct paths. I am reporting it as a boundedness defect rather than claiming an imminent OOM.
Possible directions
Striped locking — a fixed number of locks selected by key.hashCode(), so the map never grows (the standard fix; Guava's Striped does exactly this). Costs occasional false sharing between unrelated paths.
Remove after release — finally { map.remove(key); }, but that races with another thread that is about to acquire the same lock, so it needs care.
I have not opened a PR because the choice among these is a design call, and option 3 in particular should probably follow whatever #2261 lands on. Happy to implement whichever direction maintainers prefer, with a test asserting the map stays bounded across many distinct paths.
Summary
Three lock maps are keyed by file path and never evict entries. In an agent deployment the paths are chosen by the model rather than by a fixed application config, so the key space is effectively unbounded.
Related to #2261 (
ReActAgent.stateCacheunbounded growth), but these are different maps and one of them isstatic.Affected code (current
main)agentscope-core/.../skill/SkillBox.java:57private static final ConcurrentHashMap<String, Object> FILE_LOCKStargetPath.toString()(line 834)agentscope-harness/.../workspace/WorkspaceManager.java:116private final Map<String, ReentrantLock> pathLocksagentscope-harness/.../filesystem/local/LocalFilesystem.java:93private final ConcurrentHashMap<String, ReentrantLock> fileLocksEach is populated exclusively through
computeIfAbsent, e.g.:Grepping each file for
remove/clearon these maps returns 0 matches — entries are only ever added.Why it matters here
FILE_LOCKSbeingstaticis the notable one: every distinct path any skill has ever written to retains an entry for the lifetime of the process, across allSkillBoxinstances.The usual justification for never evicting a per-key lock map is that the key space is bounded by application configuration. That does not hold for an agent runtime — file paths come from model output, so a long-running process accumulates one entry per distinct path the model ever produced.
Scale, stated honestly
The leaked values are small (
Object,ReentrantLock), so this is a slow leak rather than a fast one — unlike #2261, which retainsAgentStateobjects. Reaching memory pressure needs a large number of distinct paths. I am reporting it as a boundedness defect rather than claiming an imminent OOM.Possible directions
key.hashCode(), so the map never grows (the standard fix; Guava'sStripeddoes exactly this). Costs occasional false sharing between unrelated paths.finally { map.remove(key); }, but that races with another thread that is about to acquire the same lock, so it needs care.I have not opened a PR because the choice among these is a design call, and option 3 in particular should probably follow whatever #2261 lands on. Happy to implement whichever direction maintainers prefer, with a test asserting the map stays bounded across many distinct paths.