Skip to content

Heisenbug that kills process via SIGALRM #133687

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
johndoe31415 opened this issue May 8, 2025 · 2 comments
Open

Heisenbug that kills process via SIGALRM #133687

johndoe31415 opened this issue May 8, 2025 · 2 comments
Labels
pending The issue will be closed if no feedback is provided type-bug An unexpected behavior, bug, or error

Comments

@johndoe31415
Copy link

johndoe31415 commented May 8, 2025

Bug report

Bug description:

First of all, I know exactly how this bug is going to sound: batshit insane. I implore you to keep reading.

I've had a timer application for ages that uses signal.alarm() concurrently with input() so that it has a rudimentary way of displaying things in the background while still allowing user interaction on the CLI. The gist of it is this:

@staticmethod
def _input_timeout(prompt, timeout_secs):
	class TimeExpiredException(Exception): pass

	def raise_exception(signum, frame):
		raise TimeExpiredException()

	signal.signal(signal.SIGALRM, raise_exception)
	try:
		signal.alarm(timeout_secs)
		result = input(prompt)
		signal.alarm(0)
	except TimeExpiredException:
		result = None
	return result

This program has mysteriously stopped working with Python 3.13.3 with an exceptionally strange error mode: my signal handler is called from somewhere I apparently do not expect (HelpFormatter), causing the whole process to terminate. This always happens after exactly 29 seconds when I run my process as user and after exactly 25 seconds when I run as root. It happens only when there is no __pycache__ present, i.e., when the Python bytecode is generated on startup.

I tried creating a minimal reproducer, which was super difficult. Even when I remove dead code from the Python script, the bug stops triggering. Some code removal was fine, but for example if I remove the wrapper around argparse, the bug disappears.

As I said. This sounds batshit crazy.

When the mystery signal is caught, it gives one final indication where it comes from:

reliant [/tmp/heisenbug]: rm -fr __pycache__/ && time ./stopwatch 
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Exception ignored in tp_clear of HelpFormatter:
Traceback (most recent call last):
  File "/tmp/heisenbug/./stopwatch", line 69, in raise_exception
    raise TimeExpiredException()
StopWatch._input_timeout.<locals>.TimeExpiredException: 
FINALLY

real	0m25,035s
user	0m0,025s
sys	0m0,011s

Note the message: Exception ignored in tp_clear of HelpFormatter: (this was running as root, you see that it took 25 seconds.)

Just so you believe me that it always triggers after the same time, here are three consecutive runs as root (I swear this is not copy paste, timings are accurate to millisecond level!):

reliant [/tmp/heisenbug]: rm -fr __pycache__/ && time ./stopwatch 
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Exception ignored in tp_clear of HelpFormatter:
Traceback (most recent call last):
  File "/tmp/heisenbug/./stopwatch", line 69, in raise_exception
    raise TimeExpiredException()
StopWatch._input_timeout.<locals>.TimeExpiredException: 
FINALLY

real	0m25,035s
user	0m0,025s
sys	0m0,011s

reliant [/tmp/heisenbug]: rm -fr __pycache__/ && time ./stopwatch 
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Exception ignored in tp_clear of HelpFormatter:
Traceback (most recent call last):
  File "/tmp/heisenbug/./stopwatch", line 69, in raise_exception
    raise TimeExpiredException()
StopWatch._input_timeout.<locals>.TimeExpiredException: 
FINALLY

real	0m25,035s
user	0m0,025s
sys	0m0,011s

reliant [/tmp/heisenbug]: rm -fr __pycache__/ && time ./stopwatch 
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Exception ignored in tp_clear of HelpFormatter:
Traceback (most recent call last):
  File "/tmp/heisenbug/./stopwatch", line 69, in raise_exception
    raise TimeExpiredException()
StopWatch._input_timeout.<locals>.TimeExpiredException: 
FINALLY

real	0m25,035s
user	0m0,025s
sys	0m0,010s

And here some runs as user:

reliant joe [/tmp/heisenbug]: rm -fr __pycache__ && time ./stopwatch 
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 Exception ignored in tp_clear of HelpFormatter:
Traceback (most recent call last):
  File "/tmp/heisenbug/./stopwatch", line 69, in raise_exception
    raise TimeExpiredException()
StopWatch._input_timeout.<locals>.TimeExpiredException: 
FINALLY

real	0m29,038s
user	0m0,029s
sys	0m0,010s

reliant joe [/tmp/heisenbug]: rm -fr __pycache__ && time ./stopwatch 
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 Exception ignored in tp_clear of HelpFormatter:
Traceback (most recent call last):
  File "/tmp/heisenbug/./stopwatch", line 69, in raise_exception
    raise TimeExpiredException()
StopWatch._input_timeout.<locals>.TimeExpiredException: 
FINALLY

real	0m29,039s
user	0m0,027s
sys	0m0,012s

reliant joe [/tmp/heisenbug]: rm -fr __pycache__ && time ./stopwatch 
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 Exception ignored in tp_clear of HelpFormatter:
Traceback (most recent call last):
  File "/tmp/heisenbug/./stopwatch", line 69, in raise_exception
    raise TimeExpiredException()
StopWatch._input_timeout.<locals>.TimeExpiredException: 
FINALLY

real	0m29,038s
user	0m0,029s
sys	0m0,009s

I'm running on 6.14.0-15-generic #15-Ubuntu. My CPU is an AMD Ryzen Threadripper PRO 3945WX 12-Cores. I'm using the Ubuntu-provided Python package (Ubuntu calls it 3.13.3-1):

Python 3.13.3 (main, Apr  8 2025, 19:55:40) [GCC 14.2.0] on linux

I cannot reproduce this issue on Debian with Python 3.11/ARM nor on another Ubuntu with Python 3.12/x86_64.

I cannot attach files (the reproducer) to this bug, so here it is as base64:

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
3dZt3dZt3dZt3dZt3dZt3faN2v8Au7ZfrgBQAAA=

You can simply unpack it by doing:

$ openssl base64 -d | tar xvz

And pasting the above base64 blob in. Remember you have to clear __pycache__ to trigger the bug. I'm including the bytecode in case it contains something interesting.

Please someone tell me this is reproducible and I'm not completely losing my marbles.

CPython versions tested on:

3.13

Operating systems tested on:

Linux

@johndoe31415 johndoe31415 added the type-bug An unexpected behavior, bug, or error label May 8, 2025
@johndoe31415 johndoe31415 changed the title Heisenbug that kills process via SIGALARM Heisenbug that kills process via SIGALRM May 8, 2025
@colesbury
Copy link
Contributor

I haven't looked at the reproducer yet, but this is understandable if unexpected behavior. Exceptions raised during destructors (i.e., __del__ methods) get printed as unraisable exceptions and then swallowed:

https://docs.python.org/3/library/sys.html#sys.unraisablehook

That's not new behavior and it's pretty much a fundamental limitation of raising exception asynchronously, such as with signal.signal(). Most likely, the timing of things slightly changed in 3.13 such that you routinely get "unlucky" as to what's executing when the SIGALRM triggers.

@johndoe31415
Copy link
Author

Interesting, so are you saying that raising an exception is always undefined behavior in Python, always has been?

The thing that makes me quite curious is that I do believe that in the scenario, this does look like it is well-defined behavior. I'm setting signal.alarm() inside the try block, then cancel it before leaving the try block. Any SIGALRM should call the installed signal handler and the raised exception should be possible to stack-unwind (there is only a single main thread there). In this instance, it seems to be the case that the signal is delivered while outside the try...except block, which should never be possible.

Can you try to elaborate why this would be happening?

I would be fine with the exception being omitted (e.g., during __del__, as you described) and being silently discarded. But somehow my program is not always able to catch the raised exception, which is indeed a problem.

Thanks for taking the time to read through my report! Cheers.

@picnixz picnixz added the pending The issue will be closed if no feedback is provided label May 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pending The issue will be closed if no feedback is provided type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

3 participants