1#![warn(missing_docs)]
83#![warn(clippy::all)]
84
85use std::sync::atomic::{AtomicBool, Ordering};
86use std::sync::Arc;
87
88use enough::{Stop, StopReason};
89
90struct CancellationState {
96 cancelled: AtomicBool,
97}
98
99impl CancellationState {
100 fn new() -> Self {
101 Self {
102 cancelled: AtomicBool::new(false),
103 }
104 }
105
106 #[inline]
107 fn cancel(&self) {
108 self.cancelled.store(true, Ordering::Relaxed);
109 }
110
111 #[inline]
112 fn is_cancelled(&self) -> bool {
113 self.cancelled.load(Ordering::Relaxed)
114 }
115}
116
117#[repr(C)]
133pub struct FfiCancellationSource {
134 inner: Arc<CancellationState>,
135}
136
137impl FfiCancellationSource {
138 fn new() -> Self {
139 Self {
140 inner: Arc::new(CancellationState::new()),
141 }
142 }
143
144 #[inline]
146 pub fn cancel(&self) {
147 self.inner.cancel();
148 }
149
150 #[inline]
152 pub fn is_cancelled(&self) -> bool {
153 self.inner.is_cancelled()
154 }
155
156 fn create_token(&self) -> FfiCancellationToken {
158 FfiCancellationToken {
159 inner: Some(Arc::clone(&self.inner)),
160 }
161 }
162}
163
164#[repr(C)]
176pub struct FfiCancellationToken {
177 inner: Option<Arc<CancellationState>>,
178}
179
180impl FfiCancellationToken {
181 #[inline]
185 pub fn never() -> Self {
186 Self { inner: None }
187 }
188
189 #[inline]
199 pub unsafe fn from_ptr(ptr: *const FfiCancellationToken) -> FfiCancellationTokenView {
200 FfiCancellationTokenView { ptr }
201 }
202}
203
204impl Stop for FfiCancellationToken {
205 #[inline]
206 fn check(&self) -> Result<(), StopReason> {
207 match &self.inner {
208 Some(state) if state.is_cancelled() => Err(StopReason::Cancelled),
209 _ => Ok(()),
210 }
211 }
212
213 #[inline]
214 fn should_stop(&self) -> bool {
215 self.inner
216 .as_ref()
217 .map(|s| s.is_cancelled())
218 .unwrap_or(false)
219 }
220}
221
222impl std::fmt::Debug for FfiCancellationToken {
223 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224 f.debug_struct("FfiCancellationToken")
225 .field("is_cancelled", &self.should_stop())
226 .field("is_never", &self.inner.is_none())
227 .finish()
228 }
229}
230
231#[derive(Clone, Copy)]
240pub struct FfiCancellationTokenView {
241 ptr: *const FfiCancellationToken,
242}
243
244unsafe impl Send for FfiCancellationTokenView {}
247unsafe impl Sync for FfiCancellationTokenView {}
248
249impl FfiCancellationTokenView {
250 #[inline]
252 pub const fn never() -> Self {
253 Self {
254 ptr: std::ptr::null(),
255 }
256 }
257}
258
259impl Stop for FfiCancellationTokenView {
260 #[inline]
261 fn check(&self) -> Result<(), StopReason> {
262 if self.ptr.is_null() {
263 return Ok(());
264 }
265 unsafe {
267 if (*self.ptr).should_stop() {
268 Err(StopReason::Cancelled)
269 } else {
270 Ok(())
271 }
272 }
273 }
274
275 #[inline]
276 fn should_stop(&self) -> bool {
277 if self.ptr.is_null() {
278 return false;
279 }
280 unsafe { (*self.ptr).should_stop() }
282 }
283}
284
285impl std::fmt::Debug for FfiCancellationTokenView {
286 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287 f.debug_struct("FfiCancellationTokenView")
288 .field("ptr", &self.ptr)
289 .field("is_null", &self.ptr.is_null())
290 .finish()
291 }
292}
293
294#[no_mangle]
305pub extern "C" fn enough_cancellation_create() -> *mut FfiCancellationSource {
306 Box::into_raw(Box::new(FfiCancellationSource::new()))
307}
308
309#[no_mangle]
319pub unsafe extern "C" fn enough_cancellation_cancel(ptr: *const FfiCancellationSource) {
320 if let Some(source) = ptr.as_ref() {
321 source.cancel();
322 }
323}
324
325#[no_mangle]
332pub unsafe extern "C" fn enough_cancellation_is_cancelled(
333 ptr: *const FfiCancellationSource,
334) -> bool {
335 ptr.as_ref().map(|s| s.is_cancelled()).unwrap_or(false)
336}
337
338#[no_mangle]
349pub unsafe extern "C" fn enough_cancellation_destroy(ptr: *mut FfiCancellationSource) {
350 if !ptr.is_null() {
351 drop(Box::from_raw(ptr));
352 }
353}
354
355#[no_mangle]
371pub unsafe extern "C" fn enough_token_create(
372 source: *const FfiCancellationSource,
373) -> *mut FfiCancellationToken {
374 let token = match source.as_ref() {
375 Some(s) => s.create_token(),
376 None => FfiCancellationToken::never(),
377 };
378 Box::into_raw(Box::new(token))
379}
380
381#[no_mangle]
386pub extern "C" fn enough_token_create_never() -> *mut FfiCancellationToken {
387 Box::into_raw(Box::new(FfiCancellationToken::never()))
388}
389
390#[no_mangle]
397pub unsafe extern "C" fn enough_token_is_cancelled(token: *const FfiCancellationToken) -> bool {
398 token.as_ref().map(|t| t.should_stop()).unwrap_or(false)
399}
400
401#[no_mangle]
409pub unsafe extern "C" fn enough_token_destroy(token: *mut FfiCancellationToken) {
410 if !token.is_null() {
411 drop(Box::from_raw(token));
412 }
413}
414
415#[cfg(test)]
420mod tests {
421 use super::*;
422
423 #[test]
424 fn source_create_cancel_destroy() {
425 unsafe {
426 let ptr = enough_cancellation_create();
427 assert!(!ptr.is_null());
428
429 assert!(!enough_cancellation_is_cancelled(ptr));
430
431 enough_cancellation_cancel(ptr);
432
433 assert!(enough_cancellation_is_cancelled(ptr));
434
435 enough_cancellation_destroy(ptr);
436 }
437 }
438
439 #[test]
440 fn token_lifecycle() {
441 unsafe {
442 let source = enough_cancellation_create();
443 let token = enough_token_create(source);
444
445 assert!(!enough_token_is_cancelled(token));
446
447 enough_cancellation_cancel(source);
448
449 assert!(enough_token_is_cancelled(token));
450
451 enough_token_destroy(token);
452 enough_cancellation_destroy(source);
453 }
454 }
455
456 #[test]
457 fn token_survives_source_destruction() {
458 unsafe {
459 let source = enough_cancellation_create();
460
461 enough_cancellation_cancel(source);
463
464 let token = enough_token_create(source);
465
466 enough_cancellation_destroy(source);
468
469 assert!(enough_token_is_cancelled(token));
471
472 enough_token_destroy(token);
473 }
474 }
475
476 #[test]
477 fn token_from_destroyed_source_never_cancels() {
478 unsafe {
479 let source = enough_cancellation_create();
480 let token = enough_token_create(source);
481
482 enough_cancellation_destroy(source);
484
485 assert!(!enough_token_is_cancelled(token));
488
489 enough_token_destroy(token);
490 }
491 }
492
493 #[test]
494 fn token_never() {
495 unsafe {
496 let token = enough_token_create_never();
497 assert!(!enough_token_is_cancelled(token));
498 enough_token_destroy(token);
499 }
500 }
501
502 #[test]
503 fn null_safety() {
504 unsafe {
505 enough_cancellation_cancel(std::ptr::null());
507 enough_cancellation_destroy(std::ptr::null_mut());
508 assert!(!enough_cancellation_is_cancelled(std::ptr::null()));
509
510 enough_token_destroy(std::ptr::null_mut());
511 assert!(!enough_token_is_cancelled(std::ptr::null()));
512
513 let token = enough_token_create(std::ptr::null());
515 assert!(!enough_token_is_cancelled(token));
516 enough_token_destroy(token);
517 }
518 }
519
520 #[test]
521 fn token_view_from_ptr() {
522 unsafe {
523 let source = enough_cancellation_create();
524 let token = enough_token_create(source);
525
526 let view = FfiCancellationToken::from_ptr(token);
528
529 assert!(!view.should_stop());
530 assert!(view.check().is_ok());
531
532 enough_cancellation_cancel(source);
533
534 assert!(view.should_stop());
535 assert_eq!(view.check(), Err(StopReason::Cancelled));
536
537 enough_token_destroy(token);
538 enough_cancellation_destroy(source);
539 }
540 }
541
542 #[test]
543 fn token_view_never() {
544 let view = FfiCancellationTokenView::never();
545 assert!(!view.should_stop());
546 assert!(view.check().is_ok());
547 }
548
549 #[test]
550 fn types_are_send_sync() {
551 fn assert_send_sync<T: Send + Sync>() {}
552 assert_send_sync::<FfiCancellationToken>();
553 assert_send_sync::<FfiCancellationTokenView>();
554 }
555
556 #[test]
557 fn multiple_tokens_same_source() {
558 unsafe {
559 let source = enough_cancellation_create();
560 let t1 = enough_token_create(source);
561 let t2 = enough_token_create(source);
562 let t3 = enough_token_create(source);
563
564 assert!(!enough_token_is_cancelled(t1));
565 assert!(!enough_token_is_cancelled(t2));
566 assert!(!enough_token_is_cancelled(t3));
567
568 enough_cancellation_cancel(source);
569
570 assert!(enough_token_is_cancelled(t1));
571 assert!(enough_token_is_cancelled(t2));
572 assert!(enough_token_is_cancelled(t3));
573
574 enough_token_destroy(t2);
576 enough_cancellation_destroy(source);
577 enough_token_destroy(t1);
578 enough_token_destroy(t3);
579 }
580 }
581
582 #[test]
583 fn interop_with_enough() {
584 fn use_stop(stop: impl Stop) -> bool {
586 stop.should_stop()
587 }
588
589 assert!(!use_stop(FfiCancellationToken::never()));
591 assert!(!use_stop(FfiCancellationTokenView::never()));
592
593 unsafe {
595 let source = enough_cancellation_create();
596 let token = enough_token_create(source);
597 let view = FfiCancellationToken::from_ptr(token);
598
599 assert!(!use_stop(view));
600
601 enough_cancellation_cancel(source);
602 assert!(use_stop(view));
603
604 enough_token_destroy(token);
605 enough_cancellation_destroy(source);
606 }
607 }
608
609 #[test]
610 fn concurrent_access_stress() {
611 use std::sync::atomic::{AtomicUsize, Ordering};
612 use std::sync::Arc;
613 use std::thread;
614
615 unsafe {
616 let source = enough_cancellation_create();
617 let cancelled_count = Arc::new(AtomicUsize::new(0));
618 let check_count = Arc::new(AtomicUsize::new(0));
619
620 let tokens: Vec<_> = (0..10)
622 .map(|_| enough_token_create(source) as usize)
623 .collect();
624
625 let handles: Vec<_> = tokens
627 .into_iter()
628 .map(|token_addr| {
629 let cancelled_count = Arc::clone(&cancelled_count);
630 let check_count = Arc::clone(&check_count);
631
632 thread::spawn(move || {
633 let token = token_addr as *mut FfiCancellationToken;
634 let view = FfiCancellationToken::from_ptr(token);
635 for _ in 0..10000 {
636 check_count.fetch_add(1, Ordering::Relaxed);
637 if view.should_stop() {
638 cancelled_count.fetch_add(1, Ordering::Relaxed);
639 break;
640 }
641 thread::yield_now();
642 }
643 enough_token_destroy(token);
644 })
645 })
646 .collect();
647
648 thread::sleep(std::time::Duration::from_millis(1));
650 enough_cancellation_cancel(source);
651
652 for h in handles {
653 h.join().unwrap();
654 }
655
656 assert!(cancelled_count.load(Ordering::Relaxed) > 0);
658 assert!(check_count.load(Ordering::Relaxed) > 0);
659
660 enough_cancellation_destroy(source);
661 }
662 }
663
664 #[test]
665 fn cross_thread_cancellation() {
666 use std::thread;
667
668 unsafe {
669 let source = enough_cancellation_create();
670 let token = enough_token_create(source);
671
672 let token_addr = token as usize;
674 let handle = thread::spawn(move || {
675 let token = token_addr as *const FfiCancellationToken;
676 let view = FfiCancellationToken::from_ptr(token);
677
678 let mut iterations = 0;
680 while !view.should_stop() && iterations < 1_000_000 {
681 iterations += 1;
682 thread::yield_now();
683 }
684
685 view.should_stop()
686 });
687
688 thread::sleep(std::time::Duration::from_millis(5));
690 enough_cancellation_cancel(source);
691
692 let was_cancelled = handle.join().unwrap();
693 assert!(was_cancelled);
694
695 enough_token_destroy(token);
696 enough_cancellation_destroy(source);
697 }
698 }
699
700 #[test]
701 fn rapid_create_destroy() {
702 unsafe {
704 for _ in 0..1000 {
705 let source = enough_cancellation_create();
706 let tokens: Vec<_> = (0..10).map(|_| enough_token_create(source)).collect();
707
708 enough_cancellation_cancel(source);
709
710 for token in tokens {
711 assert!(enough_token_is_cancelled(token));
712 enough_token_destroy(token);
713 }
714
715 enough_cancellation_destroy(source);
716 }
717 }
718 }
719
720 #[test]
721 fn idempotent_cancel() {
722 unsafe {
723 let source = enough_cancellation_create();
724 let token = enough_token_create(source);
725
726 enough_cancellation_cancel(source);
728 enough_cancellation_cancel(source);
729 enough_cancellation_cancel(source);
730
731 assert!(enough_token_is_cancelled(token));
732
733 enough_token_destroy(token);
734 enough_cancellation_destroy(source);
735 }
736 }
737
738 #[test]
739 fn token_view_copy_semantics() {
740 unsafe {
741 let source = enough_cancellation_create();
742 let token = enough_token_create(source);
743
744 let view1 = FfiCancellationToken::from_ptr(token);
745 let view2 = view1; let view3 = view1; assert!(!view1.should_stop());
749 assert!(!view2.should_stop());
750 assert!(!view3.should_stop());
751
752 enough_cancellation_cancel(source);
753
754 assert!(view1.should_stop());
755 assert!(view2.should_stop());
756 assert!(view3.should_stop());
757
758 enough_token_destroy(token);
759 enough_cancellation_destroy(source);
760 }
761 }
762
763 #[test]
764 fn check_returns_correct_reason() {
765 unsafe {
766 let source = enough_cancellation_create();
767 let token = enough_token_create(source);
768 let view = FfiCancellationToken::from_ptr(token);
769
770 assert_eq!(view.check(), Ok(()));
771
772 enough_cancellation_cancel(source);
773
774 assert_eq!(view.check(), Err(StopReason::Cancelled));
775
776 enough_token_destroy(token);
777 enough_cancellation_destroy(source);
778 }
779 }
780
781 #[test]
782 fn debug_formatting() {
783 unsafe {
784 let source = enough_cancellation_create();
785 let token = enough_token_create(source);
786 let view = FfiCancellationToken::from_ptr(token);
787
788 let token_ref = &*token;
789 let token_debug = format!("{:?}", token_ref);
790 assert!(token_debug.contains("FfiCancellationToken"));
791 assert!(token_debug.contains("is_cancelled"));
792
793 let view_debug = format!("{:?}", view);
794 assert!(view_debug.contains("FfiCancellationTokenView"));
795
796 enough_token_destroy(token);
797 enough_cancellation_destroy(source);
798 }
799 }
800
801 #[test]
802 fn simulated_ffi_pattern() {
803 unsafe {
805 let source = enough_cancellation_create();
807 let token = enough_token_create(source);
808
809 fn rust_ffi_function(
811 token_ptr: *const FfiCancellationToken,
812 ) -> Result<i32, &'static str> {
813 let stop = unsafe { FfiCancellationToken::from_ptr(token_ptr) };
814
815 for i in 0..1000 {
816 if i % 100 == 0 {
817 stop.check().map_err(|_| "cancelled")?;
818 }
819 }
820 Ok(42)
821 }
822
823 let result = rust_ffi_function(token);
825 assert_eq!(result, Ok(42));
826
827 enough_cancellation_cancel(source);
829
830 let result = rust_ffi_function(token);
832 assert_eq!(result, Err("cancelled"));
833
834 enough_token_destroy(token);
836 enough_cancellation_destroy(source);
837 }
838 }
839}