oracle / oracle/python-oracledb

Thin mode callproc() hangs with NUMBER inputs and OUT SYS_REFCURSOR; numeric strings succeed

Open
#600 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug patch available
Dominant language
Python
Stars
451
Forks
119
PR merge metrics
No merged PRs in 30d

Description

Thin mode callproc() hangs with NUMBER inputs and OUT SYS_REFCURSOR; numeric strings succeed

1. What versions are you using?


[oracledb_ref_cursor_repro.py](https://github.com/user-attachments/files/30933861/oracledb_ref_cursor_repro.py)
[oracledb_ref_cursor_repro.sql](https://github.com/user-attachments/files/30933862/oracledb_ref_cursor_repro.sql)

Oracle Database: 23.26.3.2.0
platform.platform: Linux-6.18.33.1-microsoft-standard-WSL2-x86_64-with-glibc2.39
sys.maxsize > 2**32: True
platform.python_version: 3.12.3
oracledb.__version__: 4.0.2

The self-contained reproduction below was run with python-oracledb 4.0.2.

The original application behavior was also reproduced with python-oracledb
3.4.0 and 4.0.1, but those versions were not used for the self-contained
reproduction.

Oracle Client version is not applicable because this uses Thin mode.

2. Is it an error or a hang or a crash?

It is a hang inside Cursor.callproc(). After Connection.call_timeout is
reached and the driver attempts connection recovery, it raises DPY-4011.

No REF CURSOR fetch occurs before the hang. No process crash or segmentation
fault was observed.

3. What error or behavior are you seeing?

A packaged procedure accepts two NUMBER inputs and returns one
SYS_REFCURSOR.

The result depends on how the two NUMBER parameters are bound:

Input binding Result
Python integers Hangs, then DPY-4011
Explicit DB_TYPE_NUMBER variables Hangs, then DPY-4011
Numeric strings Returns and fetches immediately

With Python integers, the command is:

cursor.callproc(
    "oracledb_ref_cursor_repro.get_row",
    [1, 3, result],
)

After the configured 5000 ms call timeout and connection recovery attempt, it
raises:

Traceback (most recent call last):
  File "oracledb_ref_cursor_repro.py", line 65, in <module>
    main()
  File "oracledb_ref_cursor_repro.py", line 46, in main
    cursor.callproc("oracledb_ref_cursor_repro.get_row", parameters)
  File "oracledb/cursor.py", line 766, in callproc
    self._call(name, parameters, keyword_parameters)
  File "oracledb/cursor.py", line 98, in _call
    return self.execute(statement, bind_values)
  File "oracledb/cursor.py", line 859, in execute
    impl.execute(self)
  File "src/oracledb/impl/thin/cursor.pyx", line 279, in
    oracledb.thin_impl.ThinCursorImpl.execute
  File "src/oracledb/impl/thin/protocol.pyx", line 501, in
    oracledb.thin_impl.Protocol._process_single_message
  File "src/oracledb/impl/thin/protocol.pyx", line 502, in
    oracledb.thin_impl.Protocol._process_single_message
  File "src/oracledb/impl/thin/protocol.pyx", line 456, in
    oracledb.thin_impl.Protocol._process_message
oracledb.exceptions.DatabaseError: DPY-4011:
the database or network closed the connection
socket timed out while recovering from previous socket timeout

Explicit NUMBER variables produce the same result:

identifier = cursor.var(oracledb.DB_TYPE_NUMBER)
identifier.setvalue(0, 1)

run_id = cursor.var(oracledb.DB_TYPE_NUMBER)
run_id.setvalue(0, 3)

cursor.callproc(
    "oracledb_ref_cursor_repro.get_row",
    [identifier, run_id, result],
)

Binding the same values as strings succeeds:

cursor.callproc(
    "oracledb_ref_cursor_repro.get_row",
    ["1", "3", result],
)

Observed output:

callproc returned in 0.031s
fetch returned in 0.000s: (1, 3)

Calling the procedure entirely from database-side PL/SQL also succeeds.

This appears similar to #169, but it reproduces with python-oracledb 4.0.2
using the self-contained package below.

4. Does your application call init_oracle_client()?

No. It does not call oracledb.init_oracle_client().

The problem occurs in python-oracledb Thin mode:

oracledb.is_thin_mode(): True

5. Runnable reproduction

First create the package:

create or replace package oracledb_ref_cursor_repro as
  procedure get_row(
      p_id in number
    , p_run_id in number
    , p_result out sys_refcursor
  );
end oracledb_ref_cursor_repro;
/

create or replace package body oracledb_ref_cursor_repro as
  procedure get_row(
      p_id in number
    , p_run_id in number
    , p_result out sys_refcursor
  ) is
  begin
    open p_result for
      select p_id as id, p_run_id as run_id
        from dual;
  end get_row;
end oracledb_ref_cursor_repro;
/

Then run:

import os
import platform
import sys
import time

import oracledb


print("platform.platform:", platform.platform())
print("sys.maxsize > 2**32:", sys.maxsize > 2**32)
print("platform.python_version:", platform.python_version())
print("oracledb.__version__:", oracledb.__version__)
print("oracledb.is_thin_mode():", oracledb.is_thin_mode())

connection = oracledb.connect(
    user=os.environ["DB_USER"],
    password=os.environ["DB_PASSWORD"],
    dsn=os.environ["DB_DSN"],
)
connection.call_timeout = 5_000

with connection:
    print("connection.version:", connection.version)

    with connection.cursor() as cursor:
        result = cursor.var(oracledb.DB_TYPE_CURSOR)

        started = time.monotonic()
        cursor.callproc(
            "oracledb_ref_cursor_repro.get_row",
            [1, 3, result],
        )
        print(f"callproc returned in {time.monotonic() - started:.3f}s")

        result_cursor = result.getvalue()
        try:
            print("result:", result_cursor.fetchone())
        finally:
            result_cursor.close()

Expected behavior: Python integers or explicit DB_TYPE_NUMBER variables bind
to the PL/SQL NUMBER parameters and the procedure returns its REF CURSOR.

If the bind configuration is invalid, an immediate actionable exception would
be preferable to a timeout and lost connection.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the supplied oracledb_ref_cursor_repro.sql and oracledb_ref_cursor_repro.py reproductions in Thin mode, comparing numeric and string binds. Then inspect the callproc path in oracledb/cursor.py and the Thin implementation at src/oracledb/impl/thin/cursor.pyx and protocol.pyx. Done means NUMBER inputs return the REF CURSOR correctly, or invalid binding produces an immediate actionable exception rather than a hang.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, sql
Domain
backend-api-design, databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.