Skip to content

Commit 1e4c7d6

Browse files
hanno-beckermkannwischer
authored andcommitted
CBMC: Allow specification of per-proof timeouts
This commit adds support for per-proof timeouts in CBMC proofs to prevent CI from running for hours on failing or slow proofs. The implementation leverages Litani's existing timeout mechanism via the CBMC_TIMEOUT environment variable. - Add `--per-proof-timeout` argument to `run-cbmc-proofs.py` (default: 1800s) - Pass timeout to Litani via `CBMC_TIMEOUT` environment variable - Modify `lib/summarize.py` to detect `timeout_reached` flag and display "timeout" instead. - Add `--per-proof-timeout` argument to `tests cbmc` command - Set explicit 30-minute timeout in CI workflow Fixes #733 Signed-off-by: Hanno Becker <beckphan@amazon.co.uk>
1 parent ca53148 commit 1e4c7d6

File tree

4 files changed

+61
-9
lines changed

4 files changed

+61
-9
lines changed

.github/actions/cbmc/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,5 @@ runs:
5454
shell: ${{ env.SHELL }}
5555
run: |
5656
echo "::group::cbmc_${{ inputs.mldsa_parameter_set }}"
57-
tests cbmc --mldsa_parameter_set ${{ inputs.mldsa_parameter_set }};
57+
tests cbmc --mldsa_parameter_set ${{ inputs.mldsa_parameter_set }} --per-proof-timeout 1800;
5858
echo "::endgroup::"

proofs/cbmc/lib/summarize.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,17 +90,31 @@ def _get_status_and_proof_summaries(run_dict):
9090
for proof_pipeline in run_dict["pipelines"]:
9191
if proof_pipeline["name"] == "print_tool_versions":
9292
continue
93-
status_pretty_name = proof_pipeline["status"].title().replace("_", " ")
94-
try:
95-
count_statuses[status_pretty_name] += 1
96-
except KeyError:
97-
count_statuses[status_pretty_name] = 1
93+
94+
# Check if any job timed out
95+
has_timeout = False
9896
duration = 0
9997
for stage in proof_pipeline["ci_stages"]:
10098
for job in stage["jobs"]:
99+
if job.get("timeout_reached", False):
100+
has_timeout = True
101101
if "duration" in job.keys():
102102
duration += int(job["duration"])
103-
proofs.append([proof_pipeline["name"], status_pretty_name, str(duration)])
103+
104+
# Determine status display
105+
if has_timeout:
106+
status_pretty_name = "Timeout"
107+
duration_str = "--"
108+
else:
109+
status_pretty_name = proof_pipeline["status"].title().replace("_", " ")
110+
duration_str = str(duration)
111+
112+
try:
113+
count_statuses[status_pretty_name] += 1
114+
except KeyError:
115+
count_statuses[status_pretty_name] = 1
116+
117+
proofs.append([proof_pipeline["name"], status_pretty_name, duration_str])
104118
statuses = [["Status", "Count"]]
105119
for status, count in count_statuses.items():
106120
statuses.append([status, str(count)])
@@ -134,8 +148,15 @@ def print_proof_results(out_file):
134148
"Click the 'Summary' button to view a Markdown table "
135149
"summarizing all proof results"
136150
)
137-
if run_dict["status"] != "success":
151+
152+
# Check for timeouts by examining status table
153+
has_timeout = any(row[0] == "Timeout" for row in status_table[1:])
154+
has_failure = run_dict["status"] != "success"
155+
156+
if has_timeout or has_failure:
138157
logging.error("Not all proofs passed.")
158+
if has_timeout:
159+
logging.error("Some proofs timed out.")
139160
logging.error(msg)
140161
sys.exit(1)
141162
logging.info(msg)

proofs/cbmc/run-cbmc-proofs.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,13 @@ def get_args():
181181
"action": "store_true",
182182
"help": "do property checking without coverage checking",
183183
},
184+
{
185+
"flags": ["--per-proof-timeout"],
186+
"type": int,
187+
"metavar": "SECONDS",
188+
"default": 1800,
189+
"help": "timeout for each individual proof in seconds (default: 1800)",
190+
},
184191
]:
185192
flags = arg.pop("flags")
186193
pars.add_argument(*flags, **arg)
@@ -342,6 +349,7 @@ async def configure_proof_dirs( # pylint: disable=too-many-arguments
342349
enable_memory_profiling,
343350
report_target,
344351
debug,
352+
timeout,
345353
):
346354
while True:
347355
print_counter(counter)
@@ -352,6 +360,10 @@ async def configure_proof_dirs( # pylint: disable=too-many-arguments
352360
pools = ["ENABLE_POOLS=true"] if enable_pools else []
353361
profiling = ["ENABLE_MEMORY_PROFILING=true"] if enable_memory_profiling else []
354362

363+
# Set up environment with CBMC_TIMEOUT
364+
env = os.environ.copy()
365+
env["CBMC_TIMEOUT"] = str(timeout)
366+
355367
# delete old reports
356368
proc = await asyncio.create_subprocess_exec(
357369
"make",
@@ -373,6 +385,7 @@ async def configure_proof_dirs( # pylint: disable=too-many-arguments
373385
report_target,
374386
"" if debug else "--quiet",
375387
cwd=path,
388+
env=env,
376389
stdout=asyncio.subprocess.PIPE,
377390
stderr=asyncio.subprocess.PIPE,
378391
)
@@ -497,6 +510,7 @@ async def main(): # pylint: disable=too-many-locals
497510
enable_memory_profiling,
498511
report_target,
499512
args.debug,
513+
args.per_proof_timeout,
500514
)
501515
)
502516
tasks.append(task)

scripts/tests

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,8 @@ class Tests:
811811
"run-cbmc-proofs.py",
812812
"--summarize",
813813
"--no-coverage",
814+
"--per-proof-timeout",
815+
str(self.args.per_proof_timeout),
814816
"-p",
815817
func,
816818
]
@@ -867,7 +869,15 @@ class Tests:
867869
return
868870
envvars = {"MLD_CONFIG_PARAMETER_SET": mldsa_parameter_set}
869871
p = subprocess.run(
870-
["python3", "run-cbmc-proofs.py", "--summarize", "--no-coverage", "-p"]
872+
[
873+
"python3",
874+
"run-cbmc-proofs.py",
875+
"--summarize",
876+
"--no-coverage",
877+
"--per-proof-timeout",
878+
str(self.args.per_proof_timeout),
879+
"-p",
880+
]
871881
+ proofs
872882
+ self.make_j(),
873883
cwd="proofs/cbmc",
@@ -1231,6 +1241,13 @@ def cli():
12311241
default=3600,
12321242
)
12331243

1244+
cbmc_parser.add_argument(
1245+
"--per-proof-timeout",
1246+
help="Timeout for each individual CBMC proof passed to run-cbmc-proofs.py, in seconds (default: 1800)",
1247+
type=int,
1248+
default=1800,
1249+
)
1250+
12341251
cbmc_parser.add_argument(
12351252
"-f",
12361253
"--fail-upon-error",

0 commit comments

Comments
 (0)