Repository navigation
feat(obc-data): each step runs in a process of its own - #2743
Merged
Merged
Conversation
A Rust step runs as `<binary> obc_data_step <function>`, with its request as JSON on standard input, as a command step does. Each binary registers its step functions by a stable name with `obc_data::engine::child`; a test binary does so in its test `obc_data_step`. So the engine stays small, and the receipt of each step has the peak and the CPU time of that step. - Delete the in-process path: `in_process`, the CHILDREN and STOP thread-locals, `check_stop`, `run_child` and `process_peak_rss_bytes`. - Each step leads a process group of its own. When the run stops, the group of each step that runs gets SIGTERM. A step starts with fork, so its peak starts at the current resident set of the engine and not at its peak. - A dead run keeps its lock while a process of its session runs: the programs of a step are no longer in the process group of the run. - The step with the longest chain of planned steps starts first. A step without an estimated peak reserves its input bytes times the largest peak per input byte of its layer; a layer without one runs its first step alone. A step that does not fit holds back the steps after it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…it steps Review fixes: - obc-data enables serde_json `float_roundtrip`: the child of a Rust step parsed some 17-digit floats of its options 1 ULP off its key. - A receipt has `own_process`. An estimate reads a peak only from such a receipt, never from the cost in a release, so the high-water marks of the engine refuse no update. - A step without a peak reserves the smallest measured peak of its layer, or its input bytes times the per-byte peak of the measured step that read the most. A reservation is at most the memory. - On Linux a step gets SIGKILL when the engine ends. Off Linux no run takes over a lock: no session can be listed there. - `ps` shows `obc-data obc_data_step <function>`; the wait polls each 10 ms; a test checks that FUNCTIONS names each static once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…f a step Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Between fork and exec another thread can hold the malloc lock, so the error of a dead engine is ESRCH, not a formatted message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Each step of
obc dataruns in a process of its own. The engine stays small, and each receipt holds the peak memory and the CPU time of its own step. The scheduler then admits steps by these peaks, the longest chain first.A process per step
<binary> obc_data_step <function>(on Linux/proc/self/exe, so a rebuild during a run does not change the code), with the request as JSON on standard input, as for a command step. Each binary registers its step functions at the start ofmainwithobc_data::engine::child(FUNCTIONS):obc datawithhost/obc-data-steps/src/functions.rs,obc-data-plumbingwithobc-data/pass. A function name is<crate>/<function>.obc_data_step, that registers its test functions. libtest readsobc_data_step <function>as test names, so the child runs that test alone. A name with/matches no test, soorderrefuses a function name without/.in_process, theCHILDRENandSTOPthread-locals,check_stop(with its calls in obc-pack),run_child(obc-osm waits for Osmium itself, andwait4of the step counts it),process_peak_rss_bytesof the review, and the spec caveats "exact only while no other step runs" and "an upper bound".pre_execsets the group, soCommandforks. Linux starts the peak of a vfork child at the peak of the engine, and that of a fork child at its current resident set./proc. Other systems cannot list a session, so they take no lock over:unlockfrees it after 10 minutes.TMPDIR(fix(obc-data): a step program writes its temporary files on the disk of the store #2732) now applies to Rust steps too.float_roundtrip: the child parses the floats of its options as the key reads them (the default parser was 1 ULP off for about 12 % of 17-digit coordinates). The feature unifies into every crate of a build that links obc-data, so those parse JSON floats exactly, as Python does. No baked bytes change:*/gridindex.jsonfiles (16,778 of 45,811 floats) parse otherwise, and only Python steps read their contents (planner/index, planner/sun, overlays); the catalog pointer reads their digests.boundsfromrelease.jsonintocatalog.json. These bounds are microdegrees divided by 1e6, so short decimals, which both parsers read exactly.the_same_inputs_bake_the_same_bytes_foreverand the lifted plane) and the semantic geometry digests of obc-draw pass. They pin device bytes, but no JSON floats reach them, so they are weak evidence here. The planner outputs have no digest pin:planner_buildchecks their structure.PR_SET_PDEATHSIGSIGKILL, so a killed run leaves no step behind (the programs that a step started can stay; the session check below keeps the lock).psshows a Rust step asobc-data obc_data_step <function>(arg0).Admission (the owner chose rule A, scaled by input size)
wall_msplus the longest chain of the steps that read it. A step without an estimate counts as the longest. I rank by the chain, not by the step's own time, because the chain keeps the order a dependency order: the scan of ready steps relies on that.own_process(absent =false). An estimate reads a peak only from a receipt with it; the cost in a release never gives one, as a release does not tell whose peak it is. So the in-process high-water marks of old receipts and of a release baked on a bigger machine refuse no update: their steps count as without a peak.Notes
ru_maxrssis the largest process of a step, not the sum. A step with worker processes, such as the snow bake after perf(planner): the snow bake runs its chunks on every core and keeps its tiles on disk #2734, reports its largest worker. A cgroupmemory.peakper step counts them all. That is not in this PR: it needs a delegated cgroup per step (a systemd user scope), andmemory.peakalso counts page cache, so a step that reads a 6 GB extract would look larger than it is. It is a follow-up.memory.peakper step.obc-draw(LAND_INDEX) is now built once per draw-cells process instead of once per engine. Measured on timosPC: the shapefile has 848,110 records; a warm read of the 1.3 GB file takes 0.15 s (the first read 1.4 s), and a scan with the same read pattern 0.5 s in Python. Against minutes per leaf this is not material, so there is no cross-process cache.Checks
OBC_UPDATE_DATA_SPEC=1 obc test -p obc-data: 202 passed. New: a receipt has the peak of its own step; a Rust step gets the floats of its options exactly (10,000 17-digit coordinates); a peak comes only from a receipt withown_process; a stop ends a step with the programs that it started; the memory budget with a scaled reservation; the first step of a layer without a peak runs alone; the longest step starts first and holds back the rest; FUNCTIONS has unique names and lists every static.host/andplanner/from this branch (the worktrees share one target directory),obc test -ppassed for obc-data-steps (37), obc-osm (3), obc-pack (95), obc-draw (102), obc-network (30), obc-dem (48), obc-search-bake (16), planner-router-build (65) and obc-bake (187). These hold the golden tests of the Rust steps: exact float parsing changed none of their outputs.planner_buildneedsbuilder/web/node_modules; the worktree had none, so I linked the one of the main checkout for the run.obc test fixtures -p obc-data-steps:cells_fixture,terrain_fixtureandplanner_buildpassed, also after the merge of develop with fix(deps): zip 9 for RUSTSEC-2026-0367, -0368 and -0369 #2745.cargo clippy -p <crate> --all-targets -- -D warningsfor obc-data, obc-data-steps, obc-osm and obc-pack: clean.cargo fmt --all -- --check,obc suites check,obc prose --checkanddocs/build_docs.py --check-links: clean.process_peak_rss_bytes), and no bake (shared PC).Public docs changed:
specs/obc-data.md(step contract, Receipt, Runs, locks, Review) andhost/obc-data/README.md, in their owndocs:commit. The generated JSON schema stays with the code.Requirements: none (internal bake tooling; I could not read the verification console, as this machine has no agent token).
🤖 Generated with Claude Code