oracle / odpi

ODPI-C: Oracle Database Programming Interface for Drivers and Applications
https://oracle.github.io/odpi/
Other
264 stars 75 forks source link

When ODPI tries to translate a variable to a temporary LOB, and there is inadequate TEMP tablespace, ODPI segfaults when the cursor is closed #168

Closed charliejacobsen closed 2 years ago

charliejacobsen commented 2 years ago
  1. What version of ODPI-C are you using (see dpi.h)? v4.0.2 (via cx_Oracle 8.0.1)

  2. What OS (and version) is your application on? CentOS 8

  3. What compiler (and version) did you use? Using the build incorporated into cx_Oracle 8.0.1.

  4. What is your version of the Oracle Client (e.g. Instant Client)? How was it installed? Where it is installed? Oracle Instant Client 19.5, installed from public Oracle repo. Installed under standard location, /usr/lib/oracle.

  5. What is your Oracle Database version? Oracle 21c XE

  6. What is the PATH environment variable (on Windows) or LD_LIBRARY_PATH (on Linux) set to? LD_LIBRARY_PATH is empty (but I do have some /etc/ld.so.conf files, one of which contains /usr/lib/oracle/19.5, so I guess you could say it's not really empty). I suspect this is N/A though.

  7. What environment variables did you set? How exactly did you set them? This is probably N/A.

  8. What problem or error(s) you are seeing? My Python3 program encounters a segfault when it tries to execute a PL/SQL anonymous block that involves a BLOB. The segfault seems to happen after an error is thrown, and inside a finally where I try to close the cursor.

This is the error backtrace in gdb, notice ptr=0x0 in dpiGen__setRefCount:

(gdb) bt
#0  0x00007f95603d2abf in raise () from /lib64/libpthread.so.0
#1  0x00007f95570fec4f in skgesigOSCrash ()
   from /usr/lib/oracle/19.5/client64/lib/libclntsh.so
#2  0x00007f95577e653d in kpeDbgSignalHandler ()
   from /usr/lib/oracle/19.5/client64/lib/libclntsh.so
#3  0x00007f95570fef22 in skgesig_sigactionHandler ()
   from /usr/lib/oracle/19.5/client64/lib/libclntsh.so
#4  <signal handler called>
#5  dpiGen__setRefCount (ptr=0x0, error=0x7ffedd4a6ad0, increment=-1)
    at odpi/src/dpiGen.c:267
#6  0x00007f955b590048 in dpiStmt__clearBindVars (error=0x7ffedd4a6ad0, 
    stmt=0x55be6420d370) at odpi/src/dpiStmt.c:251
#7  dpiStmt__close (stmt=0x55be6420d370, tag=0x0, tagLength=0, 
    propagateErrors=1, error=0x7ffedd4a6ad0) at odpi/src/dpiStmt.c:322
#8  0x00007f955b591cb6 in dpiStmt_close (stmt=0x55be6420d370, 
    tag=tag@entry=0x0, tagLength=tagLength@entry=0) at odpi/src/dpiStmt.c:1172
#9  0x00007f955b55f5ed in cxoCursor_close (cursor=0x7f955cf47768, 
    args=<optimized out>) at src/cxoCursor.c:655
#10 0x00007f956077d421 in call_function () from /lib64/libpython3.6m.so.1.0
#11 0x00007f956077de08 in _PyEval_EvalFrameDefault ()
   from /lib64/libpython3.6m.so.1.0
#12 0x00007f95607021d4 in gen_send_ex () from /lib64/libpython3.6m.so.1.0
#13 0x00007f95607d854c in _gen_throw () from /lib64/libpython3.6m.so.1.0
  1. Do you have a runnable code snippet to describe the problem? Set up a database with insufficient TEMP tablespace, and try to run this code with a larger file (this is an adaption of some code I am running, it should demonstrate the issue):
    
    filename = "some_file"
    with open(filename, 'rb') as f:
    data = f.read()

conn = cx_Oracle.connect(...) cursor = conn.cursor()

try: cursor.execute(""" DECLARE v_file_handle UTL_FILE.FILE_TYPE; v_buffer RAW(32767); v_amount INTEGER := 32767; v_pos INTEGER := 1; v_blob_len INTEGER; v_blob BLOB; BEGIN -- Open destination file: v_file_handle := UTL_FILE.FOPEN(:directory, :filename, 'wb');

        SELECT :data INTO v_blob FROM DUAL;

        -- Read chunks of the BLOB and write them to the file
        -- until complete.
        v_blob_len := DBMS_LOB.getlength(v_blob);
        WHILE v_pos <= v_blob_len LOOP
            DBMS_LOB.read(v_blob, v_amount, v_pos, v_buffer);
            UTL_FILE.put_raw(v_file_handle, v_buffer, TRUE);
            v_pos := v_pos + v_amount;
        END LOOP;

        -- Close the file.
        UTL_FILE.fclose(v_file_handle);
    END;
    """, directory="DATA_PUMP_DIR", filename=filename, data=data)

finally: cursor.close() conn.close()


<!--

Use a gist for long code, see https://gist.github.com/

Or format code by using three backticks on a line before and after code snippets, for example:

include


-->

I believe the issue is occurring in here:
https://github.com/oracle/odpi/blob/f73a7c13d643b3fe252614bafc930afbd8e287dd/src/dpiStmt.c#L146

`bindVars[i].var` has been `NULL`'d out, and we encounter an error when trying to set up the BLOB (can't extend TEMP tablespace segment, out of room), and the `bindVars[i].var` remains `NULL`. The TEMP tablespace issue triggers an exception, and in the `finally`, `cursor.close()` leads to a segfault, masking the real issue.
cjbj commented 2 years ago

Thanks for the details.

anthony-tuininga commented 2 years ago

Looks like I can replicate it with an even simpler set of code:

data = "A" * (1024 * 1024 * 5)

cursor.execute("""
        declare
            t_Clob          clob;
        begin
            select :data into t_Clob from dual;
        end;""",
        data=data)

Let me see if I can correct that!

anthony-tuininga commented 2 years ago

I have added the patch to ODPI-C. It is quite simple. You can apply the patch yourself and build cx_Oracle...or you can ensure that instead of passing a string to the PL/SQL block you pass a temporary LOB that you have created and populated yourself.

charliejacobsen commented 2 years ago

Thanks 👍 For me this is just in my dev env and I can fix the issue by just making sure there's sufficient tablespace and stuff :)

Just posting it here for community to pick up any fix.

anthony-tuininga commented 2 years ago

Thanks for reporting it!

cjbj commented 2 years ago

ODPI-C 4.4 contains this fix.