Thin mode: segfault when a cached query is executed again after its first execute failed with ORA-01438
评估
- 难度
- 4/5
- 预计耗时
- 1-2 天
- 新手友好度
- 38/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 技术栈
- python
调研方向
Start with MessageWithData._process_error_info() and ExecuteMessage.process() in src/oracledb/impl/thin/messages/base.pyx, then trace how cached statements are re-executed and row data is processed. Reproduce with the provided script in Thin mode using statement caching enabled; stmtcachesize=0 avoids the crash. Done means the second execution returns its row without a segmentation fault, with synchronous and asynchronous regression coverage.
由索引模型根据 Issue 内容生成。
描述
- What versions are you using?
connection.version: 18.0.0.0.0 (Oracle Database 18c XE 18.4, Docker image gvenzl/oracle-xe:18.4.0-slim)
platform.platform: macOS-27.0.1-arm64-arm-64bit
sys.maxsize > 2**32: True
platform.python_version: 3.12.14
oracledb.__version__: 26.1.0
I also reproduced it with 26.0.1, with 4.0.2 and with main at 4c669ddb6ed18274d394f3d0116321859652f24b (built from source, reports 26.2.0b1).
- Is it an error or a hang or a crash?
A crash. The Python process ends with a segmentation fault.
- What error(s) or behavior you are seeing?
In Thin mode, a query fails with ORA-01438 the first time it is executed on a connection. That is correct. The next execute of the same SQL text on the same connection crashes the process:
$ python -u repro.py
oracledb 26.1.0 database 18.0.0.0.0
first execute: ORA-01438: value larger than specified precision allowed for this column
Help: https://docs.oracle.com/error-help/db/ora-01438/
Fatal Python error: Segmentation fault
Current thread 0x00000001ed09bf80 (most recent call first):
File ".venv/lib/python3.12/site-packages/oracledb/cursor.py", line 898 in wrapped_f
File "repro.py", line 24 in <module>
Extension modules: oracledb.arrow_impl, oracledb.base_impl, oracledb.thick_impl, _cffi_backend, oracledb.thin_impl (total: 5)
The macOS crash report shows EXC_BAD_ACCESS (SIGSEGV), KERN_INVALID_ADDRESS, with the top frame in oracledb.thin_impl.MessageWithData.
What I found:
- It occurs with
oracledb.connect()and withoracledb.connect_async(). - It needs the statement cache. With
stmtcachesize=0there is no crash. - There is no crash if the first execute of the statement succeeds and a later execute fails with ORA-01438.
- There is no crash if the first execute fails with ORA-01722 or ORA-01476 instead.
- I did not test Thick mode. I did not test other database versions.
The cause, as far as I can tell from the source (main at 4c669ddb):
- The first execute is a full execute (parse, execute, fetch). The database raises ORA-01438 while it produces the first row, so the response contains only the error information (with a cursor id). No describe information arrives, and
Statement._fetch_var_implsstaysNone. MessageWithData._process_error_info()insrc/oracledb/impl/thin/messages/base.pyxstores the cursor id on the statement. Error 1438 is inERR_INTEGRITY_ERROR_CODES, soclear_cursor()is not called.ExecuteMessage.process()then sets_executed = True. The statement returns to the statement cache with a cursor id, with_executed = Trueand without fetch variables.- The next execute gets this statement from the cache.
ExecuteMessage._write_message()selectsTNS_FUNC_REEXECUTE_AND_FETCH.MessageWithData._preprocess_query()returns early because_fetch_var_implsisNone, soout_var_implsstaysNone. - The database returns the row header and the row data, without describe information.
MessageWithData._process_row_data()iterates overself.out_var_impls, which isNone. The generated C code has noNonecheck at that point, so it readsNoneas a list.
With PYO_DEBUG_PACKETS=1, the second request has function code 0x4E, and its response starts with message types 6 and 7 (there is no message type 16).
This explains the conditions above: an error that is not in ERR_INTEGRITY_ERROR_CODES clears the cursor, and a statement that succeeded before already has its fetch variables.
A possible fix is to clear the cursor in this case too, so that the next execute is a full execute and gets the describe information:
--- a/src/oracledb/impl/thin/messages/base.pyx
+++ b/src/oracledb/impl/thin/messages/base.pyx
@@ -1357,7 +1357,9 @@ cdef class MessageWithData(Message):
self.retry = True
conn_impl._statement_cache.clear_cursor(cursor_impl._statement)
elif self.error_info.num != 0 and self.error_info.cursor_id != 0:
- if self.error_info.num not in errors.ERR_INTEGRITY_ERROR_CODES:
+ if self.error_info.num not in errors.ERR_INTEGRITY_ERROR_CODES \
+ or (cursor_impl._statement._is_query
+ and cursor_impl._statement._fetch_var_impls is None):
conn_impl._statement_cache.clear_cursor(cursor_impl._statement)
With this change on main, the script passes. Two new tests with the same sequence (one with a sync connection, one with an asyncio connection) pass too; both tests segfault without the change. tests/cursor on this XE 18c database has the same result with and without the change: test_cursor_1614 and test_cursor_1714 fail with ORA-00600 [kposcws-0] from the database (the released 26.1.0 fails in the same way), and all other tests pass.
Not tested: the elif self.retry: branch above it also keeps the cursor id for any error, so a failed retry may leave the statement in the same state.
The change, with the two tests and a release note line, is in pull request #632.
Workaround for now: stmtcachesize=0.
- Does your application call init_oracle_client()?
No. This is Thin mode.
- Include a runnable Python script that shows the problem.
No schema objects are necessary.
import faulthandler
import os
import oracledb
faulthandler.enable()
conn = oracledb.connect(
user=os.environ["PYO_USER"],
password=os.environ["PYO_PASSWORD"],
dsn=os.environ["PYO_DSN"],
)
print("oracledb", oracledb.__version__, "database", conn.version)
sql = "select cast(:1 as number(1)) from dual"
with conn.cursor() as cursor:
try:
cursor.execute(sql, [10])
except oracledb.DatabaseError as e:
print("first execute:", e)
with conn.cursor() as cursor:
cursor.execute(sql, [5])
print("second execute:", cursor.fetchall())
- 主要语言
- Python
- 星标
- 458
- 派生
- 123
- PR 合并指标
- 30 天内没有已合并 PR
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
oracle/python-oracledb 的其他 Issue
-
bug
难度 3/5 1-2 天 新手友好度 57/100
oracle/python-oracledb#636 · 1 条评论 ·
-
Thin mode: an outputtypehandler redefining a CLOB as DB_TYPE_LONG misreads the reply when the statement is cached可能重新可做 关联的 PR 已关闭且未合并。 未关闭
难度 4/5 3-5 天 新手友好度 35/100
oracle/python-oracledb#634 ·
-
难度 4/5 3-5 天 新手友好度 55/100
oracle/python-oracledb#633 ·
-
难度 4/5 3-5 天 新手友好度 48/100
oracle/python-oracledb#630 ·
-
难度 4/5 3-5 天 新手友好度 15/100
oracle/python-oracledb#629 ·
查看 oracle/python-oracledb 的全部 Issue
相似的 Issue
-
SslLibrary crashes with unhandled OSError: port/proto not found on non-standard SSL ports可能已有人在做 @himanchalkaushale 今天认领。 未关闭
难度 2/5 1-3 小时 新手友好度 82/100
OWASP/Nettacker#1884 · 1 条评论 ·
维护者通常 1 天内回复
-
A-connection-core C-enhancement
难度 2/5 1-3 小时 新手友好度 68/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 88/100
aws-samples/sample-per-model-bedrock#869 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 72/100
维护者通常 1 天内回复
-
enhancement
难度 2/5 1-3 小时 新手友好度 82/100
fzn0x/watchtower#17 ·