Skip to content

Set the named Playwright executor limit from VM memory - #479

Open
rgarcia wants to merge 1 commit into
mainfrom
hypeship/playwright-executor-limit-from-memory
Open

rgarcia wants to merge 1 commit into
mainfrom
hypeship/playwright-executor-limit-from-memory

Conversation

@rgarcia

@rgarcia rgarcia commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Named Playwright executors were capped at a fixed 8 per browser. Each one runs its own daemon process (90-130 MiB RSS) and owns a tab, so on a ~975 MiB headless VM the 4th executor already failed setup with 500s (open tab: Target.createTarget: context deadline exceeded, not ready within 5s). MemAvailable then dropped to ~20 MiB and calls on the default executor started failing with EOF or page crashes. The 409 limit was never reached; the VM ran out of memory first. Headful and GPU VMs (6-8 GiB) handle all 8.

The limit is now set from memory when the API starts:

limit = clamp((memory - 512 MiB) / 192 MiB, 1, 8)
  • memory is MemTotal from /proc/meminfo, lowered to the cgroup v2 memory.max when one is set, so a docker run --memory container gets the same treatment as a VM.
  • The 512 MiB reserve covers Chromium, the default executor and the API. A headless VM boots with about 367 MB available out of about 975 MiB.
  • The 192 MiB budget covers one executor's daemon plus its tab, with room for a real page.
  • If memory can't be read, the limit stays at 8 (the previous behavior) and a warning is logged. The chosen limit is logged at startup.
VM memory Named executor limit
≤ 704 MiB 1
~975 MiB (headless) 2
1 GiB 2
1.5 GiB 5
≥ 2 GiB (headful, GPU) 8

The 409 response shape is unchanged: message reports the limit actually in effect (named executor limit (2) reached; ...) and executors lists them. The /playwright/execute description in openapi.yaml now describes the memory-based limit, and oapi.go is regenerated (only the embedded spec changed).

Testing

  • go vet ./... and go test -race over every non-e2e package pass.
  • New unit tests:
    • TestPlaywrightExecutorLimitFromMemory: the formula at each boundary.
    • TestVMMemoryBytes: MemTotal parsing, a max cgroup, a cgroup below or above MemTotal, a missing cgroup file, a missing MemTotal.
    • TestPlaywrightExecutorLimitBelowMax: a limit of 2 returns the limit error with (2) on the 3rd executor, and default still works.
  • Built the headless image locally and ran it two ways:
    • --memory=1g: logged memory_bytes=1073741824 limit=2. Executors a and b returned 200; c returned 409 with named executor limit (2) reached; default still returned 200. After DELETE /playwright/executors/a, c returned 200.
    • No memory cap (about 10 GB host): logged limit=8. e1-e8 returned 200 and e9 returned 409 with named executor limit (8) reached.

Not tested: a real ~975 MiB unikernel VM. The container check covers the cgroup path; the /proc/meminfo path is covered by unit tests.

Follow-ups outside this repo

The public API and its clients still say "at most 8 named executors". That reads wrong on headless browsers after this ships: kernel/kernel packages/api/openapi.yaml (from which the Node, Python and Go SDK docstrings are generated), the docs page browsers/playwright-execution.mdx and its changelog entry, the CLI browsers playwright help text, and the MCP server's playwright tool description. The public API forwards the in-VM 409 message, so the actual limit reaches callers unchanged.

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