Skip to content

Assertion failure in free_callback_context via create_collation #146090

@devdanzin

Description

@devdanzin

Crash report

What happened?

The interpreter will abort when running the following MRE.

Automated diagnosis:

Bug: When sqlite3_create_collation_v2() fails with SQLITE_BUSY, error cleanup at line 2201 calls free_callback_context(ctx) directly. But ctx has refcount=1 (set at creation, line 1064). free_callback_context asserts ctx->refcount == 0 (line 1076) -> crash in debug builds.

Fix: Use decref_callback_context(ctx) instead of free_callback_context(ctx).
File: Modules/_sqlite/connection.c, line 2201

MRE:

import sqlite3

conn = sqlite3.connect(":memory:")
conn.create_collation("mycoll", lambda a, b: (a > b) - (a < b))
conn.execute("CREATE TABLE t(x TEXT)")
for i in range(100):
    conn.execute("INSERT INTO t VALUES (?)", (f"item_{i:03d}",))
conn.commit()

cursor = conn.execute("SELECT x FROM t ORDER BY x COLLATE mycoll")
next(cursor)

# Replace the collation while the statement is active -> SQLITE_BUSY
# -> free_callback_context(ctx) with refcount=1 -> assertion failure
try:
    conn.create_collation("mycoll", lambda a, b: 0)
except sqlite3.OperationalError:
    pass  # We never get here in debug builds — assertion fires first

Backtrace:

python: ./Modules/_sqlite/connection.c:1076: void free_callback_context(callback_context *): Assertion `ctx->refcount == 0' failed.

Program received signal SIGABRT, Aborted.

#0  __pthread_kill_implementation (threadid=<optimized out>, signo=6, no_tid=0) at ./nptl/pthread_kill.c:44
#1  __pthread_kill_internal (threadid=<optimized out>, signo=6) at ./nptl/pthread_kill.c:89
#2  __GI___pthread_kill (threadid=<optimized out>, signo=signo@entry=6) at ./nptl/pthread_kill.c:100
#3  0x00007ffff7c45e2e in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#4  0x00007ffff7c28888 in __GI_abort () at ./stdlib/abort.c:77
#5  0x00007ffff7c287f0 in __assert_fail_base (fmt=<optimized out>, assertion=<optimized out>, file=<optimized out>, line=<optimized out>, function=<optimized out>) at ./assert/assert.c:118
#6  0x00007ffff7c3c19f in __assert_fail (assertion=<optimized out>, file=<optimized out>, line=<optimized out>, function=<optimized out>) at ./assert/assert.c:127
#7  0x00007ffff72e678f in free_callback_context (ctx=ctx@entry=0x7ffff74af940) at ./Modules/_sqlite/connection.c:1076
#8  0x00007ffff72e765b in pysqlite_connection_create_collation_impl (self=0x7ffff75b6450, cls=0x555555f5ec50, name=0x7ffff74e6688 "mycoll", callable=0x7ffff74f0a10)
    at ./Modules/_sqlite/connection.c:2201
#9  pysqlite_connection_create_collation (self=0x7ffff75b6450, cls=0x555555f5ec50, args=0x7fffffffb5e0, nargs=nargs@entry=2, kwnames=0x0) at ./Modules/_sqlite/clinic/connection.c.h:1289
#10 0x00005555556b6e0b in method_vectorcall_FASTCALL_KEYWORDS_METHOD (func=func@entry=0x7ffff7343350, args=args@entry=0x7fffffffb5d8, nargsf=nargsf@entry=9223372036854775811,
    kwnames=kwnames@entry=0x0) at Objects/descrobject.c:381
#11 0x00005555556a2ebe in _PyObject_VectorcallTstate (tstate=0x555555d99c08 <_PyRuntime+360664>, callable=0x7ffff7343350, args=0x7fffffffb5d8, nargsf=9223372036854775811, kwnames=0x0)
    at ./Include/internal/pycore_call.h:144
#12 0x000055555583f859 in _Py_VectorCallInstrumentation_StackRefSteal (callable=..., arguments=0x7ffff7fa7078, total_args=total_args@entry=3, kwnames=kwnames@entry=...,
    call_instrumentation=false, frame=frame@entry=0x7ffff7fa7020, this_instr=0x555555f392f8, tstate=0x555555d99c08 <_PyRuntime+360664>) at Python/ceval.c:775
#13 0x000055555584a8e4 in _PyEval_EvalFrameDefault (tstate=tstate@entry=0x555555d99c08 <_PyRuntime+360664>, frame=<optimized out>, frame@entry=0x7ffff7fa7020, throwflag=throwflag@entry=0)
    at Python/generated_cases.c.h:1838
#14 0x000055555583f08b in _PyEval_EvalFrame (tstate=0x555555d99c08 <_PyRuntime+360664>, frame=0x7ffff7fa7020, throwflag=0) at ./Include/internal/pycore_ceval.h:118
#15 _PyEval_Vector (tstate=tstate@entry=0x555555d99c08 <_PyRuntime+360664>, func=func@entry=0x7ffff7466690, locals=locals@entry=0x7ffff746a4b0, args=args@entry=0x0,
    argcount=argcount@entry=0, kwnames=kwnames@entry=0x0) at Python/ceval.c:2130
#16 0x000055555583ee1e in PyEval_EvalCode (co=co@entry=0x555555f390f0, globals=globals@entry=0x7ffff746a4b0, locals=locals@entry=0x7ffff746a4b0) at Python/ceval.c:686
#17 0x00005555559c8f8e in run_eval_code_obj (tstate=0x555555d99c08 <_PyRuntime+360664>, co=co@entry=0x555555f390f0, globals=globals@entry=0x7ffff746a4b0, locals=locals@entry=0x7ffff746a4b0)
    at Python/pythonrun.c:1368
#18 0x00005555559c8adb in run_mod (mod=mod@entry=0x555555f45cc8, filename=filename@entry=0x7ffff74f8040, globals=globals@entry=0x7ffff746a4b0, locals=locals@entry=0x7ffff746a4b0,
    flags=0x7fffffffc910, arena=arena@entry=0x7ffff74dac20, interactive_src=0x0, generate_new_source=0) at Python/pythonrun.c:1471

Found using cpython-review-toolkit with Claude Opus 4.6, using the /cpython-review-toolkit:explore Modules/_sqlite/ all deep command.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Output from running 'python -VV' on the command line:

Python 3.15.0a7+ (heads/main:e0f7c1097e1, Mar 17 2026, 18:10:52) [Clang 21.1.2 (2ubuntu6)]

Metadata

Metadata

Assignees

Labels

extension-modulesC modules in the Modules dirtopic-sqlite3type-crashA hard crash of the interpreter, possibly with a core dump

Projects

Status

No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions