11use std:: sync:: LazyLock ;
22
3+ use magnus:: {
4+ DataTypeFunctions , Error , Module , Object , RModule , Ruby , TypedData , function, method,
5+ } ;
36use tokio:: runtime:: { Builder , Runtime } ;
47
58use crate :: { error:: interrupt_error, gvl} ;
@@ -17,11 +20,11 @@ pub fn try_block_on<F, T>(future: F) -> F::Output
1720where
1821 F : Future < Output = Result < T , magnus:: Error > > ,
1922{
20- gvl:: nogvl_cancellable ( |flag | {
23+ gvl:: nogvl_cancellable ( |token | {
2124 RUNTIME . block_on ( async move {
2225 tokio:: select! {
2326 biased;
24- _ = flag . cancelled( ) => Err ( interrupt_error( ) ) ,
27+ _ = token . cancelled( ) => Err ( interrupt_error( ) ) ,
2528 result = future => result,
2629 }
2730 } )
@@ -35,13 +38,57 @@ pub fn maybe_block_on<F, T>(future: F) -> F::Output
3538where
3639 F : Future < Output = Option < T > > ,
3740{
38- gvl:: nogvl_cancellable ( |flag | {
41+ gvl:: nogvl_cancellable ( |token | {
3942 RUNTIME . block_on ( async move {
4043 tokio:: select! {
4144 biased;
42- _ = flag . cancelled( ) => None ,
45+ _ = token . cancelled( ) => None ,
4346 result = future => result,
4447 }
4548 } )
4649 } )
4750}
51+
52+ /// A cancellation token for cooperative cancellation of Rust async tasks from Ruby.
53+ ///
54+ /// This type wraps [`tokio_util::sync::CancellationToken`] and is exposed to Ruby as
55+ /// `Wreq::CancellationToken`. It allows Ruby code to signal cancellation to long-running or
56+ /// blocking Rust tasks, enabling graceful interruption.
57+ ///
58+ /// Typical usage:
59+ /// - Ruby creates a `Wreq::CancellationToken` and passes it to a Rust-backed async operation.
60+ /// - Rust code checks or awaits the token to detect cancellation and aborts work if cancelled.
61+ /// - Calling `cancel` from Ruby triggers cancellation for all tasks observing this token or its
62+ /// clones.
63+ ///
64+ /// This is especially useful for interrupting HTTP requests, streaming, or other operations that
65+ /// may need to be stopped from Ruby.
66+ #[ derive( Clone , DataTypeFunctions , TypedData ) ]
67+ #[ magnus( class = "Wreq::CancellationToken" , free_immediately, size) ]
68+ pub struct CancellationToken ( tokio_util:: sync:: CancellationToken ) ;
69+
70+ impl CancellationToken {
71+ /// Create a new [`CancellationToken`].
72+ #[ inline]
73+ pub fn new ( ) -> Self {
74+ Self ( tokio_util:: sync:: CancellationToken :: new ( ) )
75+ }
76+
77+ /// Signal cancellation to all tasks observing this token.
78+ #[ inline]
79+ pub fn cancel ( & self ) {
80+ self . 0 . cancel ( )
81+ }
82+
83+ #[ inline]
84+ async fn cancelled ( & self ) {
85+ self . 0 . cancelled ( ) . await
86+ }
87+ }
88+
89+ pub fn include ( ruby : & Ruby , gem_module : & RModule ) -> Result < ( ) , Error > {
90+ let headers_class = gem_module. define_class ( "CancellationToken" , ruby. class_object ( ) ) ?;
91+ headers_class. define_singleton_method ( "new" , function ! ( CancellationToken :: new, 0 ) ) ?;
92+ headers_class. define_method ( "cancel" , method ! ( CancellationToken :: cancel, 0 ) ) ?;
93+ Ok ( ( ) )
94+ }
0 commit comments