asyncio: DPY-4011 on connect_async() when the listener closes the original socket after a redirect
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 35/100
Hướng nghiên cứu
Start with BaseAsyncProtocol._connect_tcp() and BaseAsyncProtocol.connection_lost() in the asyncio protocol implementation; the report identifies the unguarded waiter handling when the old redirected transport closes. Run the supplied fake-listener reproducer, which needs no database, and verify that the redirected connection waits until the 3-second timeout rather than failing early with DPY-4011. Pull request #617 is already open with a proposed fix.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
- What versions are you using?
- Oracle Database 19.28.0.0.0 (RAC, the listener redirects every connection to another address)
- python-oracledb 26.0.1 (Thin mode). Also reproduced with a build of
main. platform.platform: Linux-6.18.40.1-microsoft-standard-WSL2-x86_64-with-glibc2.39sys.maxsize > 2**32: Trueplatform.python_version: 3.14.3(same result with 3.13.2)
- Is it an error or a hang or a crash?
An error.
- What error(s) or behavior you are seeing?
oracledb.connect_async() and oracledb.create_pool_async() always fail with
oracledb.exceptions.DatabaseError: DPY-4011: the database or network closed the connection
about 0.3 seconds after the connect starts. oracledb.connect() with the same parameters works, with or without disable_oob=True and with either server_type.
With PYO_DEBUG_PACKETS the sync and async traces are the same up to the second authorization request. The async trace ends there:
Sending request authorization [op 17] on socket 7
Disconnecting transport [op 18] on socket 7
Wrapping the protocol passed to loop.create_connection() shows that the socket that gets closed is not the one in use (addresses replaced):
[0.050s] fd 6 connected to LISTENER:1521
[0.092s] fd 6 received 378 bytes <- redirect
[0.134s] fd 7 connected to NODE:1521
[0.176s] fd 7 received 8 bytes
[0.217s] fd 7 received 45 bytes <- accept
[0.259s] fd 7 received 191 bytes
[0.302s] fd 7 received 2717 bytes
[0.344s] fd 7 received 371 bytes <- auth phase one response
[0.376s] fd 6 EOF received from LISTENER:1521
[0.376s] fd 6 connection_lost exc=None
[0.376s] DPY-4011: the database or network closed the connection
[0.376s] fd 7 connection_lost exc=None <- closed by the driver after the error
Cause
After a redirect the driver opens a second transport using the same protocol object (lambda: self in BaseAsyncProtocol._connect_tcp()), and the transport to the listener is left open. In this environment the listener closes its side roughly 0.3 seconds later, while the driver is waiting for a packet on the new transport. BaseAsyncProtocol.connection_lost() is then called for the old transport:
def connection_lost(self, exc):
if not self._in_connect:
...
if self._read_buf._waiter is not None \
and not self._read_buf._waiter.done():
error = errors._create_err(errors.ERR_CONNECTION_CLOSED)
self._read_buf._waiter.set_exception(error.exc_type(error))
The first block is skipped because _in_connect is set, as the docstring intends ("losing the connection is ignored since this can happen normally when a listener redirect is encountered"). The second block is not guarded, so the pending wait on the healthy transport is failed with DPY-4011. When the listener closes its socket before the driver starts waiting on the new transport there is no pending waiter, which is presumably why this is not seen everywhere.
The unguarded block is already present in 3.4.2 and 4.0.0, so this is not new in 26.x.
- Does your application call init_oracle_client()?
No. Thin mode.
- Include a runnable Python script that shows the problem.
No database is needed. A fake listener answers the CONNECT packet with a REDIRECT and closes its socket 0.3 seconds later; the redirected server accepts the TCP connection and stays silent. The driver should keep waiting on the redirected connection until the 3 second timeout.
"""
Reproduces DPY-4011 with asyncio and a listener redirect, without a database.
A fake listener answers the CONNECT packet with a REDIRECT to a second server
and closes its own socket only 0.3 seconds later. The second server accepts the
TCP connection and stays silent, so the driver should keep waiting on it until
the 3 second timeout below expires.
"""
import asyncio
import struct
import time
import oracledb
LISTENER_CLOSE_DELAY = 0.3
async def main():
async def redirected_server(reader, writer):
await reader.read() # never answers; waits for the client to give up
writer.close()
target = await asyncio.start_server(redirected_server, "127.0.0.1", 0)
target_port = target.sockets[0].getsockname()[1]
async def listener(reader, writer):
await reader.read(8192) # CONNECT packet
data = (
f"(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT={target_port}))"
"\x00(DESCRIPTION=(CONNECT_DATA=(SERVICE_NAME=svc)))"
).encode()
# REDIRECT packet (type 5) followed by a DATA packet (type 6) with
# the redirect data, as sent by an Oracle Database 19c listener
writer.write(struct.pack(">HHBBHH", 10, 0, 5, 0, 0, len(data)))
writer.write(
struct.pack(">HHBBHH", 10 + len(data), 0, 6, 0, 0, 0) + data
)
await writer.drain()
await asyncio.sleep(LISTENER_CLOSE_DELAY)
writer.close() # the listener closes its side late
server = await asyncio.start_server(listener, "127.0.0.1", 0)
listener_port = server.sockets[0].getsockname()[1]
start = time.monotonic()
try:
await asyncio.wait_for(
oracledb.connect_async(
user="u",
password="p",
dsn=f"127.0.0.1:{listener_port}/svc",
retry_count=0,
),
3,
)
except asyncio.TimeoutError:
print(
f"{oracledb.__version__}: OK - still waiting on the redirected "
f"connection after {time.monotonic() - start:.1f}s"
)
except oracledb.Error as e:
print(
f"{oracledb.__version__}: BUG - failed after "
f"{time.monotonic() - start:.1f}s: {e.__cause__ or e}"
)
server.close()
target.close()
asyncio.run(main())
Output with 26.0.1:
26.0.1: BUG - failed after 0.3s: DPY-4011: the database or network closed the connection
Output with the fix proposed in the linked pull request:
26.1.0b1: OK - still waiting on the redirected connection after 3.0s
- Ngôn ngữ chính
- Python
- Star
- 454
- Fork
- 119
- Chỉ số merge pull request
- Không có pull request nào được merge trong 30 ngày
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của oracle/python-oracledb
-
bug
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
oracle/python-oracledb#615 ·
-
Fetch character columns as raw bytes in the database character set into data frames (Thick mode)Đang mởenhancement
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
oracle/python-oracledb#614 · 1 bình luận ·
-
Client Library or Database
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 35/100
oracle/python-oracledb#613 · 3 bình luận ·
-
enhancement
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 40/100
oracle/python-oracledb#612 ·
-
Allow Cursor.execute() to preserve leading and trailing SQL whitespace to avoid SQL_ID changesĐang mởenhancement
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 55/100
oracle/python-oracledb#611 · 4 bình luận ·
Tất cả issue của oracle/python-oracledb
Issue tương tự
-
Atomic append fails to create a missing fileCó thể đã có người làm @harbinresearcher đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
fabiocaccamo/python-fsutil#201 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 1 ngày
-
task
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
danparshall/claude_researcher#87 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
GhostManager/Ghostwriter#998 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
datajoint/datajoint-python#1586 ·
Maintainer thường phản hồi trong vòng 1 ngày