google / google/or-tools

CP-SAT 9.15.6755: memory-safety regression building large models — `std::bad_alloc` via `repr(IntVar)` and heap corruption (0xC0000374) via `IntVar.Proto()` (works on 9.14)

Open
#5,297 1 comment 0 reactions 1 assignee Claimed by @lperron View on GitHub
Solver: CP-SAT Solver
Dominant language
C++
Stars
14.1k
Forks
2.5k
Avg merge
8h 39m
Merged PRs (30d)
72

Description

**What version of OR-Tools and what language are you using?**
Version: **ortools 9.15.6755** (Python), regression vs **9.14.6206** and **9.12.4544**
Language: **Python 3.12.9**

**Which solver are you using (e.g. CP-SAT, Routing Solver, GLOP, BOP, Gurobi)**
CP-SAT (`ortools.sat.python.cp_model`)

**What operating system (Linux, Windows, ...) and version?**
Windows 10/11 x64 (also reproduced on Linux CI).

We build a moderately sized CP-SAT model (~16 000 variables) programmatically.
The build code and input data are unchanged across ortools versions.

- On **9.12.4544** and **9.14.6206**: the model builds and solves fine
(peak working set ≈ 360 MB).
- On **9.15.6755**: the build aborts.

The failure first appears as `MemoryError: bad allocation` (`std::bad_alloc`)
raised from a plain `repr()` / `str()` of an `IntVar`, executed inside an eager
debug f-string:

```python
logger.debug(f"{volume_var=}") # volume_var: normal IntVar, domain [0, 40]
```

At the moment of that crash the process working set is only **~140 MB**, so this
is a **single pathological allocation**, not gradual growth. Inside a
`ProcessPoolExecutor` it surfaces as `BrokenProcessPool`.

### It is not only `repr()`

We removed the offending log line — the crash just moved to the next eager
`f"{var=}"` site, and then to a third one in another module. We then
monkey‑patched `__repr__`/`__str__` to a cheap `` on **all 27
classes** of `ortools.sat.python.cp_model`. Logging stopped crashing and the
build progressed further, but the process **still died**, now with **heap
corruption**:

```
Windows fatal exception: code 0xC0000374 # STATUS_HEAP_CORRUPTION
Current thread (most recent call first):
File ".../classes/vars.py", line 113 in _var_can_be_positive
domain = list(var.Proto().domain)
...
```

In isolation, both `repr(var)` and `var.Proto().domain` behave correctly on 9.15
(e.g. `new_int_var(0, 50, "v")` → `repr` is short, `Proto().domain == [0, 50]`).
The failures only appear while building the **full** model, and manifest in
different forms (`std::bad_alloc` via `repr`, heap corruption via `Proto()`).
This strongly suggests an underlying **memory-safety regression in the 9.15
CP-SAT / pybind layer** for large models.

## Repro / attachments

- `model_dump.pb` — `CpModelProto` (≈1.8 MB, 16 027 variables) serialized on
9.14 at the failing construction step.
- `mem_probe.py` — benchmark showing common construction patterns (sum, min/max,
multiplication, big domains, affine) do **not** regress in memory on 9.15
(negative evidence).
- `repr_probe.py` — rebuilds each variable from the proto in isolation and
`str()`s them; does **not** reproduce (domain alone is not the trigger).
- `real_build_probe.py`, `min_repro_attempt.py` — driver / attempts.

Note: because the crash is context-dependent (full model construction), the
isolated scripts don’t reproduce it by themselves; the reliable reproduction is
the full build. We’re happy to provide a runnable end-to-end reproducer or work
with a maintainer to capture the exact triggering API-call sequence.

## What did you expect to see?

Building the model succeeds on 9.15, as it does on 9.14 / 9.12; `repr()`/`str()`
and `IntVar.Proto()` remain safe.

## What did you see instead?

`std::bad_alloc` (via `repr`) and, after neutralizing repr, `STATUS_HEAP_CORRUPTION`
(`0xC0000374`) via `IntVar.Proto()`, during model construction on 9.15.6755 only.

## API notes (possibly related)

- `CpModel.proto` is now a native `cp_model_helper.CpModelProto`;
`proto.ByteSize()` and `proto.ParseFromString()` are gone (present on ≤9.14).
- `str(IntVar)` formatting is inconsistent for in-model variables (sometimes
`name(lb..ub)`, sometimes just `name`); some variables report an empty `.name`.

## Workaround

Pin `ortools` to `~9.14.6206`. Removing logging is not a reliable fix (the crash
reappears via `IntVar.Proto()`).

[mem_probe.py](https://github.com/user-attachments/files/31222517/mem_probe.py)
[min_repro_attempt.py](https://github.com/user-attachments/files/31222520/min_repro_attempt.py)
[model_dump.zip](https://github.com/user-attachments/files/31222521/model_dump.zip)
[real_build_probe.py](https://github.com/user-attachments/files/31222518/real_build_probe.py)
[repr_probe.py](https://github.com/user-attachments/files/31222519/repr_probe.py)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.