Hi,
Here is a simple script to reproduce the bug:
# /// script
# requires-python = "==3.14.6"
# dependencies = [
# "cinderx",
# "sqlalchemy>=2.0",
# ]
# ///
"""Minimal reproducer for a CinderX (Meta's CPython JIT, alpha) miscompilation
that *silently* breaks SQLAlchemy 2.0.
uv run cinderx_sqlalchemy_repro.py
What it does: against an in-memory SQLite database, in a loop, INSERT a row,
commit (which expires the ORM object's attributes), then read back the primary
key `.id`. That read triggers a "deferred load" SELECT to repopulate the
expired attribute. With `cinderx.jit.auto()` enabled, that reload path is
miscompiled and the value fails to populate, raising:
KeyError: "Deferred loader for attribute 'id' failed to populate correctly"
Run the exact same loop with the JIT off (`NO_JIT=1`) and it completes cleanly,
proving the fault is the JIT, not the SQLAlchemy usage. Needs nothing external.
Env knobs:
NO_JIT=1 run the control: do NOT enable the CinderX JIT
ITERATIONS=N loop count (default 3000)
"""
import os
import sys
# The JIT must be enabled BEFORE SQLAlchemy is imported so its bytecode becomes
# eligible for compilation. This is the *only* difference between the failing
# and passing runs.
ENABLE_JIT = os.environ.get("NO_JIT") != "1"
if ENABLE_JIT:
import cinderx.jit
cinderx.jit.auto()
import sqlalchemy as sa
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column
from sqlalchemy.pool import StaticPool
class Base(DeclarativeBase):
pass
class Widget(Base):
__tablename__ = "cinderx_repro_widget"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(sa.String(50))
def main() -> int:
iterations = int(os.environ.get("ITERATIONS", "3000"))
jit_on: bool = ENABLE_JIT and cinderx.jit.is_enabled()
# In-memory SQLite: keep ONE shared connection (StaticPool) so the table
# persists across the per-iteration sessions.
engine = sa.create_engine(
"sqlite://", poolclass=StaticPool, connect_args={"check_same_thread": False}
)
# drop_all is a no-op on the fresh in-memory DB, but running it first warms
# the code path that makes the JIT miscompile surface as the documented
# "Deferred loader ... failed to populate correctly" rather than a FlushError.
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
failures = 0
first_failure_at: int | None = None
print(f"JIT enabled: {jit_on}; iterations: {iterations}")
for i in range(iterations):
try:
with Session(engine) as session:
w = Widget(name="x")
session.add(w)
session.commit() # expire_on_commit=True -> all attrs expired
_ = w.id # deferred-reload SELECT -> the miscompiled path
except Exception as exc: # noqa: BLE001 - we are counting any failure
failures += 1
if first_failure_at is None:
first_failure_at = i
print(f" first failure at iter {i}: {type(exc).__name__}: {exc}")
if i % 1000 == 0:
print(f" iter {i}: failures so far = {failures}")
engine.dispose()
print()
print(f"JIT enabled : {jit_on}")
print(f"iterations : {iterations}")
print(f"failures : {failures}")
if failures:
print(
f"first failed: iter {first_failure_at}\n"
"=> BUG REPRODUCED: deferred primary-key reload corrupted under the CinderX JIT."
)
return 1
print("=> clean: no failures (expected when NO_JIT=1).")
return 0
if __name__ == "__main__":
sys.exit(main())
Hi,
Here is a simple script to reproduce the bug: