Commit 3b8227a
fix(client): harden transaction + pool cancellation discipline
Coordinated fix for six cross-cutting cancellation/lifecycle bugs in
DqliteConnection.transaction() and ConnectionPool. Seven issues land
together (ISSUE-71, 72, 73, 74, 75, 76, 77) because they share the
same code blocks and invariants; splitting them would produce
repeated rewrites of the same methods across multiple PRs.
transaction() rewrites (connection.py):
- ISSUE-73: when body raises AND ROLLBACK also raises, invalidate the
connection (set _protocol=None) so the pool discards it instead of
returning to the queue with a live server-side transaction. The
previous `finally` block cleared _in_transaction unconditionally,
masking the dangling transaction from the pool's _reset_connection
check.
- ISSUE-75: the body-raised ROLLBACK no longer uses
suppress(BaseException). CancelledError / KeyboardInterrupt /
SystemExit propagate (structured-concurrency contract — TaskGroup /
asyncio.timeout() rely on it). Non-cancellation exceptions still
invalidate the connection so the original body exception propagates
cleanly.
- ISSUE-79: cancellation matrix — cancel before BEGIN, in body, during
COMMIT, during ROLLBACK — all four phases now produce consistent
post-conditions: CancelledError propagates, connection state is
terminal (either usable or invalidated).
ConnectionPool rewrites (pool.py):
- ISSUE-77: initialize() now uses asyncio.gather(return_exceptions=True)
and closes survivors before re-raising the first failure. The
previous default-exceptions path cancelled sibling tasks but did NOT
close already-succeeded connections, leaking transports.
- ISSUE-74: the check-`_closed`-then-clear-`closed_event` sequence in
acquire() now runs under self._lock, eliminating the window where a
concurrent close() could set() the event between check and clear
(which would lose the close signal and stall parked waiters until
the per-poll timeout).
- ISSUE-76: acquire()'s except branch now wraps cleanup in a try/
finally with asyncio.shield(self._release_reservation()). The
finally runs even when a second cancellation lands mid-cleanup;
shield keeps the _size decrement uninterruptible so the pool never
loses a reservation under rapid-fire cancellation.
- CRITICAL transport leak fix (flagged by concurrency review on the
initial diff): every `_pool_released = True` now comes AFTER
`await conn.close()`. DqliteConnection.close() has an early-return
guard on _pool_released; setting the flag first made every
pool-side close() a silent no-op, leaking the transport. Applies
to all 8 cleanup branches across acquire() and _release().
connect() failure path (ISSUE-72):
- New _abort_protocol() helper uses
asyncio.wait_for(protocol.wait_closed(), timeout=0.5) so the
transport drains under normal conditions but never hangs on an
unresponsive peer. Different call site than cycle-2 ISSUE-38
(leader-query finally block, where unbounded wait_closed was
correctly rejected for hang reasons); connect() is a retry-loop
path that benefits from bounded draining.
Tests (all RED before this commit, GREEN after):
- tests/test_transaction_cancellation.py (7 tests):
- TestRollbackFailureInvalidatesConnection (ISSUE-73)
- TestRollbackHappyPath: regression for body-raise + clean-rollback
- TestRollbackCancellationPropagates (ISSUE-75)
- TestTransactionCancellationPhases (ISSUE-79): 4-phase matrix
- tests/test_pool_cancellation.py (4 tests):
- TestInitializePartialFailureClosesSurvivors (ISSUE-77)
- TestAcquireCancellationRestoresSize (ISSUE-71 + 76)
- TestCloseWakesAllWaiters (ISSUE-74): message-text assertion
(CI-latency robust) rather than timing
The _FakeConn test double replicates the real DqliteConnection's
_pool_released close-guard via close_effective. Without this, the
transport-leak bug (the critical finding on the initial diff) would
have been invisible to tests.
Two concurrency reviews and one test-quality review ran on the
implementation. The first review caught the _pool_released ordering
bug; the fix applies to every cleanup branch. A follow-up review
verified all 8 branches close-before-flag.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent b68a949 commit 3b8227a
File tree
4 files changed
+817
-47
lines changed- src/dqliteclient
- tests
4 files changed
+817
-47
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
176 | 176 | | |
177 | 177 | | |
178 | 178 | | |
179 | | - | |
180 | | - | |
| 179 | + | |
181 | 180 | | |
182 | 181 | | |
183 | 182 | | |
| |||
187 | 186 | | |
188 | 187 | | |
189 | 188 | | |
190 | | - | |
191 | | - | |
| 189 | + | |
192 | 190 | | |
193 | 191 | | |
194 | 192 | | |
| |||
210 | 208 | | |
211 | 209 | | |
212 | 210 | | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
213 | 230 | | |
214 | 231 | | |
215 | 232 | | |
| |||
383 | 400 | | |
384 | 401 | | |
385 | 402 | | |
386 | | - | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
387 | 423 | | |
388 | 424 | | |
389 | 425 | | |
| |||
409 | 445 | | |
410 | 446 | | |
411 | 447 | | |
412 | | - | |
413 | | - | |
414 | | - | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
415 | 464 | | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
416 | 477 | | |
417 | 478 | | |
418 | 479 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
154 | 154 | | |
155 | 155 | | |
156 | 156 | | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
157 | 164 | | |
158 | 165 | | |
159 | 166 | | |
| |||
163 | 170 | | |
164 | 171 | | |
165 | 172 | | |
166 | | - | |
167 | | - | |
168 | | - | |
169 | | - | |
170 | | - | |
171 | | - | |
172 | | - | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
173 | 192 | | |
174 | 193 | | |
175 | | - | |
176 | | - | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
177 | 198 | | |
178 | 199 | | |
179 | 200 | | |
| |||
292 | 313 | | |
293 | 314 | | |
294 | 315 | | |
295 | | - | |
296 | | - | |
297 | | - | |
298 | | - | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
299 | 323 | | |
300 | 324 | | |
301 | 325 | | |
| |||
337 | 361 | | |
338 | 362 | | |
339 | 363 | | |
340 | | - | |
341 | | - | |
342 | | - | |
343 | | - | |
344 | | - | |
345 | | - | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
346 | 403 | | |
347 | | - | |
348 | | - | |
349 | | - | |
350 | | - | |
351 | | - | |
352 | | - | |
353 | | - | |
354 | | - | |
355 | | - | |
356 | | - | |
357 | | - | |
358 | | - | |
359 | | - | |
360 | | - | |
361 | | - | |
362 | | - | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
363 | 409 | | |
364 | 410 | | |
365 | 411 | | |
| |||
402 | 448 | | |
403 | 449 | | |
404 | 450 | | |
405 | | - | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
406 | 459 | | |
407 | | - | |
408 | 460 | | |
| 461 | + | |
409 | 462 | | |
410 | 463 | | |
411 | 464 | | |
412 | 465 | | |
413 | | - | |
414 | 466 | | |
415 | 467 | | |
| 468 | + | |
416 | 469 | | |
417 | 470 | | |
418 | 471 | | |
419 | | - | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
420 | 475 | | |
421 | 476 | | |
422 | 477 | | |
423 | 478 | | |
| 479 | + | |
424 | 480 | | |
| 481 | + | |
| 482 | + | |
425 | 483 | | |
426 | 484 | | |
427 | 485 | | |
| |||
0 commit comments