GeezerVison is showing github.com — Assertion failure in `free_callback_context` via `create_collation` and missing `PyErr_NoMemory` in `create_callback_context` · Issue #146090 · python/cpython
Assertion failure in free_callback_context via create_collation and missing PyErr_NoMemory in create_callback_context #146090DescriptionCrash report
What happened?
The interpreter will abort when running the following MRE.
Automated diagnosis:
Bug 1: 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
Bug 2: create_callback_context returns NULL without exception when PyMem_Malloc fails. All 7 callers propagate NULL → SystemError.
Fix: Add if (ctx == NULL) { return (callback_context *)PyErr_NoMemory(); }.
File: Modules/_sqlite/connection.c, line 1061
Full report (contains both bugs)
MRE 1:
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
MRE 2:
import sqlite3, _testcapi
conn = sqlite3.connect(":memory:")
_testcapi.set_nomemory(1, 0)
try:
conn.set_trace_callback(lambda s: None)
except (MemoryError, SystemError):
pass
finally:
_testcapi.remove_mem_hooks()
Backtrace 1:
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
Backtrace 2:
Fatal Python error: _Py_CheckFunctionResult: a function returned NULL without setting an exception
Python runtime state: initialized
object address : 0x7ffff75ac520
object refcount : 1
object type : 0x555555d05978
object type name: MemoryError
object repr :
lost sys.stderr
Current thread 0x00007ffff7e8b780 [python] (most recent call first):
File "/home/danzin/crashers/sqlite_oom1.py", line 6 in <module>
Extension modules: _testcapi (total: 1)
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 0x0000555555993a4d in fatal_error_exit (status=3064086, status@entry=-1) at Python/pylifecycle.c:3341
#6 0x0000555555993737 in fatal_error (fd=2, header=header@entry=1, prefix=prefix@entry=0x555555ac7235 "_Py_CheckFunctionResult",
msg=msg@entry=0x555555ac724d "a function returned NULL without setting an exception", status=status@entry=-1) at Python/pylifecycle.c:3571
#7 0x0000555555991ca4 in _Py_FatalErrorFunc (func=0x555555ac7235 "_Py_CheckFunctionResult", msg=0x555555ac724d "a function returned NULL without setting an exception")
at Python/pylifecycle.c:3587
#8 0x00005555556a2d05 in _Py_CheckFunctionResult (tstate=<optimized out>, callable=<optimized out>, result=<optimized out>, where=<optimized out>) at Objects/call.c:43
#9 0x000055555583f859 in _Py_VectorCallInstrumentation_StackRefSteal (callable=..., arguments=0x7ffff7fa7078, total_args=total_args@entry=2, kwnames=kwnames@entry=...,
call_instrumentation=false, frame=frame@entry=0x7ffff7fa7020, this_instr=0x7ffff756d9ea, tstate=0x555555d99c08 <_PyRuntime+360664>) at Python/ceval.c:775
#10 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
#11 0x000055555583f08b in _PyEval_EvalFrame (tstate=0x555555d99c08 <_PyRuntime+360664>, frame=0x7ffff7fa7020, throwflag=0) at ./Include/internal/pycore_ceval.h:118
#12 _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
#13 0x000055555583ee1e in PyEval_EvalCode (co=co@entry=0x7ffff756d8a0, globals=globals@entry=0x7ffff746a4b0, locals=locals@entry=0x7ffff746a4b0) at Python/ceval.c:686
#14 0x00005555559c8f8e in run_eval_code_obj (tstate=0x555555d99c08 <_PyRuntime+360664>, co=co@entry=0x7ffff756d8a0, globals=globals@entry=0x7ffff746a4b0, locals=locals@entry=0x7ffff746a4b0)
at Python/pythonrun.c:1368
#15 0x00005555559c8adb in run_mod (mod=mod@entry=0x555555f3f5b0, filename=filename@entry=0x7ffff74e8580, globals=globals@entry=0x7ffff746a4b0, locals=locals@entry=0x7ffff746a4b0,
flags=0x7fffffffc950, arena=arena@entry=0x7ffff74dad40, 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)]
Linked PRs
Activitypicnixz commented on Mar 17, 2026 I think this one is on me. I added the refcount logic here and in the tests I said that coverage was missing so this likely caused this issue to slip. picnixz commented on Mar 17, 2026 I will try to address this in the next two weeks. I am away this week but I may address it while travelling. devdanzin commented on Mar 18, 2026 The full report presents another crash, please let me know if I should open another issue for it. vstinner commented on Mar 18, 2026 @picnixz plans to write a PR to fix this issue.
I confirm that the proposed fix works as expected:
diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c
index af63271b9fd..76df1089ebb 100644
--- a/Modules/_sqlite/connection.c
+++ b/Modules/_sqlite/connection.c
@@ -2198,7 +2198,7 @@ pysqlite_connection_create_collation_impl(pysqlite_Connection *self,
* the context before returning.
*/
if (callable != Py_None) {
- free_callback_context(ctx);
+ decref_callback_context(ctx);
}
set_error_from_db(self->state, self->db);
return NULL;picnixz commented on Mar 18, 2026
please let me know if I should open another issue for it
No, it's fine. Just rename the issue and add the two bugs in that issue (that is, bugs concerning custom refcounting in sqlite3) changed the title [-]Assertion failure in `free_callback_context` via `create_collation`[/-] [+]Assertion failure in `free_callback_context` via `create_collation` and missing `PyErr_NoMemory` in `create_callback_context`[/+] on Mar 19, 2026added a commit that references this issue on Mar 29, 2026gh-146090: fix memory management of internal `sqlite3` callback conte…
MetadataMetadataStatusShow more project fields
DevelopmentNo branches or pull requestsIssue actions
|