11use futures_util:: StreamExt ;
22use std:: sync:: Arc ;
33
4- use pyo3:: { Bound , PyAny , PyRef , Python } ;
4+ use pyo3:: { Bound , Py , PyAny , PyRef , Python } ;
55use tokio:: sync:: Mutex ;
66
77use crate :: {
@@ -12,14 +12,56 @@ use crate::{
1212#[ pyo3:: pyclass]
1313pub struct Subscription {
1414 inner : Option < Arc < Mutex < async_nats:: Subscriber > > > ,
15+ reading_task : Option < tokio:: task:: AbortHandle > ,
16+ }
17+
18+ async fn process_message ( message : async_nats:: message:: Message , py_callback : Py < PyAny > ) {
19+ let task = async || -> NatsrpyResult < ( ) > {
20+ let message = crate :: message:: Message :: try_from ( & message) ?;
21+ let awaitable = Python :: attach ( |gil| -> NatsrpyResult < _ > {
22+ let res = py_callback. call1 ( gil, ( message, ) ) ?;
23+ let rust_task = pyo3_async_runtimes:: tokio:: into_future ( res. into_bound ( gil) ) ?;
24+ Ok ( rust_task)
25+ } ) ?;
26+ awaitable. await ?;
27+ Ok ( ( ) )
28+ } ;
29+ if let Err ( err) = task ( ) . await {
30+ log:: error!( "Cannot process message {message:?}. Error: {err}" ) ;
31+ }
32+ }
33+
34+ async fn start_py_sub (
35+ sub : Arc < Mutex < async_nats:: Subscriber > > ,
36+ py_callback : Py < PyAny > ,
37+ locals : pyo3_async_runtimes:: TaskLocals ,
38+ ) {
39+ while let Some ( message) = sub. lock ( ) . await . next ( ) . await {
40+ let py_cb = Python :: attach ( |py| py_callback. clone_ref ( py) ) ;
41+ tokio:: spawn ( pyo3_async_runtimes:: tokio:: scope (
42+ locals. clone ( ) ,
43+ process_message ( message, py_cb) ,
44+ ) ) ;
45+ }
1546}
1647
1748impl Subscription {
18- #[ must_use]
19- pub fn new ( sub : async_nats:: Subscriber ) -> Self {
20- Self {
21- inner : Some ( Arc :: new ( Mutex :: new ( sub) ) ) ,
22- }
49+ pub fn new ( sub : async_nats:: Subscriber , callback : Option < Py < PyAny > > ) -> NatsrpyResult < Self > {
50+ let sub = Arc :: new ( Mutex :: new ( sub) ) ;
51+ let cb_sub = sub. clone ( ) ;
52+ let task_locals = Python :: attach ( pyo3_async_runtimes:: tokio:: get_current_locals) ?;
53+ let task_handle = callback. map ( move |cb| {
54+ tokio:: task:: spawn ( pyo3_async_runtimes:: tokio:: scope (
55+ task_locals. clone ( ) ,
56+ start_py_sub ( cb_sub, cb, task_locals) ,
57+ ) )
58+ . abort_handle ( )
59+ } ) ;
60+
61+ Ok ( Self {
62+ inner : Some ( sub) ,
63+ reading_task : task_handle,
64+ } )
2365 }
2466}
2567
@@ -38,11 +80,15 @@ impl Subscription {
3880 let Some ( inner) = self . inner . clone ( ) else {
3981 unreachable ! ( "Subscription used after del" )
4082 } ;
83+ if self . reading_task . is_some ( ) {
84+ log:: warn!(
85+ "Callback is set. Getting messages from this subscription might produce unpredictable results."
86+ ) ;
87+ }
4188 natsrpy_future_with_timeout ( py, timeout, async move {
4289 let Some ( message) = inner. lock ( ) . await . next ( ) . await else {
4390 return Err ( NatsrpyError :: AsyncStopIteration ) ;
4491 } ;
45-
4692 crate :: message:: Message :: try_from ( message)
4793 } )
4894 }
@@ -96,6 +142,9 @@ impl Drop for Subscription {
96142 fn drop ( & mut self ) {
97143 pyo3_async_runtimes:: tokio:: get_runtime ( ) . block_on ( async move {
98144 self . inner = None ;
145+ if let Some ( reading) = self . reading_task . take ( ) {
146+ reading. abort ( ) ;
147+ }
99148 } ) ;
100149 }
101150}
0 commit comments