What about interruptible threads

Yeah, this is one of those classic traps: it’s almost useful, it seems to work great in lots of cases, but if you want your software to be actually reliable then this kind of thread cancellation just… cannot be made workable. And sometimes you don’t discover that until you’ve already shipped it. (Like happened to Java and Win32. They have regrets).

For example:

try:
   do_stuff()  # <-- if an exception is raised here, the finally block will clean things up
finally:
   # <-- if an exception is raised here, whoops, no cleanup
   cleanup()

And putting a with nointerrupts() inside the finally block doesn’t help, because the interrupt might happen just before the call to with nointerrupts().

Or consider:

jobs = [...]
while jobs:
    job = jobs.pop()
    execute_job(job)

Suppose this gets cancelled. No problem! You can just look at the jobs list to see which jobs have executed and which haven’t. Except… that doesn’t work, because you can’t distinguish between an interrupt that fires before or after the pop. And this applies to like, any non-trivial state manipulation your program might do. Mayyyybe if you audit every single place in your program, and all third party libraries, that have any side-effects, and carefully disable interrupts etc., you could get your one program to work, but no-one can live like that.

(Thread cancellation actually does work great in Haskell, because you can make sure that the thread you’re cancelling has no possible side-effects. But Python isn’t Haskell :-).)

We mostly get away with it for KeyboardInterrupt, because most programs exit after KeyboardInterrupt, so any corrupted state gets wiped clean. And if once in 100 times, it does corrupt your program’s state and makes the program crash… well, crashing is like exiting, so the user won’t be too upset? (Thank you for playing Wing Commander.)

IMO PyThreadState_SetAsyncExc shouldn’t exist. There’s no way to write reliable programs using it, and it’s not even a good implementation of thread cancellation, because if a thread is stuck waiting on some I/O, you can’t interrupt it this way. It’s grandfathered in because it got added as a kind of experimental gimmick back in 2003, and I think IDLE used to use it? But you shouldn’t use it in new code.

8 Likes