Skip to content

Bound nrepl-completed-requests with a FIFO cap#3898

Merged
bbatsov merged 1 commit into
masterfrom
bound-completed-requests
Apr 29, 2026
Merged

Bound nrepl-completed-requests with a FIFO cap#3898
bbatsov merged 1 commit into
masterfrom
bound-completed-requests

Conversation

@bbatsov
Copy link
Copy Markdown
Member

@bbatsov bbatsov commented Apr 29, 2026

Found during a wider audit of request handling. The longest-standing of the four issues this audit surfaced.

nrepl--mark-id-completed moves request handlers from nrepl-pending-requests to nrepl-completed-requests with no upper bound on the latter. The function carries a ;; FIXME: This should go away eventually when we get rid of pending-request hash table comment that's been sitting there for years.

Each completed entry retains its handler closure (often hundreds of bytes once you count its lexical environment), so a long-running session accumulated thousands of stale handlers in memory.

The completed table exists for a real reason: late messages -- responses arriving after their request's done status -- still need a callback to dispatch them. In practice that window is sub-second.

This PR adds a FIFO cap:

  • New defcustom nrepl-completed-requests-max-size, default 1000.
  • New buffer-local nrepl--completed-requests-order tracking insertion order.
  • nrepl--mark-id-completed enqueues each id; when the cap is exceeded, dequeues the oldest and removes it from the hash.

1000 is enough to cover any plausible late-message window: requests complete in milliseconds, so this caps roughly a second of activity. Setting the size to 0 bypasses eviction, restoring the previous unbounded behavior for users who want it.

Three Buttercup specs cover: the cap is honored, FIFO eviction order is correct, and 0 disables eviction.

`nrepl--mark-id-completed' moved request handlers from
`nrepl-pending-requests' to `nrepl-completed-requests' with no upper
bound on the latter.  Each entry retains its handler closure (often
hundreds of bytes once you count its lexical environment), so a long-
running session accumulated thousands of stale handlers in memory.

The completed table exists so late messages -- responses arriving
after their request's `done' status -- can still find a callback to
dispatch them.  In practice that window is sub-second.  Add a FIFO
cap so the table is bounded to the recently-completed entries:

- New defcustom `nrepl-completed-requests-max-size' (default 1000).
- New buffer-local `nrepl--completed-requests-order' tracking
  insertion order.
- `nrepl--mark-id-completed' enqueues each id and, when the cap is
  exceeded, dequeues + removes the oldest.

1000 is enough to cover any plausible late-message window: requests
complete in milliseconds, so this caps about a second of activity.
Setting the size to 0 bypasses the eviction, restoring the previous
unbounded behavior for users who have a reason to keep everything.

Three Buttercup specs cover the cap, FIFO eviction, and the 0-disables
case.
@bbatsov bbatsov force-pushed the bound-completed-requests branch from a1266f4 to 4b5711f Compare April 29, 2026 21:40
@bbatsov bbatsov merged commit e711107 into master Apr 29, 2026
13 checks passed
@bbatsov bbatsov deleted the bound-completed-requests branch April 29, 2026 21:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant