Allowing KeyboardInterrupt to reach a #[pyfunction] #3795
-
Hey there folks, absolutely love PyO3. I'm building some functions that use Win32 functions to automate installation of software on my system. One of the Win32 functions I'm calling is WaitForSingleObject which waits for a handle or times out. Here's the Rust code that implements this using the windows-sys crate to wait for a process to exit. #[pyfunction]
pub fn wait_for_process(process_id: u32, timeout_ms: Option<u32>) -> PyResult<()> {
let timeout_ms = timeout_ms.unwrap_or(INFINITE);
let process_handle = unsafe { OpenProcess(SYNCHRONIZE, FALSE, process_id) };
if process_handle == 0 {
return Err(core::create_pyerr_from_last_error());
}
let result = unsafe { WaitForSingleObject(process_handle, timeout_ms) };
match result {
WAIT_TIMEOUT => Err(PyTimeoutError::new_err(())),
WAIT_FAILED => Err(core::create_pyerr_from_last_error()),
_ => Ok(()),
}
} Naturally this is a blocking operation which can normally be interrupted with Ctrl+C. I can confirm that Ctrl+C aborts the wait if the function is run directly from Rust, but when running from Python, the KeyboardInterrupt event doesn't reach the function to interrupt the operation. I found this question about a similar matter on stackoverflow, but it doesn't really help me as I'm calling a C function from Rust where I can't interrupt the polling. While I may be able to approach this particular problem in a different way, I have several other similar situations which won't have a workaround, such as: #[pyfunction]
pub fn close_window(handle: isize, timeout_ms: u32) -> PyResult<()> {
let result = unsafe {
SendMessageTimeoutW(
handle,
WM_CLOSE,
0,
0,
SMTO_ABORTIFHUNG,
timeout_ms,
ptr::null_mut(),
)
};
if result == 0 {
let last_error = unsafe { GetLastError() };
return match last_error {
ERROR_TIMEOUT => Err(PyTimeoutError::new_err(())),
_ => Err(core::create_pyerr_from_error(last_error)),
};
}
Ok(())
}
#[pyfunction]
pub fn set_text_for_control(handle: isize, text: &str, timeout_ms: u32) -> PyResult<()> {
let text = U16CString::from_str_truncate(text);
let result = unsafe {
SendMessageTimeoutW(
handle,
WM_SETTEXT,
0,
text.as_ptr() as _,
SMTO_ABORTIFHUNG,
timeout_ms,
ptr::null_mut(),
)
};
if result == 0 {
let last_error = unsafe { GetLastError() };
if last_error == ERROR_TIMEOUT {
return Err(PyTimeoutError::new_err(()));
}
return Err(core::create_pyerr_from_error(last_error));
}
Ok(())
} Is there any way to solve this or is it impossible at the current time? Thanks heaps |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 1 reply
-
Interestingly, I've tried the same code via pywin32 which is written in C++, and it seems that I can't Ctrl-C to interrupt those calls either. The code is below for your reference: def wait_for_process(process_id: int, timeout_ms: int | None = None) -> None:
process_handle = win32api.OpenProcess(win32con.SYNCHRONIZE, False, process_id)
result = win32event.WaitForSingleObject(
process_handle, timeout_ms if timeout_ms else win32event.INFINITE
)
if result == win32event.WAIT_TIMEOUT:
raise TimeoutError This makes me think it may not be possible to allow t hose signals to reach Rust or C/C++ when writing Python extensions. But I'd love to be wrong about this of course. 😄 Cheers |
Beta Was this translation helpful? Give feedback.
-
Thank you so much for the detailed reply, really appreciate it. 😄 |
Beta Was this translation helpful? Give feedback.
The Python interpreter will catch signals and queue them internally and hence you would need to periodically call
Python::check_signals
to surface them. Which of course you cannot while you block inWaitForSingleObject
.I think generally speaking if you want timely interrupts via
SIGINT
, you'll need to restructure your code to expect multiple event sources, e.g. useWaitForMultipleObjects
and have an additional event to signal cancellation. You can then run this stuff on a background thread, pollcheck_signals
on the main thread and signal the background thread when you are interrupted. (Or if you use Python's built-in mechanism for waiting on the background thread, you could probably avo…