Skip to content

bug: mypyc async driver segfaults on aiosqlite statement errors #691

Description

@cofin

Description

A mypyc-compiled SQLSpec installation segfaults when the async aiosqlite driver encounters an ordinary SQLite statement error. The equivalent interpreted SQLSpec installation correctly raises sqlspec.exceptions.SQLSpecError.

This is not limited to result conversion: both driver.select() and driver.execute() reproduce the crash.

Environment

  • SQLSpec: 0.58.1 with the mypyc extra
  • Python: 3.10.18
  • aiosqlite: 0.22.1
  • SQLite: 3.50.4
  • Platform: Linux x86_64, kernel 7.0.0-28-generic

Compiled modules involved:

  • sqlspec/driver/_async.cpython-310-x86_64-linux-gnu.so
  • sqlspec/driver/_exception_handler.cpython-310-x86_64-linux-gnu.so

The aiosqlite adapter driver itself is interpreted Python.

Minimal reproducer

import asyncio
import tempfile

from sqlspec import SQLSpec
from sqlspec.adapters.aiosqlite import AiosqliteConfig


async def main() -> None:
    with tempfile.NamedTemporaryFile(suffix=".db") as database:
        config = AiosqliteConfig(
            connection_config={"database": database.name},
        )
        registry = SQLSpec()
        registry.add_config(config)

        async with registry.provide_session(config) as driver:
            await driver.execute(
                "CREATE TABLE example (id INTEGER PRIMARY KEY)"
            )
            await driver.select(
                "SELECT missing_column FROM example"
            )


asyncio.run(main())

Actual behavior

With a mypyc-compiled SQLSpec installation:

Segmentation fault
exit code 139

The final Python frame is AsyncDriverAdapterBase._check_pending_exception() in sqlspec/driver/_async.py.

The call path is:

AsyncDriverAdapterBase.select()
  -> execute()
  -> dispatch_statement_execution()
  -> _check_pending_exception()

Expected behavior

The interpreted installation raises:

sqlspec.exceptions.SQLSpecError:
AIOSQLite database error: no such column: missing_column

Comparison and suspected boundary

Running the reproducer against the interpreted SQLSpec 0.58.1 source tree produces the expected SQLSpecError and exits normally with status 1. The compiled synchronous SQLite adapter also raises SQLSpecError correctly, so the defect appears specific to compiled asynchronous dispatch.

dispatch_statement_execution() uses a deferred exception handler:

exc_handler = self.handle_database_exceptions()
async with exc_handler, self.with_cursor(connection) as cursor:
    ...
self._check_pending_exception(exc_handler)

The handler stores the mapped exception in pending_exception, which is raised outside __aexit__. A manually constructed handler and exception do not segfault; the crash requires the full compiled async driver path with the original exception coming from an awaited aiosqlite operation. This suggests a mypyc lifetime or exception-state problem around the combined async context-manager dispatch.

Additional observations

  • Replacing select() with execute() still exits with status 139.
  • The equivalent synchronous SQLite operation does not crash.
  • PyArrow is not required or loaded by the minimal reproducer.
  • The relevant exception-dispatch code remains structurally unchanged on current origin/main at version 0.58.3, although this reproduction used the installed 0.58.1 compiled package.
  • A regression test should run invalid SQL in a subprocess against a compiled installation and assert normal exception termination rather than a signal, so a regression cannot kill the main pytest process.

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