cpbenchy run instances/ -s ortools -t 60 --terminate --grace 1- Version
- cpbenchy 0.1.0.dev0
- Last updated
- 6 Oct 2026 · 1 commit
- Authors
- ThomSerg
- Tags
- limitssignalscompetition
What it does
By default, a solver is told how much time it has, and it is killed if it is still running a grace
period after that; a killed run has no answer. Competitions stop solvers differently: at the time
limit, the solver gets SIGTERM and must print the best solution it found, and a second later it gets
SIGKILL. --terminate makes runs behave like that.
At its time limit (wall or CPU), or on SIGTERM, a run reports the last solution its solver found:
competition output gets its s and v lines, CheckSolutions checks it,
and the run ends as feasible with that objective, or as timeout without one.
Use it
import cpbenchy
from cpbenchy import observers
cpbenchy.run("instances/", solvers=["ortools"], time_limit=60, plugins=[observers.PBOutput()],
args=["--terminate", "--grace", "1"])cpbenchy run instances/ -s ortools -t 60 --terminate --grace 1 -p cpbenchy.observers:PBOutputRules switch it on with terminate = true, and cpbenchy solve always has it.
What it records
| What the run had | status |
termination |
|---|---|---|
| a solution of an optimization problem | feasible, with its objective |
walltime, cputime or signal |
| no solution | timeout |
same |
Notes
- The solutions are those the solver reports through its solution callback; satisfaction problems and solvers without one have nothing to report until they finish.
- Keeping each solution costs a little time per solution, as it does for competition solvers.
- A watcher thread stops the run, so a solver that holds Python’s GIL while it searches can’t be stopped this way; native solvers release it. Others are killed after the grace period.
Measurement and limits has the details.
Implementation
The stop in src/cpbenchy/worker/terminate.py, lines 58–84 of 84, as of this version of the docs.
def stop(ctx: WorkerContext, reason: str) -> bool:
"""Report what the run has and exit. Returns (True: stop watching) only if the run is already finishing
by itself."""
ctx.lock.acquire() # never released: the process ends here, and a solution callback must not change `best`
if ctx.finishing:
ctx.lock.release()
return True # the run reports by itself; the executor's kill is the backstop
try:
ctx.terminated = reason
ctx.log(f"stopped at the {reason} limit" if reason != "signal" else "stopped by SIGTERM")
if ctx.solve_started is not None:
ctx.times["solve_s"] = time.perf_counter() - ctx.solve_started
if ctx.restore_best():
ctx.status, ctx.objective = "feasible", ctx.best[0] if ctx.best else None
else:
ctx.status = "unknown"
ctx.finishing = True
try:
ctx.hook.cpbenchy_worker_finish(ctx=ctx)
except Exception as e:
ctx.status, ctx.error = "error", f"in finish: {type(e).__name__}: {e}"
traceback.print_exc()
ctx.emit("result", **ctx.report())
finally:
sys.stdout.flush()
sys.stderr.flush()
os._exit(1 if ctx.status == "error" else 0)