Fix CancelledError handling in connection pool to prevent connection leaks

Problem: High-concurrency streaming requests with client disconnects trigger
asyncio.CancelledError (BaseException in Python 3.8+), but exception handlers
only caught Exception, causing:
- yielded_sqlor.used permanently stays True
- sqlor.exit() never called
- Connection slots permanently consumed, eventual pool exhaustion

Fix:
1. SqlorPool.context(): Use try/finally instead of try/except to always
   reset yielded_sqlor.used=False, even on CancelledError
2. DBPools.sqlorContext(): Change except Exception to except BaseException,
   add finally block to always call sqlor.exit(), suppress CancelledError
   logging noise

Verification: Ad-hoc tests confirm connections properly reset on cancellation
and can be reused. Normal exceptions still propagate correctly.
This commit is contained in:
yumoqing 2026-06-27 14:40:06 +08:00
parent a2ef1bc48a
commit 627b955897

View File

@ -126,13 +126,10 @@ class SqlorPool:
yielded_sqlor.use_at = time.time()
try:
yield yielded_sqlor.sqlor
finally:
# Always reset state, even on CancelledError (BaseException in Python 3.8+)
yielded_sqlor.used = False
yielded_sqlor.use_at = time.time()
return
except Exception as e:
yielded_sqlor.used = False
yielded_sqlor.use_at = time.time()
raise e
@SingletonDecorator
@ -178,17 +175,23 @@ class DBPools(EventDispatcher):
yield sqlor
if sqlor and sqlor.dataChanged:
await sqlor.commit()
await sqlor.exit()
except Exception as e:
except BaseException as e:
self.e_except = e
cb = format_exc()
exception(f'sqlorContext():EXCEPTION{e}, {cb}')
if not isinstance(e, asyncio.CancelledError):
cb = format_exc()
exception(f'sqlorContext():EXCEPTION{e}, {cb}')
try:
await sqlor.rollback()
except:
pass
await sqlor.exit()
raise e
raise
finally:
# Always call exit(), even on CancelledError (BaseException in Python 3.8+)
if sqlor:
try:
await sqlor.exit()
except:
pass
def get_exception(self):
return self.e_except