Skip to content

Async support not compatible with alternative event loop implementations like uvloop #276

@citreae535

Description

@citreae535
  1. What versions are you using?
    python-oracledb 2.0.0 on Python 3.12 and Oracle Database XE 18c. Both run inside docker containers on Windows 11 23H2 with WSL 2 backend. The database container image is gvenzl/oracle-xe:18-faststart.
  1. Is it an error or a hang or a crash?
    Error.

  2. What error(s) or behavior you are seeing?
    Using connect_async() with uvloop (a faster drop-in replacement for asyncio) throws the following exception:
    (The first line is the success output of asyncio.run(main()).)

('TEST',) 
Traceback (most recent call last):
  File "src/oracledb/impl/thin/connection.pyx", line 502, in oracledb.thin_impl.AsyncThinConnImpl._connect_with_address
  File "src/oracledb/impl/thin/protocol.pyx", line 567, in _connect_phase_one
  File "src/oracledb/impl/thin/protocol.pyx", line 730, in _connect_tcp
  File "src/oracledb/impl/thin/transport.pyx", line 301, in oracledb.thin_impl.Transport.set_from_socket
AttributeError: 'uvloop.loop.TCPTransport' object has no attribute 'setsockopt'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<frozen runpy>", line 189, in _run_module_as_main
  File "<frozen runpy>", line 148, in _get_module_details
  File "<frozen runpy>", line 112, in _get_module_details
  File "/app/example_service/__init__.py", line 18, in <module>
    uvloop.run(main())
  File "/app/.venv/lib/python3.12/site-packages/uvloop/__init__.py", line 109, in run
    return __asyncio.run(
           ^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/asyncio/runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "uvloop/loop.pyx", line 1517, in uvloop.loop.Loop.run_until_complete
  File "/app/.venv/lib/python3.12/site-packages/uvloop/__init__.py", line 61, in wrapper
    return await main
           ^^^^^^^^^^
  File "/app/example_service/__init__.py", line 8, in main
    async with oracledb.connect_async(
  File "/app/.venv/lib/python3.12/site-packages/oracledb/connection.py", line 1398, in __aenter__
    await self._connect_coroutine
  File "/app/.venv/lib/python3.12/site-packages/oracledb/connection.py", line 1443, in _connect
    await impl.connect(params_impl)
  File "src/oracledb/impl/thin/connection.pyx", line 609, in connect
  File "src/oracledb/impl/thin/connection.pyx", line 605, in oracledb.thin_impl.AsyncThinConnImpl.connect
  File "src/oracledb/impl/thin/connection.pyx", line 563, in _connect_with_params
  File "src/oracledb/impl/thin/connection.pyx", line 543, in _connect_with_description
  File "src/oracledb/impl/thin/connection.pyx", line 512, in _connect_with_address
  File "/app/.venv/lib/python3.12/site-packages/oracledb/errors.py", line 162, in _raise_err
    raise exc_type(_Error(message)) from cause
oracledb.exceptions.OperationalError: DPY-6005: cannot connect to database (CONNECTION_ID=+o4qjIZQoVmu5EZP54G9xw==).
'uvloop.loop.TCPTransport' object has no attribute 'setsockopt'
  1. Does your application call init_oracle_client()?
    No.
  1. Include a runnable Python script that shows the problem.
import asyncio

import oracledb
import uvloop


async def main():
    async with oracledb.connect_async(
        user="test", password="test", dsn="host.docker.internal/xepdb1"
    ) as connection:
        with connection.cursor() as cursor:
            await cursor.execute("select user from dual")
            async for result in cursor:
                print(result)


asyncio.run(main())
uvloop.run(main())
  1. Cause
    The thin mode transport implementation detects Async mode by checking if the supplied transport is an instance of asyncio.Transport. To support alternative implementations without depending on them directly, we need to detect Async mode without hard-coded runtime instance checks. Maybe passing in an explicit flag variable?
    self._is_async = isinstance(transport, asyncio.Transport)
    if self._is_async:
    sock = transport.get_extra_info('socket')
    else:
    sock = transport
    if description.expire_time > 0:
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
    if hasattr(socket, "TCP_KEEPIDLE") \
    and hasattr(socket, "TCP_KEEPINTVL") \
    and hasattr(socket, "TCP_KEEPCNT"):
    sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE,
    description.expire_time * 60)
    sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 6)
    sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 10)
    sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

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