Skip to content

Deadlock on insert with cursors and fetchmany #182

Closed

Description

@ww2406

What happens?

DBAPI cursor with fetchmany causes deadlock when concurrent connections used within single process

To Reproduce

from collections import deque, namedtuple
from typing import Sequence

import duckdb
import pyarrow
from loguru import logger
from tqdm import tqdm

schema = pyarrow.schema([
    ( 'id', pyarrow.int32()),
    ( 'name', pyarrow.string()),
    ( 'loves_food', pyarrow.bool_())
])

logger.info('Generating data')
data = deque()
for i in tqdm(range(4_000_000)):
    data.append({
        'id': i,
        'name': 'Namey McNameFace',
        'loves_food': True
    })

arrow_table = pyarrow.Table.from_pylist(data, schema=schema)

conn = duckdb.connect('test.db')
conn.execute('CREATE OR REPLACE TABLE test (id INTEGER, name VARCHAR, loves_food BOOLEAN)')
logger.info('Inserting data')
conn.execute('INSERT INTO test SELECT * FROM arrow_table')

conn.execute('CREATE OR REPLACE TABLE test_write (id INTEGER, name VARCHAR, loves_food BOOLEAN)')

def get_data_in_chunks():
    with conn.cursor() as cur:
        cur.execute('SELECT * FROM test')
        while True:
            rows = cur.fetchmany(10000)
            if not rows:
                break
            Row = namedtuple('Row', [c[0] for c in cur.description])
            yield [Row(*r) for r in rows]

def insert_data_in_chunks(ins_data: Sequence):
    with conn.cursor() as cur:
        ins_table = pyarrow.Table.from_pylist(ins_data, schema=schema)
        # this will stop executing at some point -- the exact number may be system-dependent, my mac is 2,240,000
        # however, CPU usage will remain very high
        cur.execute('INSERT INTO test_write SELECT * FROM ins_table')


num_processed = 0
for rs in get_data_in_chunks():
    num_processed += len(rs)
    logger.info(f'Got {len(rs)} records; {num_processed} processed so far')
    dict_data = map(lambda r: r._asdict(), rs)
    insert_data_in_chunks(rs)

OS:

macOS (aarch64), Windows 11 (x64)

DuckDB Package Version:

1.4.2

Python Version:

3.12

Full Name:

William Welch

Affiliation:

Self

What is the latest build you tested with? If possible, we recommend testing with the latest nightly build.

I have tested with a stable release

Did you include all relevant data sets for reproducing the issue?

Yes

Did you include all code required to reproduce the issue?

  • Yes, I have

Did you include all relevant configuration to reproduce the issue?

  • Yes, I have

Activity

  1. changed the title [-]Deadlock on insert[/-] [+]Deadlock on insert with cursors and fetchmany[/+] on Nov 16, 2025
  2. paultiq commented on Nov 17, 2025

    @paultiq
    Contributor

    Reproduced, looks like the root here is that the first cursor from get_data_in_chunks has a lock on test while insert_data_in_chunks is writing to it.

    MRE:

    import duckdb
    
    cur1 = duckdb.connect('test.db')
    
    cur1.execute("create or replace table test_write(i int, i_str varchar)")
    # Execute and leave cursor open
    cur1.execute('SELECT * FROM test_write')
    
    with duckdb.connect('test.db') as cur2:
            num_processed = 0
            while True:
                r = cur2.execute("INSERT INTO test_write SELECT i, i||'_str' FROM range(10000) t(i)")
                num_processed += r.fetchone()[0]
                print(f'{num_processed} processed so far')
    

    stack trace:

    Thread 1 (Thread 0x7fb5adbe5740 (LWP 2848603) "python"):
    #0  0x00007fb59dceaa70 in duckdb::StorageLock::GetExclusiveLock() () from /home/ec2-user/git/goodenv/.venv/lib/python3.13/site-packages/_duckdb.cpython-313-x86_64-linux-gnu.so
    #1  0x00007fb59dceab44 in duckdb::DataTable::GetCheckpointLock() () from /home/ec2-user/git/goodenv/.venv/lib/python3.13/site-packages/_duckdb.cpython-313-x86_64-linux-gnu.so
    #2  0x00007fb59dceabd0 in duckdb::SingleFileCheckpointWriter::WriteTable(duckdb::TableCatalogEntry&, duckdb::Serializer&) () from /home/ec2-user/git/goodenv/.venv/lib/python3.13/site-packages/_duckdb.cpython-313-x86_64-linux-gnu.so
    #3  0x00007fb59dd12bd8 in duckdb::SingleFileCheckpointWriter::CreateCheckpoint() () from /home/ec2-user/git/goodenv/.venv/lib/python3.13/site-packages/_duckdb.cpython-313-x86_64-linux-gnu.so
    #4  0x00007fb59dcf9a61 in duckdb::SingleFileStorageManager::CreateCheckpoint(duckdb::QueryContext, duckdb::CheckpointOptions) () from /home/ec2-user/git/goodenv/.venv/lib/python3.13/site-packages/_duckdb.cpython-313-x86_64-linux-gnu.so
    #5  0x00007fb59dd2b075 in duckdb::DuckTransactionManager::CommitTransaction(duckdb::ClientContext&, duckdb::Transaction&) () from /home/ec2-user/git/goodenv/.venv/lib/python3.13/site-packages/_duckdb.cpython-313-x86_64-linux-gnu.so
    #6  0x00007fb59dd25a15 in duckdb::MetaTransaction::Commit() () from /home/ec2-user/git/goodenv/.venv/lib/python3.13/site-packages/_duckdb.cpython-313-x86_64-linux-gnu.so
    #7  0x00007fb59dd2858d in duckdb::TransactionContext::Commit() () from /home/ec2-user/git/goodenv/.venv/lib/python3.13/site-packages/_duckdb.cpython-313-x86_64-linux-gnu.so
    #8  0x00007fb59db38705 in duckdb::ClientContext::EndQueryInternal(duckdb::ClientContextLock&, bool, bool, duckdb::optional_ptr<duckdb::ErrorData, true>) () from /home/ec2-user/git/goodenv/.venv/lib/python3.13/site-packages/_duckdb.cpython-313-x86_64-linux-gnu.so
    #9  0x00007fb59db38cd8 in duckdb::ClientContext::CleanupInternal(duckdb::ClientContextLock&, duckdb::BaseQueryResult*, bool) () from /home/ec2-user/git/goodenv/.venv/lib/python3.13/site-packages/_duckdb.cpython-313-x86_64-linux-gnu.so
    #10 0x00007fb59db392a5 in duckdb::ClientContext::FetchResultInternal(duckdb::ClientContextLock&, duckdb::PendingQueryResult&) () from /home/ec2-user/git/goodenv/.venv/lib/python3.13/site-packages/_duckdb.cpython-313-x86_64-linux-gnu.so
    #11 0x00007fb59db39cb6 in duckdb::PendingQueryResult::ExecuteInternal(duckdb::ClientContextLock&) () from /home/ec2-user/git/goodenv/.venv/lib/python3.13/site-packages/_duckdb.cpython-313-x86_64-linux-gnu.so
    #12 0x00007fb59db39d56 in duckdb::PendingQueryResult::Execute() () from /home/ec2-user/git/goodenv/.venv/lib/python3.13/site-packages/_duckdb.cpython-313-x86_64-linux-gnu.so
    #13 0x00007fb59d1f88ef in duckdb::DuckDBPyConnection::CompletePendingQuery(duckdb::PendingQueryResult&) () from /home/ec2-user/git/goodenv/.venv/lib/python3.13/site-packages/_duckdb.cpython-313-x86_64-linux-gnu.so
    
  3. ww2406 commented on Nov 25, 2025

    @ww2406
    Author

    Thanks for confirming I'm not crazy @paultiq! If I were potentially interested in investigating how to fix this, would you be able to provide direction on where to start?

  4. paultiq commented on Nov 25, 2025

    @paultiq
    Contributor

    Are you asking how to write this differently to avoid this problem, or how to fix the underlying issue / modify DuckDB?

    If the rewriting, I'd say: modify it to not use a long lived cursor, especially not when doing concurrent reads and writes. For example, you could retrieve the entire working set and iterate over it. Or, you could page through the data using offset and limit.

    If fix/modify DuckDB, I'm just another user and will leave that for the experts.

  5. ww2406 commented on Nov 30, 2025

    @ww2406
    Author

    Ah gotcha, thanks anyways!

  6. evertlammerts commented on Sep 22, 2026

    @evertlammerts
    Member

    Thanks for reporting this and for the detailed MRE. The deadlock was caused by a core locking bug where the checkpoint would block waiting for a scan lock held by an unfetched cursor. This was fixed in DuckDB core v1.5.0+ (duckdb/duckdb#20286).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions