Skip to content

identified a possible hang in async cursor.executemany() #278

@zzzeek

Description

@zzzeek

hi gang -

our sqlalchemy test suite for the new async oracledb driver we are developing hangs semi-reliably on one particular series of tests that are fielding an intentional IntegrityError from a cursor.executemany() call.

the condition is extremely difficult to reproduce, and I've come up with a script that seems to reproduce it a bit more portably when run on a linux machine, but it still takes many iterations for the condition to hit. The script below runs the same set of operations over and over again - running it against an Oracle 18c express database here, it will hang usually within 200 iterations. but i have no idea if it will do this in other circumstances. if i move the create table step out of the loop, it doesnt hang at all.

import asyncio
import oracledb
import uuid


async def main():
    conn = await oracledb.connect_async(
        dsn="(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=oracle18c)(PORT=1521))(CONNECT_DATA=(SID=xe)))",
        password="tiger",
        user="scott",
    )
    cursor = conn.cursor()

    try:
        await cursor.execute("DROP TABLE data")
    except:
        pass

    cursor.close()

    i = 0
    while True:
        await run_async_thing(conn)
        i += 1
        print(f"finished run: {i}")


async def run_async_thing(conn):
    cursor = conn.cursor()

    await cursor.execute(
        """
CREATE TABLE data (
    id VARCHAR2(30 CHAR) NOT NULL,
    data VARCHAR2(50 CHAR),
    has_server_default VARCHAR2(30 CHAR) DEFAULT 'some_server_default',
    "uid" CHAR(32),
    PRIMARY KEY (id)
)

"""
    )

    ret0, ret1, ret2 = (
        cursor.var(oracledb.STRING, arraysize=10),
        cursor.var(oracledb.STRING, arraysize=10),
        cursor.var(oracledb.STRING, arraysize=10),
    )

    parameters = [
        {
            "data": f"d{i}",
            '"uid"': uuid.uuid4().hex,
            "ret_0": ret0,
            "ret_1": ret1,
            "ret_2": ret2,
        }
        for i in range(10)
    ]

    try:
        await cursor.executemany(
            'INSERT INTO data (data, "uid") VALUES (:data, :"uid") '
            "RETURNING data.id, data.data, data.has_server_default "
            "INTO :ret_0, :ret_1, :ret_2",
            parameters,
        )
    except oracledb.IntegrityError as err:
        print("got error as expected!")

    print("next go!")

    await cursor.execute("DROP TABLE data")
    cursor.close()


asyncio.run(main())

so if I run it here it looks like:

got error as expected!
next go!
finished run: 1
got error as expected!
next go!
finished run: 2
got error as expected!
next go!
finished run: 3
got error as expected!
next go!
finished run: 4
got error as expected!
next go!

... keeps running ...


finished run: 29
got error as expected!
next go!
finished run: 30
got error as expected!
next go!
finished run: 31

then it's hung. press ctrl-C and stack shows as follows:

^CTraceback (most recent call last):
  File "/usr/lib64/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/asyncio/base_events.py", line 684, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/home/classic/dev/sqlalchemy/test4.py", line 23, in main
    await run_async_thing(conn)
  File "/home/classic/dev/sqlalchemy/test4.py", line 62, in run_async_thing
    await cursor.executemany(
  File "/home/classic/.venv3/lib64/python3.12/site-packages/oracledb/cursor.py", line 1067, in executemany
    await self._impl.executemany(
  File "src/oracledb/impl/thin/cursor.pyx", line 297, in executemany
  File "src/oracledb/impl/thin/protocol.pyx", line 817, in _process_single_message
  File "src/oracledb/impl/thin/protocol.pyx", line 818, in oracledb.thin_impl.BaseAsyncProtocol._process_single_message
  File "src/oracledb/impl/thin/protocol.pyx", line 769, in _process_message
  File "src/oracledb/impl/thin/protocol.pyx", line 872, in _reset
  File "src/oracledb/impl/thin/packet.pyx", line 571, in wait_for_packets_async
asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/classic/dev/sqlalchemy/test4.py", line 77, in <module>
    asyncio.run(main())
  File "/usr/lib64/python3.12/asyncio/runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/asyncio/runners.py", line 123, in run
    raise KeyboardInterrupt()
KeyboardInterrupt

that's what I got so far. Will see if @CaselIT can reproduce on his environment.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions