Repository navigation
Keep SV-COMP runs within the competition's memory and CPU budget - #88
Closed
nils-loose wants to merge 10 commits into
Closed
nils-loose wants to merge 10 commits into
nils-loose wants to merge 10 commits into
Conversation
It is not imported anywhere (SVCompDriver replaced it) and still carried the old -Xmx32g heap and 60 s executor timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The explorer and all executor JVMs share SV-COMP's limits (15 GB, 900 s CPU time), but the executor got -Xmx32g, its JavaSMT Z3 context grows outside the heap (about as much as the heap), the explorer's Z3 can allocate gigabytes in one query, and nothing watched the CPU time. Runs ended as OUT OF MEMORY or TIMEOUT instead of with a verdict. A monitor thread (svcomp/ResourceBudget.py) now polls the memory and CPU time of the explorer and its descendants: - The limits default to the competition's (--memory-limit 15000 MB, --cpu-limit 900 s); a lower cgroup memory limit wins, and --cpu-used adds what the run used before the explorer started. - The executor heap is derived from the memory limit (5483 MiB for 15 GB) and Z3's own memory limit (memory_max_size) is set below it. - 2 GB below the memory limit the executor run in progress is killed with SIGKILL (no shutdown hook sends a trace cut short) and the exploration goes on with other branches; a Z3 query is interrupted. - 20 s before the CPU limit the exploration stops with UNKNOWN; a violation the stopped run printed is still reported. - If the main thread does not wind down, or the explorer alone is too large, the monitor reports UNKNOWN and exits. A run cut short (stopped by the budget, or an OutOfMemoryError in the executor) leaves its path unexplored, so it rules out SAFE like the other soundness downgrades. Its trace is not checked for divergence. stats.json gets a 'resources' section. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every round starts a new JVM that runs for one to a few seconds. On long runs (4 cores) the C2 compiler threads took 41-44% of the executor JVMs' CPU time (MinePump spec1-5_product11, TCAS_prop1, Ramp-and-Hold), work that such short runs never earn back; GC took 0.2%. With -XX:TieredStopAtLevel=1 the same 120 s of CPU time give 22-53% more rounds (44 -> 63, 59 -> 90, 49 -> 60). The serial GC or -Xint do not help (-Xint: 33 rounds). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
run_swat.py passes the CPU time already spent on copying and compiling the task (--cpu-used), since the limit covers the whole run; the memory and CPU limits are the explorer's defaults, which are the competition's. The local harness passes --testcase-timeout-s as the CPU limit, so the explorer stops with UNKNOWN before the harness kills it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While a killed executor exits, or an interrupted query returns, the next polls see the memory still over the threshold; they no longer count and log it as another stop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Z3's memory_max_size made Z3 abort the explorer (SIGABRT, no verdict) on float-nonlinear-calculation/Conflict, and an interrupt does not stop Z3 while it bit-blasts, during which it grew by 2 GB/s. Without the Z3 limit, a query that is still over the threshold 1 s after its interrupt, or is within 1 GB of the memory limit, now ends the run with UNKNOWN (Conflict: at 14.1 GB of 15 GB, before: killed at 15 GB). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under BenchExec the run's cgroup also holds run-swat.sh and run_swat.py, which kept running next to the explorer (34 s of CPU time in 900 s on MinePump spec1-5_product11), so the explorer's deadline came after BenchExec's limit. Where the memory limit comes from a cgroup, the CPU time is now at least that cgroup's (cpu.stat usage_usec). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The walk logged one INFO line per branch node in every round: 270 of the 320 MB of explorer.log in 900 s on MinePump spec1-5_product11. In a BenchExec container the log is kept in memory, and writing it cost CPU time. It is now at a TRACE level below DEBUG. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
run_swat.py collected the explorer's whole output, re-logged it line by line and then searched it for the verdict. After a 900 s run (300 MB of output) that took more than 20 s of CPU time, after the explorer had stopped in time, so BenchExec still ended the run as TIMEOUT; it also held the output in memory several times. The output now goes straight to stdout, and the verdict for printing the witness comes from the explorer's stats.json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The root logger printed several lines per branch and query to stdout: 500 MB in 900 s on MinePump spec1-5_product11, all of which run-swat.sh writes to out.log (in memory in a BenchExec container). In sv-comp mode the root logger is now at WARNING; explorer.log and the verdict lines are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nils-loose
added a commit
that referenced
this pull request
Oct 19, 2026
SVCompDriver.py: - one C1-only option (#82 and #88 both added it), in #88's EXECUTOR_JVM_OPTIONS, with #88's budget-derived heap; - determine_next_step drops a stack-overflow run's violations (#78) before #88's out-of-memory and resource-limit handling, and reads the Database without copying the tree (#82); - a run stopped by the budget is not checked for divergence; #72's no-trace check applies to the others; - every query, first or retry (#74), runs under the budget's interrupt; - #88's cut-short check joins #72's incomplete-search check, and both apply to every SAFE; - the stats keep the pre-check (#73), divergence retries (#74) and resources (#88). DFS.py: the per-node line is at #88's TRACE level, with the CHOICE label. TestcaseStats.py, test_solver_rlimit.py: both sides. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nils-loose
added a commit
that referenced
this pull request
Oct 19, 2026
Combining #72, #74, #78, #82 and #88: the driver tests create #88's budget and verdict lock, and #88's tests get the stack-overflow (#78) and copy-free (#82) Database reads and the choice flag (#76). A run reports one verdict (#88), so the incomplete-search subtests reset the flag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nils-loose
added a commit
that referenced
this pull request
Oct 19, 2026
SVCompDriver.py: one no_trace mechanism. run_testcase decides received_trace once (#89); a run without one is not checked for divergence and, after the output scan, ends UNKNOWN with a single no_trace error, which names the branch #72 used to record it for. A run stopped by the resource budget (#88) is handled before, as in #88. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
4 tasks
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.
Addresses #51: SV-COMP runs now stay inside the competition's limits (15 GB, 900 s CPU, 4 cores) and stop with UNKNOWN instead of being killed.
What changed and why
symbolic-explorer/svcomp/ResourceBudget.py): the explorer and every executor JVM share it. A monitor thread polls the RSS and CPU time of the explorer and its descendants every 0.2 s. Where the memory limit comes from a cgroup (BenchExec, systemd scope), the CPU time is at least the cgroup'scpu.stat.--memory-limit 15000(MB, i.e. BenchExec's 15e9 bytes) and--cpu-limit 900. A lower cgroupmemory.maxwins.--cpu-usedadds CPU time spent before the explorer started.-Xmx5483mfor 15 GB, replacing-Xmx32g. JavaSMT's Z3 context grows outside the heap at about the heap's rate: with-Xmx12gthe executor RSS reached 14-15 GB.memory_max_sizewas tried and dropped because it made Z3 abort the process.OutOfMemoryErrorin the executor) leaves its path unexplored. It is recorded asresource_limit/out_of_memoryand rules out SAFE, like the existing downgrades. Its (missing) trace is not checked for divergence.stats.jsongets aresourcessection.-XX:TieredStopAtLevel=1). Every round is a fresh JVM that runs 1-3 s. On 4 cores, C2 compiler threads took 41-44 % of the executor CPU and GC took 0.2 %. Same 120 s CPU: 22-53 % more rounds (MinePump 44→63, TCAS 59→90, Ramp-and-Hold 49→60). SerialGC and-Xintdid not help.run_swat.pypasses the CPU time used by copying and compiling the task.run_swat.pyno longer buffers and re-logs the explorer output. After 900 s that took over 20 s of CPU, so BenchExec still reported TIMEOUT, and it held hundreds of MB in memory. The output is now passed straight through, and the verdict for printing the witness is read fromstats.json.[DFS]line now logs below DEBUG (270 MB ofexplorer.log).svcomp test runpasses--testcase-timeout-sas--cpu-limit.svcomp/SVCompHandler.py(dead code with-Xmx32gand the 60 s timeout). The remaining 60 s defaults are in non-sv-comp drivers, or are overridden withNonein sv-comp mode.Measurements
Config:
--sv-comp, nosa, cgroupMemoryMax=15000000000, explorer--cpu-limit= harness limit. Base is the integration branch.runexec(BenchExec 3.35) of the package, 15e9 B / 900 s CPU / 960 s wallNew wrong verdicts: 0. Net points: 0. The gain is that 40 runs which used to be OOM/TIMEOUT/stall now give a clean UNKNOWN, and there is more exploration per CPU second. On the 900 s timeout sample, rounds went up 3-28 % (TCAS 363→464, HPack 265→322). Adjustable-Inner-Count-Halving finds its VIOLATION after 289 s instead of 379 s, which would be +1 under a 300 s limit.
The quick "oom" runs (4-6 s at 190 MB): these are not target OOMs.
/usr/bin/timeonly counted the explorer, because the killed JVM was never waited for. The executor actually ran for about 150 s: the solver picked a nondet loop bound of 2^30 (BinaryTreeSearch-*, SortedListInsert, SatFibonacci, DistinguishedNameParser, InfiniteLoop), and the trace grew until the cgroup limit, about half of it Z3 native memory. They now end UNKNOWN. A targetOutOfMemoryError(new int[2^31-1]in InsertionSort/MergeSort/Tsp) is not a property violation: it is neither a RuntimeException nor an assert. These runs already ended UNKNOWN, and now they are explicitly recorded asout_of_memory.CPU vs wall (4 cores): base 1.17-1.6 CPU-s per wall-s on long runs, C1-only 1.02-1.16. So the 900 s CPU limit is reached after 760-860 s of wall time, before the 960 s hard wall limit.
Tests
tests/test_resource_budget.py(23 cases):The existing explorer unit tests pass.
Known limits
runexeccheck ran without cpuset delegation (cores pinned with taskset) and on a hand-assembled package directory, not a built archive. A full BenchExec campaign was not run.Maingives "No symbolic vars" and therefore SAFE.🤖 Generated with Claude Code