igraph/misc/handlers.rs
1//! Progress, status and interruption handlers (`igraph_progress.h`,
2//! `igraph_statusbar.h`, `igraph_interrupt.h`).
3//!
4//! igraph keeps one handler of each kind *per thread*. The C handlers take
5//! no user data, so the Rust closures live in thread-local storage of this
6//! module and a C trampoline dispatches to them. Installing a handler
7//! returns a guard, and the handler stays installed while its guard lives.
8//! The guards of each kind form a per-thread stack: the active handler is
9//! the one of the most recently created guard that is still alive, and when
10//! no guard is left, whatever handler was active before the first one is
11//! reinstalled. Guards may be dropped in any order: dropping a guard always
12//! removes exactly its own handler (reinstalling the next one only if it was
13//! the active one).
14//!
15//! The handler closures may call igraph themselves: they run in their own
16//! level of igraph's cleanup ("finally") stack, so even a failing call made
17//! by a handler does not disturb the computation that invoked it. They must
18//! not start ARPACK-based computations while an ARPACK-based computation is
19//! running on the same thread (see [`set_interruption_handler`]).
20
21use super::lossy;
22use crate::{
23 error::{Error, Result, catch_panic, catch_panic_or, ensure_init, resume_panic},
24 ffi::*,
25 igraph_call,
26};
27use std::{
28 cell::RefCell,
29 ffi::{CString, c_char, c_void},
30 marker::PhantomData,
31 ops::ControlFlow,
32 thread::LocalKey,
33};
34
35type ProgressDyn = dyn FnMut(&str, f64) -> ControlFlow<()>;
36type StatusDyn = dyn FnMut(&str) -> ControlFlow<()>;
37type InterruptDyn = dyn FnMut() -> bool;
38
39/// A handler installed by a guard that is still alive.
40struct Entry<F: ?Sized, C> {
41 /// Identifies the guard.
42 id: u64,
43 /// The C handler to install while this entry is on top.
44 c: C,
45 /// The Rust closure of an entry that is not on top (the closure of the
46 /// top entry lives in [`HandlerStack::active`]). `None` for C-only
47 /// handlers, and while the closure is running.
48 parked: Option<Box<F>>,
49}
50
51/// The per-thread stack of installed handlers of one kind.
52struct HandlerStack<F: ?Sized, C> {
53 /// The C handler that was active when the stack was empty.
54 base: C,
55 entries: Vec<Entry<F, C>>,
56 /// The closure of the top entry, with that entry's id; taken out while
57 /// it runs.
58 active: Option<(u64, Box<F>)>,
59 next_id: u64,
60}
61
62type Stack<F, C> = RefCell<HandlerStack<F, C>>;
63
64thread_local! {
65 static PROGRESS: Stack<ProgressDyn, igraph_progress_handler_t> =
66 const { RefCell::new(HandlerStack { base: None, entries: Vec::new(), active: None, next_id: 0 }) };
67 static STATUS: Stack<StatusDyn, igraph_status_handler_t> =
68 const { RefCell::new(HandlerStack { base: None, entries: Vec::new(), active: None, next_id: 0 }) };
69 static INTERRUPT: Stack<InterruptDyn, igraph_interruption_handler_t> =
70 const { RefCell::new(HandlerStack { base: None, entries: Vec::new(), active: None, next_id: 0 }) };
71}
72
73/// Pushes a handler on the stack of `key`, installing `c` with `set` (which
74/// returns the previous C handler). Returns the id of the new entry.
75fn push<F: ?Sized + 'static, C: Copy + 'static>(
76 key: &'static LocalKey<Stack<F, C>>,
77 set: fn(C) -> C,
78 c: C,
79 rust: Option<Box<F>>,
80) -> u64 {
81 ensure_init();
82 key.with(|stack| {
83 let mut stack = stack.borrow_mut();
84 let prev = set(c);
85 if stack.entries.is_empty() {
86 stack.base = prev;
87 // Nothing can be active without an entry.
88 debug_assert!(stack.active.is_none());
89 } else if let Some((id, f)) = stack.active.take() {
90 // Park the closure of the current top.
91 if let Some(top) = stack.entries.last_mut()
92 && top.id == id
93 {
94 top.parked = Some(f);
95 }
96 }
97 let id = stack.next_id;
98 stack.next_id += 1;
99 stack.entries.push(Entry {
100 id,
101 c,
102 parked: None,
103 });
104 stack.active = rust.map(|f| (id, f));
105 id
106 })
107}
108
109/// Removes the entry `id` from the stack of `key`; if it was on top,
110/// reinstalls the handler of the new top (or the base handler).
111fn remove<F: ?Sized + 'static, C: Copy + 'static>(
112 key: &'static LocalKey<Stack<F, C>>,
113 set: fn(C) -> C,
114 id: u64,
115) {
116 // Closures are dropped after the borrow is released: they may own other
117 // guards, whose drop borrows the stack again.
118 let leftovers = key.try_with(|stack| {
119 let mut stack = stack.borrow_mut();
120 let Some(pos) = stack.entries.iter().position(|e| e.id == id) else {
121 return (None, None);
122 };
123 let removed = stack.entries.remove(pos);
124 if pos < stack.entries.len() {
125 // Not the active handler: nothing else changes.
126 return (removed.parked, None);
127 }
128 let active = stack.active.take().map(|(_, f)| f);
129 let c = match stack.entries.last_mut() {
130 Some(top) => {
131 let next = top.parked.take().map(|f| (top.id, f));
132 let c = top.c;
133 stack.active = next;
134 c
135 }
136 None => stack.base,
137 };
138 set(c);
139 (removed.parked, active)
140 });
141 drop(leftovers);
142}
143
144/// Calls the active closure of `key` (or returns `default` if there is none).
145///
146/// The closure is taken out of the stack for the duration of the call, so
147/// that a re-entrant call (e.g. the closure itself running an igraph
148/// function that reports progress) finds no closure instead of a busy
149/// `RefCell`. It is put back afterwards, even if it panics: as the active
150/// closure if its entry is still on top, parked in its entry if other
151/// handlers were installed meanwhile, or dropped if its guard was dropped.
152fn call_with<F: ?Sized + 'static, C: 'static, R>(
153 key: &'static LocalKey<Stack<F, C>>,
154 default: R,
155 call: impl FnOnce(&mut F) -> R,
156) -> R {
157 struct PutBack<F: ?Sized + 'static, C: 'static> {
158 key: &'static LocalKey<Stack<F, C>>,
159 id: u64,
160 handler: Option<Box<F>>,
161 }
162 impl<F: ?Sized + 'static, C: 'static> Drop for PutBack<F, C> {
163 fn drop(&mut self) {
164 let Some(handler) = self.handler.take() else {
165 return;
166 };
167 let id = self.id;
168 let leftover = self
169 .key
170 .try_with(|stack| {
171 let mut stack = stack.borrow_mut();
172 let stack = &mut *stack;
173 let is_top = stack.entries.last().is_some_and(|e| e.id == id);
174 if is_top && stack.active.is_none() {
175 stack.active = Some((id, handler));
176 return None;
177 }
178 match stack.entries.iter_mut().find(|e| e.id == id) {
179 Some(entry) if !is_top && entry.parked.is_none() => {
180 entry.parked = Some(handler);
181 None
182 }
183 _ => Some(handler),
184 }
185 })
186 .ok()
187 .flatten();
188 drop(leftover);
189 }
190 }
191 let Some((id, handler)) = key
192 .try_with(|stack| stack.borrow_mut().active.take())
193 .ok()
194 .flatten()
195 else {
196 return default;
197 };
198 let mut guard = PutBack {
199 key,
200 id,
201 handler: Some(handler),
202 };
203 match guard.handler.as_deref_mut() {
204 Some(f) => call(f),
205 None => default,
206 }
207}
208
209/// Runs a handler closure in a fresh level of igraph's "finally" stack.
210///
211/// igraph calls the handlers from inside running computations, which keep
212/// their temporary objects on the finally stack; igraph's error handler frees
213/// the current level when a call fails. Without the new level, a failing
214/// igraph call made by the handler (whose `Err` the handler may ignore)
215/// would free the temporaries of the computation that is still running.
216fn in_finally_level<R>(f: impl FnOnce() -> R) -> R {
217 // SAFETY: plain bookkeeping on igraph's thread-local finally stack. The
218 // closures passed here are wrapped in `catch_panic`/`catch_panic_or` and
219 // never unwind, so the matching EXIT always runs.
220 unsafe { IGRAPH_FINALLY_ENTER() };
221 let res = f();
222 unsafe { IGRAPH_FINALLY_EXIT() };
223 res
224}
225
226fn flow_to_code(flow: ControlFlow<()>) -> igraph_error_t {
227 match flow {
228 ControlFlow::Continue(()) => igraph_error_type_t_IGRAPH_SUCCESS,
229 ControlFlow::Break(()) => igraph_error_type_t_IGRAPH_INTERRUPTED,
230 }
231}
232
233fn to_cstring(message: &str) -> Result<CString> {
234 CString::new(message).map_err(|_| Error::invalid("message contains an interior NUL byte"))
235}
236
237// ---------------------------------------------------------------------------
238// Progress
239// ---------------------------------------------------------------------------
240
241unsafe extern "C" fn progress_trampoline(
242 message: *const c_char,
243 percent: igraph_real_t,
244 _data: *mut c_void,
245) -> igraph_error_t {
246 in_finally_level(|| {
247 catch_panic(|| {
248 let message = lossy(message);
249 flow_to_code(call_with(&PROGRESS, ControlFlow::Continue(()), |f| {
250 f(&message, percent)
251 }))
252 })
253 })
254}
255
256fn set_progress(c: igraph_progress_handler_t) -> igraph_progress_handler_t {
257 unsafe { igraph_set_progress_handler(c) }
258}
259
260/// Keeps a progress handler installed; returned by [`set_progress_handler`]
261/// and [`set_progress_handler_stderr`].
262///
263/// Dropping the guard uninstalls its handler: if it was the active one, the
264/// handler of the most recent guard still alive (or, if none, the handler
265/// that was active before the first guard) is reinstalled. Guards can be
266/// dropped in any order, e.g. when kept in a struct or a `Vec`.
267///
268/// The guard is bound to the thread that created it (it is neither `Send`
269/// nor `Sync`), like igraph's handlers.
270#[must_use = "the handler is uninstalled as soon as the guard is dropped"]
271pub struct ProgressHandlerGuard {
272 id: u64,
273 _not_send: PhantomData<*const ()>,
274}
275
276impl std::fmt::Debug for ProgressHandlerGuard {
277 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278 f.debug_struct("ProgressHandlerGuard")
279 .finish_non_exhaustive()
280 }
281}
282
283impl Drop for ProgressHandlerGuard {
284 fn drop(&mut self) {
285 remove(&PROGRESS, set_progress, self.id);
286 }
287}
288
289fn install_progress(
290 c: igraph_progress_handler_t,
291 rust: Option<Box<ProgressDyn>>,
292) -> ProgressHandlerGuard {
293 ProgressHandlerGuard {
294 id: push(&PROGRESS, set_progress, c, rust),
295 _not_send: PhantomData,
296 }
297}
298
299/// Installs a Rust closure as the progress handler of the calling thread
300/// until the returned guard is dropped.
301///
302/// igraph functions performing long computations (e.g. betweenness and
303/// closeness centralities, some layouts, the fast-greedy community
304/// detection) periodically call the handler with a short message describing
305/// the algorithm and the percentage of the work done (e.g.
306/// [`Graph::betweenness`](crate::Graph::betweenness) reports
307/// `"Betweenness centrality: "`): the first call has
308/// `0.0` and the last one `100.0` (unless an error occurs). Returning
309/// [`ControlFlow::Break`] aborts the computation, which then fails with
310/// [`ErrorKind::Interrupted`](crate::ErrorKind::Interrupted). A panic in the
311/// closure also aborts the computation, and is resumed when the wrapper
312/// returns.
313///
314/// Binds [`igraph_set_progress_handler`](https://igraph.org/c/html/latest/igraph-Advanced.html#igraph_set_progress_handler).
315///
316/// # Examples
317///
318/// ```
319/// use igraph::misc;
320/// use std::{cell::RefCell, ops::ControlFlow, rc::Rc};
321///
322/// let seen = Rc::new(RefCell::new(Vec::new()));
323/// let sink = Rc::clone(&seen);
324/// let guard = misc::set_progress_handler(move |msg, pct| {
325/// sink.borrow_mut().push((msg.to_owned(), pct));
326/// ControlFlow::Continue(())
327/// });
328/// misc::progress("Crunching numbers", 50.0)?;
329/// drop(guard);
330/// misc::progress("Nobody listens", 100.0)?; // no handler anymore
331/// assert_eq!(*seen.borrow(), vec![("Crunching numbers".to_owned(), 50.0)]);
332/// # Ok::<(), igraph::Error>(())
333/// ```
334pub fn set_progress_handler(
335 handler: impl FnMut(&str, f64) -> ControlFlow<()> + 'static,
336) -> ProgressHandlerGuard {
337 install_progress(Some(progress_trampoline), Some(Box::new(handler)))
338}
339
340/// Installs igraph's predefined progress handler, which prints the message
341/// and the percentage to standard error, until the guard is dropped.
342///
343/// Binds [`igraph_progress_handler_stderr`](https://igraph.org/c/html/latest/igraph-Advanced.html#igraph_progress_handler_stderr).
344pub fn set_progress_handler_stderr() -> ProgressHandlerGuard {
345 install_progress(Some(igraph_progress_handler_stderr), None)
346}
347
348/// Runs `body` with `handler` installed as the progress handler of the
349/// calling thread, restoring the previous handler afterwards (even if `body`
350/// panics). See [`set_progress_handler`].
351///
352/// # Examples
353///
354/// ```
355/// use igraph::misc;
356/// use std::{cell::Cell, ops::ControlFlow, rc::Rc};
357///
358/// let calls = Rc::new(Cell::new(0));
359/// let counter = Rc::clone(&calls);
360/// let stopped = misc::with_progress_handler(
361/// move |_, _| {
362/// counter.set(counter.get() + 1);
363/// ControlFlow::Break(()) // please stop
364/// },
365/// || misc::progress("step", 10.0),
366/// );
367/// assert_eq!(stopped.unwrap_err().kind(), igraph::ErrorKind::Interrupted);
368/// assert_eq!(calls.get(), 1);
369/// ```
370pub fn with_progress_handler<R>(
371 handler: impl FnMut(&str, f64) -> ControlFlow<()> + 'static,
372 body: impl FnOnce() -> R,
373) -> R {
374 let _guard = set_progress_handler(handler);
375 body()
376}
377
378/// Reports progress to the installed progress handler of the calling thread,
379/// exactly as igraph's own functions do; a no-op when no handler is
380/// installed. Useful to let long running Rust code built on this crate share
381/// the same progress reporting channel.
382///
383/// Binds [`igraph_progress`](https://igraph.org/c/html/latest/igraph-Advanced.html#igraph_progress).
384///
385/// # Errors
386/// [`ErrorKind::Interrupted`](crate::ErrorKind::Interrupted) if the handler
387/// asked to stop; [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue)
388/// if `message` contains a NUL byte.
389pub fn progress(message: &str, percent: f64) -> Result<()> {
390 let message = to_cstring(message)?;
391 igraph_call!(igraph_progress(
392 message.as_ptr(),
393 percent,
394 std::ptr::null_mut()
395 ))
396}
397
398// ---------------------------------------------------------------------------
399// Status
400// ---------------------------------------------------------------------------
401
402unsafe extern "C" fn status_trampoline(
403 message: *const c_char,
404 _data: *mut c_void,
405) -> igraph_error_t {
406 in_finally_level(|| {
407 catch_panic(|| {
408 let message = lossy(message);
409 flow_to_code(call_with(&STATUS, ControlFlow::Continue(()), |f| {
410 f(&message)
411 }))
412 })
413 })
414}
415
416fn set_status(c: igraph_status_handler_t) -> igraph_status_handler_t {
417 unsafe { igraph_set_status_handler(c) }
418}
419
420/// Keeps a status handler installed; returned by [`set_status_handler`]
421/// and [`set_status_handler_stderr`].
422///
423/// Like [`ProgressHandlerGuard`], guards can be dropped in any order, and
424/// each is bound to the creating thread.
425#[must_use = "the handler is uninstalled as soon as the guard is dropped"]
426pub struct StatusHandlerGuard {
427 id: u64,
428 _not_send: PhantomData<*const ()>,
429}
430
431impl std::fmt::Debug for StatusHandlerGuard {
432 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
433 f.debug_struct("StatusHandlerGuard").finish_non_exhaustive()
434 }
435}
436
437impl Drop for StatusHandlerGuard {
438 fn drop(&mut self) {
439 remove(&STATUS, set_status, self.id);
440 }
441}
442
443fn install_status(c: igraph_status_handler_t, rust: Option<Box<StatusDyn>>) -> StatusHandlerGuard {
444 StatusHandlerGuard {
445 id: push(&STATUS, set_status, c, rust),
446 _not_send: PhantomData,
447 }
448}
449
450/// Installs a Rust closure as the status handler of the calling thread until
451/// the returned guard is dropped.
452///
453/// Status messages are free-form notes about the stage a computation is in
454/// (as opposed to [progress](set_progress_handler) percentages). They are
455/// emitted with [`status`]; returning [`ControlFlow::Break`] makes the
456/// reporting call fail with
457/// [`ErrorKind::Interrupted`](crate::ErrorKind::Interrupted).
458///
459/// Binds [`igraph_set_status_handler`](https://igraph.org/c/html/latest/igraph-Advanced.html#igraph_set_status_handler).
460///
461/// # Examples
462///
463/// ```
464/// use igraph::misc;
465/// use std::{cell::RefCell, ops::ControlFlow, rc::Rc};
466///
467/// let log = Rc::new(RefCell::new(String::new()));
468/// let sink = Rc::clone(&log);
469/// let _guard = misc::set_status_handler(move |msg| {
470/// sink.borrow_mut().push_str(msg);
471/// ControlFlow::Continue(())
472/// });
473/// misc::status("loading; ")?;
474/// misc::status("done")?;
475/// assert_eq!(*log.borrow(), "loading; done");
476/// # Ok::<(), igraph::Error>(())
477/// ```
478pub fn set_status_handler(
479 handler: impl FnMut(&str) -> ControlFlow<()> + 'static,
480) -> StatusHandlerGuard {
481 install_status(Some(status_trampoline), Some(Box::new(handler)))
482}
483
484/// Installs igraph's predefined status handler, which writes the messages to
485/// standard error, until the guard is dropped.
486///
487/// Binds [`igraph_status_handler_stderr`](https://igraph.org/c/html/latest/igraph-Advanced.html#igraph_status_handler_stderr).
488pub fn set_status_handler_stderr() -> StatusHandlerGuard {
489 install_status(Some(igraph_status_handler_stderr), None)
490}
491
492/// Runs `body` with `handler` installed as the status handler of the calling
493/// thread, restoring the previous handler afterwards. See
494/// [`set_status_handler`].
495pub fn with_status_handler<R>(
496 handler: impl FnMut(&str) -> ControlFlow<()> + 'static,
497 body: impl FnOnce() -> R,
498) -> R {
499 let _guard = set_status_handler(handler);
500 body()
501}
502
503/// Sends a status message to the installed status handler of the calling
504/// thread (a no-op when none is installed).
505///
506/// Binds [`igraph_status`](https://igraph.org/c/html/latest/igraph-Advanced.html#igraph_status).
507///
508/// # Errors
509/// [`ErrorKind::Interrupted`](crate::ErrorKind::Interrupted) if the handler
510/// asked to stop; [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue)
511/// if `message` contains a NUL byte.
512pub fn status(message: &str) -> Result<()> {
513 let message = to_cstring(message)?;
514 igraph_call!(igraph_status(message.as_ptr(), std::ptr::null_mut()))
515}
516
517// ---------------------------------------------------------------------------
518// Interruption
519// ---------------------------------------------------------------------------
520
521unsafe extern "C" fn interruption_trampoline() -> igraph_bool_t {
522 in_finally_level(|| catch_panic_or(true, || call_with(&INTERRUPT, false, |f| f())))
523}
524
525fn set_interruption(c: igraph_interruption_handler_t) -> igraph_interruption_handler_t {
526 unsafe { igraph_set_interruption_handler(c) }
527}
528
529/// Keeps an interruption handler installed; returned by
530/// [`set_interruption_handler`].
531///
532/// Like [`ProgressHandlerGuard`], guards can be dropped in any order, and
533/// each is bound to the creating thread.
534#[must_use = "the handler is uninstalled as soon as the guard is dropped"]
535pub struct InterruptionHandlerGuard {
536 id: u64,
537 _not_send: PhantomData<*const ()>,
538}
539
540impl std::fmt::Debug for InterruptionHandlerGuard {
541 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
542 f.debug_struct("InterruptionHandlerGuard")
543 .finish_non_exhaustive()
544 }
545}
546
547impl Drop for InterruptionHandlerGuard {
548 fn drop(&mut self) {
549 remove(&INTERRUPT, set_interruption, self.id);
550 }
551}
552
553/// Installs a Rust closure as the interruption handler of the calling thread
554/// until the returned guard is dropped.
555///
556/// igraph calls the interruption handler regularly during long computations
557/// (shortest paths, SIR simulations, layouts, random graph generators, ...).
558/// The closure returns `true` to request the interruption of the running
559/// computation, which then fails with
560/// [`ErrorKind::Interrupted`](crate::ErrorKind::Interrupted), and `false` to
561/// let it go on. Typical uses are timeouts, cancellation flags set by
562/// another thread (e.g. an `Arc<AtomicBool>`), or Ctrl-C handling. A panic
563/// in the closure interrupts the computation and is resumed afterwards.
564///
565/// The closure may call igraph, but igraph polls the handler from inside
566/// running computations, including the ARPACK eigensolvers (e.g.
567/// [`linalg::arpack_rssolve`](crate::linalg::arpack_rssolve) or
568/// [`Graph::eigenvector_centrality`](crate::Graph::eigenvector_centrality)).
569/// igraph's ARPACK is not re-entrant (it keeps its iteration state in
570/// thread-local statics), so a handler that may run during an ARPACK-based
571/// computation must not start an ARPACK-based computation of its own: every
572/// ARPACK-based function of the crate detects this and fails with an
573/// [`ErrorKind::Failure`](crate::ErrorKind::Failure) error instead of
574/// corrupting the running solver (see [`linalg`](crate::linalg)). The same
575/// holds for progress handlers.
576///
577/// There is no default interruption handler. Binds
578/// `igraph_set_interruption_handler` (see `igraph_interrupt.h`).
579///
580/// # Examples
581///
582/// ```
583/// use igraph::prelude::*;
584/// use igraph::misc;
585/// use std::time::{Duration, Instant};
586///
587/// let deadline = Instant::now() + Duration::from_secs(3600);
588/// let _guard = misc::set_interruption_handler(move || Instant::now() > deadline);
589/// // Plenty of time: the simulation completes.
590/// let g = Graph::from_edges(&[(0, 1), (1, 2)], 3, false)?;
591/// assert_eq!(g.sir(1.0, 1.0, 3)?.len(), 3);
592/// # Ok::<(), igraph::Error>(())
593/// ```
594pub fn set_interruption_handler(
595 handler: impl FnMut() -> bool + 'static,
596) -> InterruptionHandlerGuard {
597 let handler: Box<InterruptDyn> = Box::new(handler);
598 InterruptionHandlerGuard {
599 id: push(
600 &INTERRUPT,
601 set_interruption,
602 Some(interruption_trampoline),
603 Some(handler),
604 ),
605 _not_send: PhantomData,
606 }
607}
608
609/// Runs `body` with `handler` installed as the interruption handler of the
610/// calling thread, restoring the previous handler afterwards. See
611/// [`set_interruption_handler`].
612///
613/// # Examples
614///
615/// ```
616/// use igraph::prelude::*;
617/// use igraph::misc;
618///
619/// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], 3, false)?;
620/// let err = misc::with_interruption_handler(|| true, || g.sir(1.0, 1.0, 10)).unwrap_err();
621/// assert_eq!(err.kind(), ErrorKind::Interrupted);
622/// # Ok::<(), igraph::Error>(())
623/// ```
624pub fn with_interruption_handler<R>(
625 handler: impl FnMut() -> bool + 'static,
626 body: impl FnOnce() -> R,
627) -> R {
628 let _guard = set_interruption_handler(handler);
629 body()
630}
631
632/// Asks the installed interruption handler whether the current computation
633/// should be interrupted; `false` when no handler is installed.
634///
635/// This is what igraph functions do internally; Rust code built on this
636/// crate can call it to honor the same cancellation requests.
637///
638/// Binds `igraph_allow_interruption` (see `igraph_interrupt.h`).
639pub fn allow_interruption() -> bool {
640 ensure_init();
641 let interrupt = unsafe { igraph_allow_interruption() };
642 // A panicking handler was caught in the trampoline: resume it here.
643 resume_panic();
644 interrupt
645}