fix(next): do not start Chromium under an open-file limit it would run out of

Draft
LA
5d ago
+341-18

Summary

Under a hard RLIMIT_NOFILE below 1024 (a launch from a shell after ulimit -n 256, which sets both limits), the app cannot raise its open-file limit, and Chromium aborted the whole app about 6 s after launch. Its child launch treats a failed pipe() as fatal: sandbox/mac/seatbelt_exec.cc:86-87, PFatal("SeatbeltExecClient: pipe failed"), EMFILE.

Found by the restore/chaos fuzz (fuzz catalog row h, finding h1): run h2-fd seed 2012, cmux21, pid 23845, 2026-10-31 10:15:04Z, feat d1f0e63691a0 Debug. Symbolicated with the fork's unstripped engine cef-154.0.28-cmux.28-macos-arm64-debug.tar.xz (the engine UUID matches the crash report). The crashing thread: ChildProcessLauncherHelper::BeforeLaunchOnLauncherThread -> sandbox::SeatbeltExecClient::SeatbeltExecClient() -> sandbox::logging::PFatal -> abort_report_np. The app's stderr shows FILE_ERROR_TOO_MANY_OPENED in the same second. This is not memory pressure: the hog had ended 47 s earlier, and with the 15 GB hog the same build relaunched 82 times with 0 deaths.

Change

  • CEFRuntime.loadLibrary refuses to start Chromium, before the framework is mapped, when the soft limit after the app's own raise is below OpenFileLimit.chromiumFloor (1024). So no Chromium thread runs.
  • The failure is typed (CEFUnavailableReason.openFileLimit). Chromium tabs wait in place (WebKit would open them signed out) with a localized notice that names the limit and the way out (start from Finder or the Dock, or raise ulimit -n). An explicit Chromium request gets the same text.
  • Agent chats in Chromium (agent.chromium): ChromiumPageHost.isAvailable now reads the refused start, so new chats open in WebKit. A chat that asked before the refusal reopens in WebKit at once.
  • It is logged once, and the start is not tried again. No retry, no relaunch loop, no catch of the abort. Terminals, the daemon and WebKit pages do not change.
  • A launch from Finder, the Dock or open always raises to at least 10240, so only a lowered hard limit is refused.
  • The floor value is a judgement for now. A measurement on a mini (hard limits 512, 1024, 2048 with the restored session and 5 Chromium tabs) is queued; the value and its numbers go into the code comment when it is done.

Tests (red first)

  • Red 3f4f7507fa3: OpenFileLimitTests (aHardLimitOf256KeepsChromiumFromStarting and 3 more), CEFOpenFileFloorTests.aHardLimitOf256RefusesTheLoad.
  • At the head also: CEFOpenFileFloorTests.theRefusalNamesTheLimitAndKeepsTabsOutOfWebKit, aRefusedStartIsUnavailableWithItsReason, PageWebViewChromiumTests.anAgentChatInChromiumFallsBackToWebKitWhenChromiumIsRefused.

Observed / Not run

  • Observed: fleet compile-check e41127457b18b29d053c48f0 at 632d52ad8e2: CmuxNext, app and cmux-tui ok. prepush-gate.py: no new violations, app_lines +0. localization_catalog.py check: no finding for the new key (21 locales).
  • Not run: the red and green Mac test runs. proof-step.py refuses Mac steps for this branch until it is on the Mac lane allow list (faster-2.md item 4); I asked the lead.
  • Not run: a real launch under ulimit -n 256 with the fix (part of the queued floor measurement).

No capture: the notice shows only under a hard open-file limit below 1024; a real capture comes with the floor measurement.

Changelog

Fixed: cmux no longer quits when it starts with a very low open-file limit; web pages show a notice that names the limit instead.

perf claim: none


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Fixes the app aborting about 6 seconds after launch when started under a hard open-file limit below 1024 — Chromium treated a failed pipe() on its child-launch path as fatal. The app now refuses to start Chromium before its framework is loaded when the limit is too low, and shows a notice instead of crashing.

  • Refusal is typed (openFileLimit): Chromium tabs wait in place with a localized notice that names the limit and the way out, instead of opening signed out in WebKit.
  • Agent chats in Chromium open in WebKit when Chromium is refused; a chat already asking reopens in WebKit at once.
  • A refused start is logged once and never retried.
  • The 1024 floor is backed by a cmux21 measurement: the app process peaked at about 270 open descriptors, and hard limits of 512, 1024, and 2048 produced no aborts.