Skip to content

Fails with sqlalchemy KeyError: "Deferred loader for attribute 'id' failed to populate correctly" #115

Description

@ddorian

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())

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions