Repository navigation
Conversation
nicojan
left a comment
There was a problem hiding this comment.
Thank you for this, and for writing down everything you looked at, the dead ends included. You're right that the sum of webcontent_mb hides one page that climbs, and reading each process on its own is the better measure. Each service has its own data store, so a footprint belongs to one service, and the SPI fallback is safe. I can't merge it yet, though. Most of what follows comes from main moving on since your base.
Needs fixing
- The ten-minute guard counts from when you opened the service, not from when you left it.
lastAccessTimesis only set on activation. Spend half an hour in WhatsApp, switch to Slack, and the next pass can rebuild WhatsApp seconds later. Please record when a service is left and measure from that, and add a test that runs activate, switch away, sweep. - A rebuild closes every open tab. Main added tabs in a service's card, and
teardownWebViewnow callscloseAllTabs()without the save step. Every hibernation path skips a service withhasOpenTabs(id). Please add the same check before and after the await. Tabs also run in the same process as the page, so a big tab inflates the very number that triggers the rebuild. - WhatsApp may come back signed out. The comment above the visibility override in
UserScriptManagersays WhatsApp saves its session when the page goes hidden. Quit and tab close both runquitReleaseJSand wait before tearing down. The rebuild doesn't. Your by-hand test used services that weren't signed in, so it couldn't show this. - The page comes back at its home address.
preloadloadsinstance.url, so the open chat or channel is lost. Please keepwebView.urland load that. - It can still loop. The limit is
max(1.5 GB, 2 × baseline), so below a 768 MB baseline the floor decides alone. A heavy client sampled early at 700 MB that settles at 1.6 GB gets rebuilt every pass. Carrying the old footprint over as the new baseline, or adding a backoff per service, would stop that.
What I'd suggest
The evidence is one process on one evening on macOS 27, and the service is a guess. I'd rather ship the measurement first: log each page's footprint, and log when it would have rebuilt one, for a release. That would also test my own finding that memory levels off, which you rightly question. If the logs show the climb, turn the rebuild on with the fixes above, one service per pass, skipping while offline and just after wake. I'd gladly take a PR in that shape, and a release-build run against a signed-in WhatsApp would help a lot.
The CHANGELOG states the WhatsApp figures as fact, while the internal note says the service was inferred. The old "no trend" lines in OPEN-ITEMS.md also need marking as superseded. I can do both when it lands.
On Teams: thanks for checking that #17 still holds. Deleting and re-adding the service is a useful fix to know about.
Measure first, act later, as the review of nicojan#36 asked. Every five minutes Chorus logs each page's footprint, read with proc_pid_rusage on the pid from _webProcessIdentifier (SPI, probed), under the service's catalog id. It also logs when a page would be rebuilt: past both 1.5 GB and twice its baseline, out of sight for ten minutes, and not on screen, pinned, Keep Loaded, capturing, audible or holding tabs. Nothing is rebuilt. From the review: - Out of sight is counted from when the user left the service, not from when they opened it. ServiceVisits records the switch, and a test runs activate, switch away, sweep. - A service with tabs open never counts, since a rebuild closes them. - A page that counts carries its footprint over as its new baseline, so a heavy page sampled early counts once, not on every pass. - The rebuild itself, and with it the quitReleaseJS save and the restore of webView.url, waits for the logs. OPEN-ITEMS lists what turning it on needs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
609f74e to
e713814
Compare
|
Thank you, Nico, for such a careful review. You were right on every point, and the measure-first idea is better than what I sent. I have reshaped the PR to match it, rebased on 1.5.27. The conflict is gone, and the CHANGELOG change is gone with it. What the PR does now. It only measures. Every five minutes Chorus logs each page's footprint under the service's catalog id, so a reading says To read it back: Your five points:
On the docs. The internal note now says plainly that the service is a guess from one evening on macOS 27. The old "no trend" line is marked as superseded for single pages, with a pointer to the new note. 319 tests pass locally (Debug), including the three new ones. I will run the release build with a signed-in WhatsApp over a working day once this is in, and report what the logs show. Thank you again for your time and patience. |
|
Merged, thank you. You fixed all five points, and the log-only shape is what I hoped for. I ran it on a Debug build tonight: after five minutes it wrote one line per page, and the pids matched the WebContent processes Chorus had started. I added a small commit on top (8221c77) rather than send it back again:
It ships in the next release. When you run it against a signed-in WhatsApp over a day, please post what the log shows here or in a new issue. Read it with |
Hi Nico, and thank you for Chorus. I use it every day for Slack, Teams and WhatsApp, and it has been lovely to watch it grow. This PR comes out of one afternoon of poking at it on my own machine. I have tried to set down everything we looked at, including the parts that ended with no code, in case any of it is useful to you. Please treat all of it as a suggestion. You know this code far better than I do, and I will gladly change or drop anything you would rather have done another way.
What this changes
Chat services never hibernate, which is right for notifications. But it also means nothing ever makes their pages let go of what their scripts build up, so a page that grows keeps growing for as long as Chorus runs.
With this PR, every five minutes Chorus looks at how much memory each service's page is using. If a page you are not looking at has grown past both 1.5 GB and twice the size it settled at, Chorus tears it down and loads it again straight away. Because it comes back at once, a chat service keeps posting notifications. The reload is skipped when the service:
The call probe is a suspension point, so
recycleIfStillOversizedreads every guard again after it, the same wayhibernateIfStillIdledoes. No schema change, no new setting.What I measured
On 2026-10-01, with Chorus 1.5.25 on macOS 27.0.1, one Chorus WebContent process went:
footprint -pput 1.5 GB of it inWebKit Malloc, plus 41 MB of WebAssembly memory. I could not prove which service it held, because the WebKit logs hide URLs in a release build. I am fairly sure it is WhatsApp Web: it is the WebAssembly user among my six services, and the Teams process had just been rebuilt. The WhatsApp app sat at about 460 MB on the same machine.I saw that
OPEN-ITEMS.mdreadswebcontent_mbas showing no trend. I think that reading is right about the sum. The sum swings by gigabytes as pages come and go, and that swing hides one page that keeps climbing. Read one process at a time, the climb shows. I have added a short note there rather than changing your analysis.How it works
WebViewPool.webContentFootprint(for:)gets the page's process id from_webProcessIdentifierand reads its physical footprint withproc_pid_rusage, which is the number Activity Monitor shows._webProcessIdentifieris SPI, so it is probed first, the wayDataStoreManagertreats its own SPI. If a future macOS drops it, nothing is ever recycled, which is how Chorus behaves today.AppState.startMemoryRecycleTimerruns the sweep. A recycled page goes throughteardownWebViewand then the normalpreloadpath, soonServicePreloadedrestarts its background polling as usual.Two questions worth answering
While I was building this, the person I was working with raised two good questions, and the second one changed the design.
Does the check itself cost anything? Very little. Each pass makes one
proc_pid_rusagecall per live page, every five minutes. The JavaScript call probe only runs for a page that has already crossed the limit.Could a page that just needs a lot of memory get reloaded in a loop? With a fixed 1.5 GB limit, yes. A page that needs 1.6 GB just to run would be rebuilt every ten minutes, forever. That is why the limit is relative to each page's own baseline. A fresh load settles near its baseline, so the rebuild cannot trip the rule again on its own. Only real growth can. The test pins this case ("big but steady stays"). It also caught a bug in my first version:
baseline × 2could overflow and wrap to a small number, which would have recycled the page. That case is now handled and tested.Testing
xcodebuild test(Debug, asCLAUDE.mdasks): 300 tests, 0 failures, 1 skipped, as before. One new test,testRecycleTakesOnlyAPageThatKeptGrowingAndNobodyIsUsing, covers the rule.Chorus-debug: five background services were recycled in one pass. Each old WebContent process exited, a new one came up and loaded back to its earlier size (84–938 MB), and the service on screen was left alone.What I have not tested: a signed-in WhatsApp over many hours on a release build, which is the case this exists for. The services in my Debug store were not signed in. I would not call it proven until it has run that way, and I would be glad to run it on my machine and report back if that helps. I could also only check macOS 27. I am counting on CI for 14 and 15.
Something unrelated that came up: Teams sign-in
This needs no code, but it might save you a report. Teams showed its "Precisamos que você entre novamente" ("we need you to sign in again") banner, and clicking Entrar (Sign in) did nothing. I checked whether my earlier SSO fix (#17) had stopped working on macOS 27. It has not: no tracking-prevention warnings in the log. A Debug build with a fresh data store signed in to Teams cleanly in one window:
teams.cloud.microsoft→login.microsoftonline.com→kmsi→ back with#code=.In the release app, the click did reach the page. It set off a run of same-site redirects that ended back on the same screen, and WebKit dropped clicks while they ran (
handleMouseEvent ... possibly stuck?). Deleting the Teams service and adding it again fixed it. So the stored data for that service had gone bad, most likely from the time before #17. I did not check whether "Clear session (log out)" on its own would have been enough.On the writing
CLAUDE.mdasks that public text pass the humanizer check. That tool was not available in my session, so the CHANGELOG line and the OPEN-ITEMS note have only been through Orwell's rules by hand. I am sorry about that. Please tell me if anything reads wrong and I will fix it.Thank you again for your time, and for Chorus.
🤖 Generated with Claude Code