Experiment formal browser controller scope proposal 01 - kmosoti/FabricO11y GitHub Wiki

Browser whole-fixture controller: prospective reaping design01

NOT RUN. This requests static review of source/native registration scope before additional trust controls or a production correction. The actual13-method source red remains archived separately at90cdf680; current kernel behavior is unverified. Existing whole-fixture controller is the smallest observed wait domain; no new launcher is proposed. Prospective cleanup ceiling+190 live_browser_check; cgroups/enter_group and all clock files remain unchanged on this card. Later actual server ELF/clock adapter is separate.

browser-adopted-child-reaping-design-01.md

SHA-256 fc0a97080b24cb37c36c4fc9b1df9c7399389e7cf149a43b444fa69e40d1cf8f.

Prospective browser descendant ownership and reaping

Status: read-only design, not executed or admitted production. Browser source remains clean 90cdf680d23769acb8fc20fda9cf15818d6549a2; its 13 registered controls are unchanged. Olo is the sole executor. This note answers the later-fork limitation explicitly recorded in docs/experiments/formal/console-session-clock-protocol.md:408 and C-098/C-100 on #59.

Observed local source and configuration

The workstation reports kernel 7.2.8-200.fc44.x86_64. Direct read of /boot/config-7.2.8-200.fc44.x86_64 finds CONFIG_PROC_CHILDREN=y (10987), CONFIG_CGROUP_FREEZER=y (244), and CONFIG_CHECKPOINT_RESTORE=y (263). Config SHA-256: de2947fbecd9addbf0612ba44a963e082bb34a35a552184b10a00e62690756f8. Installed UAPI include/uapi/linux/wait.h:10-20 defines WNOWAIT, __WALL, P_ALL and P_PIDFD. These are static availability observations; actual syscall permissions, zombie membership, adoption and freeze/kill behavior here are NOT RUN. Full workstation kernel implementation was not inspected; the upstream v6.17 source below is supporting evidence, not an exact host-source claim.

Existing tools/bench/labs/completion/cgroups.py:33-57 creates a supervisor, exclusive subgroup and pre-exec placement check; enter_group.py:3-4 enters before exec. Retain the service-parent aggregate identity/cap, not the supervisor as a substitute. live_browser_check.py:385-393 currently launches session leaders without this binding. Its finalizer at 1238-1254 checks leader liveness and removes scratch; the new registration targets those gaps. tools/qualification/runner.py:185-202 uses numeric waitpid and treats ECHILD as either reaped or not adopted; it cannot supply the proposed exact reaping proof unchanged.

Documented behavior and limits

Exited tasks retain their last cgroup until reaping, but zombies are absent from cgroup.procs. /proc/PID/cgroup records that association, adding a deleted suffix if the group disappears. Populated=0 describes absence of live processes. Paths are rendered relative to the reader's cgroup namespace. Kernel cgroup v2 documentation.

The task children file requires CONFIG_PROC_CHILDREN. Exit races can omit even nonexiting siblings; an empty read is not a completeness proof. Scan every controller TID when using it for discovery. proc_tid_children(5).

In upstream v6.17 exit.c, cgroup_exit precedes exit_notify (907-919); exit_notify reparents before publishing zombie status (696-735). Therefore populated=0 may precede adoption completion. The wait implementation (1554-1595) scans the waiting process's threads under tasklist_lock; __WALL includes clone children (1062-1084). WNOWAIT observes a waitable exit without consuming it (1092-1110). This supports the conditional strategy below; it is not host execution evidence.

A dying task's namespace handle can be unavailable: v6.17 namespace.c:110-123. Preserve and revalidate the reader's cgroup/PID/mount namespace and held cgroup mount/root identities; do not require a freshly readable zombie namespace symlink. Independently validate each last membership against the retained group path/device/inode. Reject deleted/replaced groups before signaling or reaping.

Small proposed production strategy

Keep the two registered functions; extend their internal stop/drain behavior, without a new registry framework. Require a dedicated controller subreaper, no foreign launches/ptracing or concurrent waiter, and ordinary SIGCHLD disposition without SA_NOCLDWAIT. Save/restore subreaper state. Subreaper contract; pidfd_open zombie identity conditions.

Preserve TERM grace. At escalation, if separately admitted, freeze each still-live owned subtree, await actual frozen=1, collect live pidfd/start identities, then use its held cgroup.kill capability. Freeze/snapshot/kill must fit the existing five-second phase and global deadline. Attempt every other owned group on any refusal. Freezing addresses live fork races, not pre-existing zombies.

Discover direct/adopted children; for each candidate hold a pidfd, bracket /proc/PID/stat identity reads, require controller parentage and the exact held owned cgroup membership in the stable reader view. Later forks and setsid then remain bindable after adoption. Never authorize a signal from a numeric PID, matching UID or process-group number alone. Retain originals and late identities until exact P_PIDFD reaping and original proc identity absence are recorded.

Children-list polling alone remains insufficient. A separate prospective interface is non-consuming waitid(P_ALL, 0, WEXITED|WNOHANG|WNOWAIT|__WALL) as an own-child observation. Bind any returned candidate before consuming ONLY its P_PIDFD; no-event means pending, not empty. ECHILD may serve as the final own-child emptiness witness only under the dedicated-controller/clone/thread assumptions above, after all owned groups are kernel-empty and known identities reaped. Foreign candidates are neither consumed nor signaled. This additional observation requires explicit trust registration/ACK; no broad consuming waitpid(-1) is proposed. waitid semantics.

Logical deduction: exact consumption plus kernel own-child emptiness avoids the documented children-file omission and intermediate-parent adoption gap under those assumptions. Native completeness, namespace rendering and exit-order applicability on kernel7.2 remain unresolved until contained literal controls run. Unsupported observation/ownership or timeout preserves failure and scratch.

Prospective independent controls and bounds

Register: late fast double-fork/setsid after binding; an already-zombie child excluded from procs; pop0 preceding adopted-child visibility; children launched by another controller thread; same-UID foreign child untouched; wrong start/pidfd, replaced/deleted group and reader namespace drift; missing/stuck freeze; nonconsuming no-event versus ECHILD; clone-child inclusion; conflicting SIGCHLD/concurrent-reaper refusal. Preserve every-group attempts and exact proc-absence assertions. Model controls must be labeled separately from actual delegated kernel controls.

Keep 15/5/2 local bounds, the single 240-second deadline, outer1800, aggregate4GB/3.5GB high/no-swap/CPU200%/tasks512, sampler join, original session oracle and all existing outcomes. Bound collection by the admitted task ceiling and archive bounded identity/namespace/stop/reap evidence before any failure. No empty-poll heuristic, unconfirmed cleanup or phase overrun can authorize scratch removal or a native acceptance claim.

browser-controller-scope-proposal-01.md

SHA-256 14e4e9be6b9bc747976c6f2a5e4c26538745edc58918593ff8a977b82fc83af6.

Prospective browser controller scope

Read-only proposal at clean 90cdf680d23769acb8fc20fda9cf15818d6549a2. No source edits, imports, tests or native processes. The 13 registered controls and their published red remain unchanged. This refines browser-adopted-child-reaping-design-01.md: a dedicated wait domain need not be a new process.

Actual boundary and launch inventory

The admitted AB→C transition stops every AB group, including AB's bound server, before a distinct C server starts with fresh state (console-session-clock-protocol.md:220-251; session_clock_oracle.py:199,214). Keeping that target server alive would contradict the contract; Olo corrected the earlier wording.

The current live_browser_check.py launches server at466, WebDriver at507/509, edge at681 and canary at702 through its local launch function385-393. All belong to the fixture. Recovery/server replacements1120/1153 belong to separate lifecycle modes. Openssl456-458 and certutil504 are synchronous subprocess.run calls. The sampler185-205 reads files and creates no children; browser_producer contains no process launch. No legitimate persistent outside child domain was found at the whole-fixture boundary. This is source inspection, not an actual process census.

Smallest viable choice

Use the existing live_browser_check process as the dedicated controller for the entire expiry-only cell. Before its first target launch, verify the own-child domain is empty and install/verify subreaper ownership. Prohibit new setup commands or launches while draining AB. Stop all admitted AB groups, collect/reap exact adopted identities and establish kernel own-child emptiness before creating C. Keep the sampler alive only as a child-free observer. The same controller, deadline and pacer survive the transition. No extra launcher, IPC protocol or generic registry is needed for the observed topology.

Retain the two registered binder/stopper functions. P_ALL with WNOWAIT is only a non-consuming own-child observation; every consumed exit is P_PIDFD after ownership validation. Include __WALL for clone children and all controller threads. Live/pending child means no-event, never successful emptiness. Unexpected child, namespace drift, incompatible SIGCHLD flags or uncertain ownership fails and retains evidence while other admitted groups receive stop attempts. Read actual sigaction flags; Python get signal handler alone cannot exclude SA_NOCLDWAIT. One thread owns all waits; the sampler must not poll/wait Popen objects. Existing healthy/driver_ready polls470/511 and recovery waits1116/1146 must be accounted for: a leader already consumed by its exact retained Popen is not an unbound replacement, and original pidfd/proc-absence evidence remains necessary.

Alternative Assessment
Existing whole-cell controller Preferred: every persistent child belongs to the fixture and must stop at AB's boundary; preserves existing real Popen/server PID relationships.
Narrow child launcher supervising only WebDriver Needed only if future admitted code genuinely retains a foreign child domain. It adds a controller Popen distinct from the real driver, a bounded readiness/stop channel, independent actual driver/browser identity collection and double-level failure cleanup. Controller must stay outside its kill subgroup. Estimated additional60-90 net lines beyond the preferred repair; no present consumer justifies it.
Dedicated launcher thread plus __WNOTHREAD Reject as a completeness mechanism: it excludes other threads' children, while subreaper adoption can select another living thread. It also cannot prevent another thread consuming a Popen exit.

The last comparison follows wait flags and illustrative v6.17 exit.c:555-628,1554-1587. Exact workstation adoption/wait behavior remains untested. The prior note records the matching installed CONFIG_PROC_CHILDREN and the children-file exit-race limitation.

Preserve real ELF and clock evidence

Current binary snapshots are checked before launch398-410 and again269-273. session_clock.process_binding:43-59 independently validates actual /proc/PID/exe, frozen device/inode/hash, helper maps, start ticks and libc. Use the actual post-exec server PID, never a launcher identity or synthesized row. SessionClock currently launches only its Rust interactive probe79-90; production-server argv support is a later separately registered adapter change.

There is a concrete preload trap: SessionClock82-88 sets LD_PRELOAD/FABRIC_CLOCK_FD in Popen env, and session_clock.c:42-56 consumes its first frame then removes both variables. Sending that environment to the Python enter_group wrapper would intercept the wrapper and lose the server preload. A narrow route through unchanged primitives is python ENTER_GROUP GROUP /usr/bin/env LD_PRELOAD=HELPER FABRIC_CLOCK_FD=FD SERVER serve CONFIG, with a clean initial environment and explicit pass_fds. Pin env/helper/entry-script images, preserve the final target PID across exec, and bind only after the actual server ELF/maps match. Do not use preexec_fn after sampler threads exist.

Firefox trust/profile behavior remains499-505,524-531 with acceptInsecureCerts=false. Current532-535 checks browser version but does not bind the actual browser ELF. A prospective Firefox path uses its reported moz:processID solely as a candidate, then validates retained pidfd/start, owned subgroup and the independently admitted browser ELF against /proc/exe/maps; the driver's PID is not the browser's PID. Mozilla's capability implementation supplies that field. Installed-version behavior still needs a native control. Browser/controller must remain helper-free. The native unshifted same-call monotonic witness and request join remain separate unresolved prerequisites; this cleanup proposal does not supply them.

Per-file ceilings and proposed controls

These are prospective review ceilings, not permission to implement:

File Net production ceiling / scope
tools/ui/live_browser_check.py +190: binder50, bounded exact drain80, launch/finalizer integration40, controller ownership/evidence20. Measure readable code before exceeding.
tools/bench/labs/completion/{cgroups,enter_group}.py 0; reuse unchanged.
tools/ui/session_clock.py 0 for cleanup; later packaged-argv/channel adapter ≤+50 under separate scope.
tools/ui/session_clock{.c,_probe.rs,_oracle.py} 0 on this card; timing/native/oracle changes remain held.

Extend existing test_role_matrix fixture/tables: own-child no-event versus ECHILD; late double-fork/setsid and already-zombie adoption; same-UID foreign direct child untouched; stale start/pidfd or replaced group; SIGCHLD/autoreap/concurrent-waiter refusal; all-group attempts despite a refusal. Preserve all existing 13 methods/outcomes. Controls must demonstrate non-consuming discovery and exact-PIDFD consumption, not mock population alone.

Separate native cells must exercise real delegated placement/kill, a fast late fork and pre-existing zombie, thread-launched children, foreign-child noninterference, actual ELF/helper maps and the wrapper preload refusal/positive. Failure fixtures remain retained. No native admission follows from source mocks. All phases retain15/5/2 bounds within one global240s; no reset atC, no static completion guarantee, outer1800 and existing aggregate/storage limits unchanged.