Commit 3476104
runtime: the deadline guard's teardown cannot be interrupted into leaking a timer (#121)
The last unverified finding from the 2026-08 sweep, recorded as "traced,
never demonstrated". It is real, and it is demonstrated now: without this
change the regression test records **six** further interrupts queued after
the guard was released.
`fire` queues its async exception while holding `lock`, so a guard already
blocked on that same lock inside `disarm` is handed the exception the moment
it acquires it -- at the next bytecode, which is before `armed` is cleared
and before the re-armed timer is cancelled. `disarm` then propagated and the
50ms timer it was meant to cancel stayed alive, still reading `armed` as
true, re-raising `NodeDeadlineExceeded` into that thread every 50ms for the
life of the thread. On a pooled thread that is an unattributable crash in
whatever ran next -- precisely what
`test_no_interrupt_survives_the_node_that_earned_it` exists to rule out,
reached by a path it did not cover.
The lock was not the flaw. Both sides do take it, as the old comment said;
what the lock cannot do is stop an asynchronous exception arriving between
two bytecodes inside the critical section it protects. So the teardown is
retried rather than abandoned, and clears `armed` outside the lock as a last
resort -- that single store is what stops `fire` re-arming. Swallowing the
interrupt there costs nothing: the guard decides the outcome from
`state["fired"]` once `disarm` returns, and still raises on it.
Mechanism 1 (SIGALRM) is not affected and is unchanged. CPython runs the
Python-level handler at a bytecode boundary, so `setitimer(ITIMER_REAL, 0)`
has already completed when the handler raises, and the existing `finally`
restores the handler and releases the slot.
Two notes on getting the test to say something true, since the first two
attempts did not. Raising from `Timer.cancel` proves nothing -- by then
`armed` is already false, so `fire` returns early and no timer leaks; that
version passed without the fix. And raising from the lock's `__enter__`
holds the lock forever, which deadlocks the very timer under test instead of
letting it spin, reporting a clean zero for the wrong reason. The interrupt
has to be delivered the way CPython delivers it: inside the `with` body,
with the block's exit releasing the lock.
Verified: 120 tests across the budget, async-kernel and lease files, ruff
clean, figure refreshed to 2,190. Red without the fix, green with it.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>1 parent 652d979 commit 3476104
3 files changed
Lines changed: 147 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
254 | 254 | | |
255 | 255 | | |
256 | 256 | | |
257 | | - | |
| 257 | + | |
258 | 258 | | |
259 | 259 | | |
260 | 260 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
333 | 333 | | |
334 | 334 | | |
335 | 335 | | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
336 | 343 | | |
337 | 344 | | |
338 | 345 | | |
| |||
487 | 494 | | |
488 | 495 | | |
489 | 496 | | |
490 | | - | |
491 | | - | |
492 | | - | |
493 | | - | |
494 | | - | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
495 | 530 | | |
496 | 531 | | |
497 | 532 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| 38 | + | |
38 | 39 | | |
39 | 40 | | |
40 | 41 | | |
41 | 42 | | |
42 | 43 | | |
43 | 44 | | |
| 45 | + | |
44 | 46 | | |
45 | 47 | | |
46 | 48 | | |
| |||
741 | 743 | | |
742 | 744 | | |
743 | 745 | | |
| 746 | + | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
| 774 | + | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
| 823 | + | |
| 824 | + | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
| 849 | + | |
0 commit comments