Skip to content

Thin async crashes with AttributeError: 'NoneType' object has no attribute 'decode' in _process_keyword_value_pairs #587

@Ezhvsalate

Description

@Ezhvsalate

1 . What versions are you using?

platform.platform: Linux-4.14.35-2047.501.2.el7uek.x86_64-x86_64-with-glibc2.2.5
sys.maxsize > 2**32: True
platform.python_version: 3.9.22
oracledb.version: 3.4.x (also reproduced on 3.0.0)
Oracle Database: 19c Enterprise Edition Release 19.15.0.0.0
Connection mode: Thin async, SYSDBA

2. Is it an error or a hang or a crash?
An unhandled exception that crashes the user's coroutine. The DDL itself succeeds on the database — the failure is purely client-side, in the response parser.

3. What error(s) or behavior you are seeing?
On 3.4.x:

AttributeError: 'NoneType' object has no attribute 'decode'
  File ".../oracledb/cursor.py", line 1200, in execute
    await self._impl.execute(self)
  File "src/oracledb/impl/thin/cursor.pyx", line 392, in execute
  File "src/oracledb/impl/thin/protocol.pyx", line 874, in _process_single_message
  File "src/oracledb/impl/thin/protocol.pyx", line 875, in oracledb.thin_impl.BaseAsyncProtocol._process_single_message
  File "src/oracledb/impl/thin/protocol.pyx", line 851, in _process_message
  File "src/oracledb/impl/thin/messages/base.pyx", line 274, in _process_keyword_value_pairs

On 3.0.0 the same code path raised:

UnboundLocalError: local variable 'key_value' referenced before assignment
  File "src/oracledb/impl/thin/messages.pyx", line 1134, in _process_return_parameters

The DDL that triggers it (intermittently — depends on what the server includes in TTC return parameters):

ALTER PLUGGABLE DATABASE <pdb_name> OPEN READ ONLY
ALTER PLUGGABLE DATABASE <pdb_name> OPEN READ WRITE   -- on snapshot copies
ALTER PLUGGABLE DATABASE <pdb_name> CLOSE IMMEDIATE

After the exception the connection is left in an inconsistent state and cannot be reused.

Root cause (proposed)

In src/oracledb/impl/thin/messages/base.pyx::_process_keyword_value_pairs (3.4.x):

cdef int _process_keyword_value_pairs(self, ReadBuffer buf,
                                      uint16_t num_pairs) except -1:
        ...
        if keyword_num == TNS_KEYWORD_NUM_CURRENT_SCHEMA:
            self.conn_impl._current_schema = text_value.decode()    # <-- NoneType.decode()
        elif keyword_num == TNS_KEYWORD_NUM_EDITION:
            self.conn_impl._edition = text_value.decode()
        elif keyword_num == TNS_KEYWORD_NUM_TRANSACTION_ID:
            self._update_sessionless_txn_state(binary_value)

The same bug exists in the older _process_return_parameters (3.0.0) where key_value is assigned only inside if num_bytes > 0.

Suggested fix

if keyword_num == TNS_KEYWORD_NUM_CURRENT_SCHEMA:
    if text_value is not None:
        self.conn_impl._current_schema = text_value.decode()
elif keyword_num == TNS_KEYWORD_NUM_EDITION:
    if text_value is not None:
        self.conn_impl._edition = text_value.decode()

4. Does your application call init_oracle_client()?
No. Thin mode only, async.

5. Runnable script

import asyncio
import oracledb
DSN = "host:1521/CDB19C"
SYS_PASSWORD = "..."
PDB_NAME = "TESTPDB"
IMAGE_FILE = "/path/to/source.pdb"
async def main():
    conn = await oracledb.connect_async(
        user="sys",
        password=SYS_PASSWORD,
        dsn=DSN,
        mode=oracledb.SYSDBA,
    )
    async with conn.cursor() as cursor:
        await cursor.execute(
            f"CREATE PLUGGABLE DATABASE {PDB_NAME} AS CLONE USING '{IMAGE_FILE}'"
        )
        await cursor.execute(f"ALTER PLUGGABLE DATABASE {PDB_NAME} OPEN READ WRITE")
        await cursor.execute(f"ALTER PLUGGABLE DATABASE {PDB_NAME} CLOSE IMMEDIATE")
        await cursor.execute(f"ALTER PLUGGABLE DATABASE {PDB_NAME} OPEN READ ONLY")
        await cursor.execute(f"ALTER PLUGGABLE DATABASE {PDB_NAME} CLOSE IMMEDIATE")
        await cursor.execute(f"DROP PLUGGABLE DATABASE {PDB_NAME} INCLUDING DATAFILES")
    await conn.close()
asyncio.run(main())

The crash is intermittent across runs but consistently appears on a fraction of OPEN READ ONLY / OPEN READ WRITE operations on Oracle 19c with this driver in thin async SYSDBA mode. With a fresh database session and after enough PDB lifecycle iterations, every execution sequence eventually hits it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions