Finished sweeps are still retained after the read-cache fix. I ran the same change as #25285 (detachStackFrame around the read-cache lookups) on a server with about 18,700 thread pull request links and took heap snapshots at 11 minutes and at about 3 hours of uptime. Growth dropped from 22-25 MiB/min without the change to about 1 MiB/min, but old sweeps are still held:
| after 11 min | after 3 h | |
|---|---|---|
groups maps from finished sweeps still reachable | 1 | 4 |
objects carrying a pull request avatarUrl | 14,122 | 57,528 |
| live JS heap | 337 MiB | 508 MiB |
(The two snapshots come from different nightlies, 3692 and 3717, both with the change, so the heap figures are indicative only. Whether the map count keeps rising past 3 h is not measured.)
The mechanism is the same as in this issue: a Cache entry keeps the fiber that filled it, and that fiber keeps the caller's lazy stack frames, which close over the sweep. In the 3 h snapshot the cache entries that still reach a retained map are 13 in repositoryRootCache and 11 in repositoryIdentityCache (RepositoryIdentityResolver.resolve, positive TTL 15 min), plus a few through detectionCache (VcsDriverRegistry.detect, which the identity lookup calls). The first sweep to look a directory up fills the entry, and later sweeps read it without replacing it, so that first sweep stays alive.
Wrapping those four lookups in detachStackFrame is a few-line change. A test that runs five sweeps through resolve and checks that none of their data survives garbage collection fails without it and passes with it. I have not yet measured the effect on a long-running server. If it is useful I can open a pull request once this issue is triaged, and post the 3 h snapshot result here first.
Edited after review: an earlier version of this comment also named detailCache (PullRequestService.detail) and counted five lookups. The sync sweep does not call detail, so it is not part of the change.