Not stable yet. WolOS is in active development and the current ISO is a test build: try it in a virtual machine or on a spare disk, not on a computer you depend on.

WolOS
Manual contents

Decisions

0027: One app cannot take the desktop

Per-app limits keep the session responsive when one app runs away.

Status: accepted

Context

a fork bomb, a memory bomb or a full file table from a normal user must not freeze the desktop. Before this, app scopes had only systemd's default task limit, no memory limit, and the system-wide open file count was lower than one process's hard limit.

Decision

  • Defaults, no kernel patch: measurement did not ask for one.
  • Every app window's scope (Memory Saver's adopt_args) gets TasksMax=8192, 80% of RAM and 25% of swap. user@.service shares one such set. A user's slice holds 16384 tasks.
  • The session scope gets CPUWeight=1000 and 256 MB of memory.min, passed down through its slices. system.slice keeps 128 MB, so login and Force Quit work under pressure.
  • Fork storms die on their own: wold reads pids.events each tick and kills a scope refused at least 2000 forks a tick, two ticks running (memsaver/storm.rs). A program that backs off is never touched.
  • An app with 4 OOM kills within 5 ticks is killed too: one lost tab is not a loop. When the user's slice refuses forks like a storm at 95% of its tasks, with no app at its own limit, the app with the most tasks goes (memsaver/storm.rs, engine.rs). Unit tested, not yet run in a guest.
  • fs.file-max goes back to systemd's value. Open files are charged to the app's memory, so the limit is memory, not a count everyone shares.
  • /tmp and /dev/shm hold 25% of RAM each. Their pages outlive the app that wrote them.

Measured in a throwaway guest (8 vCPU, 8 GB, WolKernel, btrfs, zram; image/vm/test/limits). Stand-in compositor at 120 Hz in the session scope, the bomb in a window scope, 20 s then killed:

AttackBeforeAfter
Fork bombruns until killed by handdead in 7.1 s, no failed spawns, 5 ms worst input latency
Thread bombruns until killed by handdead in 6.0 s
Memory bomb95 s outage, spawn median 3.7 s, root login up to 13 sspawn median 3.5 ms, root login 0.9 s at worst
Pipe fillguest wedged in 1 of 2 runs18 of 18 spawns, root 16 of 16
File table19 of 20 spawns fail, root 1 of 3no failures, root 18 of 18
PID, inotify, tmpfs, disk fillno desktop harmno desktop harm

Recovery after the kill took 1.0 s or less in every run after the change. make -j16 beside a 4000-task browser-like tree: 4.91 s before, 5.00 s after, one run each, nothing killed.

Limits: fork() from unrelated scopes returned EAGAIN with 16,000 tasks in one scope on the 8 GB guest, cause not found; 8192 stays under it. The table was measured before the OOM loop and full-slice kills existed. A full disk or a full tmpfs stays full until something deletes the files. inotify limits stay per user.

Consequences

an app that needs more than 8192 tasks or 80% of RAM cannot get it on WolOS; the limits are systemd properties, so a future change is a drop-in and no code. fs.file-max and the tmpfs sizes differ from Arch's defaults (the drop-ins under files/ are the record). The kill rules in memsaver/storm.rs have unit tests only until they run in a guest (see Limits).

Reference synced from wonderlandos/wolos docs/adr/0027-one-app-cannot-take-the-desktop.md.