Skip to content

Commit 5c551ad

Browse files
committed
Initial impl from Cursor/Composer
- attempts to call winapi directly as unsafe block to allow deregistering ctrl+c handler
1 parent 23f691b commit 5c551ad

File tree

3 files changed

+175
-17
lines changed

3 files changed

+175
-17
lines changed

hf_xet/Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

hf_xet/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,12 @@ tracing = "0.1"
4242
# Unix-specific dependencies
4343
[target.'cfg(unix)'.dependencies]
4444
signal-hook = "0.3"
45+
signal-hook-registry = "1.4"
4546

4647
# Windows-specific dependencies
4748
[target.'cfg(windows)'.dependencies]
4849
ctrlc = "3.4"
50+
winapi = { version = "0.3", features = ["consoleapi", "wincon", "errhandlingapi"] }
4951

5052
[features]
5153
default = ["no-default-cache"] # By default, hf_xet disables the disk cache.

hf_xet/src/runtime.rs

Lines changed: 171 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,85 @@ lazy_static! {
1717
}
1818

1919
#[cfg(unix)]
20-
fn install_sigint_handler() -> Result<(), MultithreadedRuntimeError> {
20+
lazy_static! {
21+
static ref SIGINT_HANDLER_REGISTRATION: RwLock<Option<signal_hook_registry::SigId>> = RwLock::new(None);
22+
}
23+
24+
#[cfg(windows)]
25+
lazy_static! {
26+
static ref SIGINT_HANDLER_INSTALLED: AtomicBool = AtomicBool::new(false);
27+
}
28+
29+
#[cfg(unix)]
30+
fn install_sigint_handler() -> Result<signal_hook_registry::SigId, MultithreadedRuntimeError> {
2131
use signal_hook::consts::SIGINT;
22-
use signal_hook::flag;
2332

2433
// Register the SIGINT handler to set our atomic flag.
25-
// Using `signal_hook::flag::register` allows us to set the atomic flag when SIGINT is received.
26-
flag::register(SIGINT, SIGINT_DETECTED.clone()).map_err(|e| {
34+
// Using signal_hook_registry directly to get a SigId that we can unregister later.
35+
let flag = SIGINT_DETECTED.clone();
36+
let signal_id = unsafe {
37+
signal_hook_registry::register(SIGINT, move || {
38+
flag.store(true, Ordering::SeqCst);
39+
})
40+
}
41+
.map_err(|e| {
2742
MultithreadedRuntimeError::Other(format!("Initialization Error: Unable to register SIGINT handler {e:?}"))
2843
})?;
2944

30-
Ok(())
45+
Ok(signal_id)
46+
}
47+
48+
#[cfg(windows)]
49+
extern "system" fn console_ctrl_handler(
50+
ctrl_type: winapi::shared::minwindef::DWORD,
51+
) -> winapi::shared::minwindef::BOOL {
52+
use winapi::um::wincon;
53+
54+
// Only handle CTRL_C_EVENT
55+
if ctrl_type == wincon::CTRL_C_EVENT {
56+
// Check if we have active operations
57+
let has_active_ops = {
58+
let guard = MULTITHREADED_RUNTIME.read().unwrap();
59+
if let Some((runtime_pid, ref runtime)) = *guard {
60+
runtime_pid == std::process::id() && runtime.external_executor_count() > 0
61+
} else {
62+
false
63+
}
64+
};
65+
66+
if has_active_ops {
67+
// We have active operations, handle it ourselves
68+
SIGINT_DETECTED.store(true, Ordering::SeqCst);
69+
winapi::shared::minwindef::TRUE
70+
} else {
71+
// No active operations, let Python's handler (or default) handle it
72+
// Return FALSE to continue handler chain
73+
winapi::shared::minwindef::FALSE
74+
}
75+
} else {
76+
// For other control events, let default handler process them
77+
winapi::shared::minwindef::FALSE
78+
}
3179
}
3280

3381
#[cfg(windows)]
3482
fn install_sigint_handler() -> Result<(), MultithreadedRuntimeError> {
35-
// On Windows, use ctrlc crate.
36-
// This sets a callback to run on Ctrl-C:
37-
let sigint_detected_flag = SIGINT_DETECTED.clone();
38-
ctrlc::set_handler(move || {
39-
sigint_detected_flag.store(true, Ordering::SeqCst);
40-
})
41-
.map_err(|e| {
42-
MultithreadedRuntimeError::Other(format!("Initialization Error: Unable to register SIGINT handler {e:?}"))
43-
})?;
83+
use winapi::um::consoleapi::SetConsoleCtrlHandler;
84+
use winapi::um::wincon::CTRL_C_EVENT;
85+
86+
// Install our handler using Windows API directly (instead of ctrlc)
87+
// Our handler checks if operations are active:
88+
// - If active: handles Ctrl+C and returns TRUE (stops propagation)
89+
// - If not active: returns FALSE (allows Python's handler or default to handle it)
90+
// This way we don't need to save/restore Python's handler - we just delegate to it
91+
unsafe {
92+
if SetConsoleCtrlHandler(Some(console_ctrl_handler), winapi::shared::minwindef::TRUE) == 0 {
93+
let error = winapi::um::errhandlingapi::GetLastError();
94+
return Err(MultithreadedRuntimeError::Other(format!(
95+
"Initialization Error: Unable to register SIGINT handler. Windows error: {error}"
96+
)));
97+
}
98+
}
4499
Ok(())
45100
}
46101

@@ -54,7 +109,20 @@ fn check_sigint_handler() -> Result<(), MultithreadedRuntimeError> {
54109
let pid = std::process::id();
55110

56111
if stored_pid == pid {
57-
return Ok(());
112+
// Check if handler is already registered
113+
#[cfg(unix)]
114+
{
115+
let reg = SIGINT_HANDLER_REGISTRATION.read().unwrap();
116+
if reg.is_some() {
117+
return Ok(());
118+
}
119+
}
120+
#[cfg(windows)]
121+
{
122+
if SIGINT_HANDLER_INSTALLED.load(Ordering::SeqCst) {
123+
return Ok(());
124+
}
125+
}
58126
}
59127

60128
// Need to install it; acquire a lock to do so.
@@ -63,10 +131,33 @@ fn check_sigint_handler() -> Result<(), MultithreadedRuntimeError> {
63131
// If another thread beat us to it while we're waiting for the lock.
64132
let stored_pid = SIGINT_HANDLER_INSTALL_PID.0.load(Ordering::SeqCst);
65133
if stored_pid == pid {
66-
return Ok(());
134+
#[cfg(unix)]
135+
{
136+
let reg = SIGINT_HANDLER_REGISTRATION.read().unwrap();
137+
if reg.is_some() {
138+
return Ok(());
139+
}
140+
}
141+
#[cfg(windows)]
142+
{
143+
if SIGINT_HANDLER_INSTALLED.load(Ordering::SeqCst) {
144+
return Ok(());
145+
}
146+
}
147+
}
148+
149+
#[cfg(unix)]
150+
{
151+
let signal_id = install_sigint_handler()?;
152+
let mut reg = SIGINT_HANDLER_REGISTRATION.write().unwrap();
153+
*reg = Some(signal_id);
67154
}
68155

69-
install_sigint_handler()?;
156+
#[cfg(windows)]
157+
{
158+
install_sigint_handler()?;
159+
SIGINT_HANDLER_INSTALLED.store(true, Ordering::SeqCst);
160+
}
70161

71162
// Finally, store that we have installed it successfully.
72163
SIGINT_HANDLER_INSTALL_PID.0.store(pid, Ordering::SeqCst);
@@ -94,6 +185,65 @@ fn in_sigint_shutdown() -> bool {
94185
SIGINT_DETECTED.load(Ordering::Relaxed)
95186
}
96187

188+
#[cfg(unix)]
189+
fn restore_sigint_handler() -> Result<(), MultithreadedRuntimeError> {
190+
use signal_hook_registry;
191+
192+
let mut reg = SIGINT_HANDLER_REGISTRATION.write().unwrap();
193+
if let Some(signal_id) = reg.take() {
194+
signal_hook_registry::unregister(signal_id);
195+
}
196+
Ok(())
197+
}
198+
199+
#[cfg(windows)]
200+
fn restore_sigint_handler() -> Result<(), MultithreadedRuntimeError> {
201+
use winapi::um::consoleapi::SetConsoleCtrlHandler;
202+
203+
// Remove our handler by unregistering it
204+
// This allows Python's handler (if any) or the default handler to take over
205+
unsafe {
206+
if SetConsoleCtrlHandler(Some(console_ctrl_handler), winapi::shared::minwindef::FALSE) == 0 {
207+
let error = winapi::um::errhandlingapi::GetLastError();
208+
// Log but don't fail - handler removal is best effort
209+
if cfg!(debug_assertions) {
210+
eprintln!("[debug] Warning: Failed to unregister console control handler. Windows error: {error}");
211+
}
212+
}
213+
}
214+
SIGINT_HANDLER_INSTALLED.store(false, Ordering::SeqCst);
215+
Ok(())
216+
}
217+
218+
fn maybe_restore_sigint_handler() -> Result<(), MultithreadedRuntimeError> {
219+
// Check if runtime exists and has no active operations
220+
let guard = MULTITHREADED_RUNTIME.read().unwrap();
221+
if let Some((runtime_pid, ref runtime)) = *guard
222+
&& runtime_pid == std::process::id()
223+
&& runtime.external_executor_count() == 0
224+
{
225+
// Check if handler is installed
226+
let should_restore = {
227+
#[cfg(unix)]
228+
{
229+
let reg = SIGINT_HANDLER_REGISTRATION.read().unwrap();
230+
reg.is_some()
231+
}
232+
#[cfg(windows)]
233+
{
234+
SIGINT_HANDLER_INSTALLED.load(Ordering::SeqCst)
235+
}
236+
};
237+
238+
if should_restore {
239+
// No active operations, restore handler
240+
drop(guard);
241+
restore_sigint_handler()?;
242+
}
243+
}
244+
Ok(())
245+
}
246+
97247
fn signal_check_background_loop() {
98248
const SIGNAL_CHECK_INTERVAL: Duration = Duration::from_millis(250);
99249

@@ -223,6 +373,10 @@ where
223373
return Err(PyKeyboardInterrupt::new_err(()));
224374
}
225375

376+
// After operation completes, check if we should restore the signal handler
377+
// This allows Python's original handler to be restored when no operations are active
378+
let _ = maybe_restore_sigint_handler();
379+
226380
// Now return the result.
227381
result
228382
}

0 commit comments

Comments
 (0)