Skip to content

Keyboard Interrupts

Some imports can change how the program handles Ctrl-C while it waits. The ropt[polars] extra installs Polars; affected Polars versions change a process-wide signal setting on import, and ropt loads Polars when it is installed. Until the Polars issue is fixed in a release, installing this extra can trigger the problem when ropt is imported. This is not a ropt pool limitation. Other libraries imported by your program may cause the same problem even without Polars. Check whether Ctrl-C interrupts your script while it waits for a thread or process pool. If it does not, call restore_keyboard_interrupt after the imports:

from ropt.utils import restore_keyboard_interrupt

restore_keyboard_interrupt()

The symptom

You press Ctrl-C while the program is waiting and it does not respond until the wait ends. A wait for evaluations may last indefinitely, so the optimization can appear to ignore Ctrl-C.

It affects the whole program, not just the optimization: anything else that waits stops responding to Ctrl-C too.

What causes it

Ctrl-C raises SIGINT, which CPython turns into a KeyboardInterrupt — but only once the interrupted thread is back in the interpreter. A thread parked in Queue.get, Event.wait, Thread.join, Future.result or Lock.acquire is not there while it waits, and whether the signal breaks that wait depends on a process-global flag called SA_RESTART.

CPython leaves the flag clear, so waits are interruptible. Some third-party extension modules install their own SIGINT handler with the flag set when they are imported. Because the flag is process-global, the effect is not confined to whoever set it: from that import onwards, Ctrl-C cannot break into any wait anywhere in the program.

When Polars is installed, a direct import polars is not required to trigger the issue: ropt loads it. Without Polars, check the other packages your program imports: any package that sets the flag can cause the same symptom.

Where to call it

If Ctrl-C does not interrupt a wait in your program, call the helper after the imports, because a later import can set the flag again. If you import a package lazily, inside a function, call it after that import instead. You can also check the behavior without polars installed, or after a Polars upgrade; the workaround is needed only if an imported package still changes the setting.

Why ropt does not do this for you

SA_RESTART is process-global state that belongs to your program. A library that quietly changes it does so for every other part of that program, including parts that deliberately installed the handler in question — and it cannot put the flag back afterwards without restoring the very problem it was called to fix.

There is also no point at which a library could do it reliably. Any import that happens later can set the flag again, and ropt does not control when your program imports things.

So the decision is left where it can be made: in the application, by someone who knows whether the trade matters.

What it does, exactly

It calls signal.siginterrupt(signal.SIGINT, True), which clears the flag and does nothing else. In particular it does not replace the SIGINT handler, so a package that chained its own keeps working; signal.signal would replace it.

It is a one-way switch on purpose. There is no context-manager form, because restoring the flag on exit would restore the hang, and a clear flag is CPython's own default in any case.

Calling it more than once is harmless, and it may be called from any thread.

Platforms

There is no SA_RESTART on Windows and therefore no problem to fix; the call is a no-op there.