当传递给 records 参数的生成器引发异常时,Connection.copy_records_to_table 会卡死,错误消息很长
import asyncio import os import sys import asyncpg DSN = os.environ.get("DSN", "postgres://postgres:postgres@localhost:5432/postgres") HANG_TIMEOUT = float(os.environ.get("HANG_TIMEOUT", "15"))
Length in bytes of the error message raised by the record generator. > 9995 bytes => PostgreSQL rejects the CopyFail frame ("invalid message length") and the deadlock triggers; at or below, the run fails cleanly. The threshold is exact: the frame's int32 length field (4 bytes, counted in itself) + payload + NUL terminator must not exceed PQ_SMALL_MESSAGE_LIMIT (10,000), so 10000 - 4 - 1 = 9995. ERROR_BYTES = int(os.environ.get("ERROR_BYTES", "9996"))
def records(): for i in range(100): yield (i,) raise ValueError("A" * ERROR_BYTES)
async def run_case(): conn = await asyncpg.connect(DSN) try: async with conn.transaction(): await conn.execute("CREATE TEMP TABLE repro_t (b int)") await conn.copy_records_to_table("repro_t", records=records()) except ValueError: # clean failure, bug not reproduced pass finally: await conn.close()
async def main(): task = asyncio.create_task(run_case()) _, pending = await asyncio.wait([task], timeout=HANG_TIMEOUT) if pending: print("Statement timed out, bug reproduced", file=sys.stderr) return 1 print("Statement executed before timeout, bug not reproduced", file=sys.stderr) return 0
if name == "main": os._exit(asyncio.run(main()))
内容来源: MagicStack/asyncpg