1+ use core:: time:: Duration ;
12use std:: sync:: { Arc , mpsc} ;
23
34use ironrdp_dvc:: encode_dvc_messages;
@@ -11,6 +12,8 @@ use crate::message::RawDataDvcMessage;
1112use crate :: os_pipe:: OsPipe ;
1213
1314const IO_BUFFER_SIZE : usize = 1024 * 64 ; // 64K
15+ const INITIAL_RECONNECT_DELAY : Duration = Duration :: from_millis ( 100 ) ;
16+ const MAX_RECONNECT_DELAY : Duration = Duration :: from_secs ( 5 ) ;
1417
1518pub ( crate ) type OnWriteDvcMessage = Box < dyn Fn ( u32 , Vec < SvcMessage > ) -> PduResult < ( ) > + Send > ;
1619
@@ -23,38 +26,85 @@ pub(crate) struct WorkerCtx {
2326 pub ( crate ) channel_id : u32 ,
2427}
2528
26- pub ( crate ) fn run_worker < P : OsPipe > ( ctx : WorkerCtx ) {
27- let _ = std:: thread:: spawn ( move || {
29+ pub ( crate ) fn run_worker < P : OsPipe > ( ctx : WorkerCtx ) -> std:: io:: Result < ( ) > {
30+ let thread_name = format ! ( "ironrdp-dvc-pipe-{}" , ctx. channel_id) ;
31+ let ( startup_tx, startup_rx) = mpsc:: sync_channel ( 1 ) ;
32+
33+ std:: thread:: Builder :: new ( ) . name ( thread_name) . spawn ( move || {
2834 let channel_name = ctx. channel_name . clone ( ) ;
2935 let pipe_name = ctx. pipe_name . clone ( ) ;
36+ debug ! ( %channel_name, %pipe_name, "Starting DVC pipe proxy worker thread" ) ;
3037
31- let runtime = tokio:: runtime:: Builder :: new_current_thread ( )
32- . enable_all ( )
33- . build ( )
34- . map_err ( DvcPipeProxyError :: Io ) ;
35-
36- let runtime = match runtime {
38+ let runtime = match tokio:: runtime:: Builder :: new_current_thread ( ) . enable_all ( ) . build ( ) {
3739 Ok ( runtime) => runtime,
3840 Err ( error) => {
3941 error ! (
4042 %channel_name,
4143 %pipe_name,
42- ? error,
43- "DVC pipe proxy worker thread initialization failed "
44+ % error,
45+ "Failed to initialize DVC pipe proxy worker thread"
4446 ) ;
47+ let _ = startup_tx. send ( Err ( error) ) ;
4548 return ;
4649 }
4750 } ;
4851
49- if let Err ( error) = runtime. block_on ( worker :: < P > ( ctx) ) {
52+ let ( async_tx, async_rx) = tokio:: sync:: mpsc:: unbounded_channel ( ) ;
53+ let WorkerCtx {
54+ on_write_dvc,
55+ to_pipe_rx : std_rx,
56+ abort_event,
57+ pipe_name,
58+ channel_name,
59+ channel_id,
60+ } = ctx;
61+
62+ let bridge_thread_name = format ! ( "ironrdp-dvc-pipe-{channel_id}-bridge" ) ;
63+ if let Err ( error) = std:: thread:: Builder :: new ( ) . name ( bridge_thread_name) . spawn ( move || {
64+ while let Ok ( data) = std_rx. recv ( ) {
65+ if async_tx. send ( data) . is_err ( ) {
66+ break ; // Receiver dropped
67+ }
68+ }
69+ } ) {
5070 error ! (
5171 %channel_name,
5272 %pipe_name,
53- ? error,
54- "DVC pipe proxy worker thread has failed "
73+ % error,
74+ "Failed to start DVC pipe proxy bridge thread"
5575 ) ;
76+ let _ = startup_tx. send ( Err ( error) ) ;
77+ return ;
78+ }
79+
80+ let ctx = BridgedWorkerCtx {
81+ on_write_dvc,
82+ to_pipe_rx : async_rx,
83+ abort_event,
84+ pipe_name,
85+ channel_name,
86+ channel_id,
87+ } ;
88+
89+ if startup_tx. send ( Ok ( ( ) ) ) . is_err ( ) {
90+ return ;
91+ }
92+
93+ debug ! (
94+ channel_name = %ctx. channel_name,
95+ pipe_name = %ctx. pipe_name,
96+ "Started DVC pipe proxy worker thread"
97+ ) ;
98+ if let Err ( error) = runtime. block_on ( worker :: < P > ( ctx) ) {
99+ error ! ( ?error, "DVC pipe proxy worker thread has failed" ) ;
56100 }
57- } ) ;
101+ } ) ?;
102+
103+ startup_rx. recv ( ) . unwrap_or_else ( |_| {
104+ Err ( std:: io:: Error :: other (
105+ "dvc pipe proxy worker stopped before startup completed" ,
106+ ) )
107+ } )
58108}
59109
60110enum NextWorkerState {
@@ -134,57 +184,39 @@ async fn process_client<P: OsPipe>(ctx: &mut BridgedWorkerCtx) -> Result<NextWor
134184 if let Err ( error) = pipe. write_all( & data) . await
135185 {
136186 error!( %channel_name, %pipe_name, ?error, "Failed to write to DVC pipe" ) ;
137- continue ;
187+ return Ok ( NextWorkerState :: Reconnect ) ;
138188 }
139189 }
140190 } ;
141191 }
142192}
143193
144- async fn worker < P : OsPipe > ( ctx : WorkerCtx ) -> Result < ( ) , DvcPipeProxyError > {
145- // Create a bridge between std::sync::mpsc and tokio for async compatibility.
146- // It is fine to use unbounded channel here because we are using it only to
147- // forward data from a bounded channel (with size IO_MPSC_CHANNEL_SIZE),
148- // so we will never have unbounded memory growth.
149- let ( async_tx, async_rx) = tokio:: sync:: mpsc:: unbounded_channel ( ) ;
150-
151- let WorkerCtx {
152- on_write_dvc,
153- to_pipe_rx : std_rx,
154- abort_event,
155- pipe_name,
156- channel_name,
157- channel_id,
158- } = ctx;
159-
160- // Spawn a thread to bridge std::sync::mpsc to tokio::sync::mpsc.
161- std:: thread:: spawn ( move || {
162- while let Ok ( data) = std_rx. recv ( ) {
163- if async_tx. send ( data) . is_err ( ) {
164- break ; // Receiver dropped
165- }
166- }
167- } ) ;
168-
169- let mut bridged_ctx = BridgedWorkerCtx {
170- on_write_dvc,
171- to_pipe_rx : async_rx,
172- abort_event,
173- pipe_name,
174- channel_name,
175- channel_id,
176- } ;
194+ async fn worker < P : OsPipe > ( mut bridged_ctx : BridgedWorkerCtx ) -> Result < ( ) , DvcPipeProxyError > {
195+ let mut reconnect_delay = INITIAL_RECONNECT_DELAY ;
196+
177197 loop {
178- match process_client :: < P > ( & mut bridged_ctx) . await ? {
179- NextWorkerState :: Abort => {
198+ match process_client :: < P > ( & mut bridged_ctx) . await {
199+ Err ( error) => {
200+ error ! (
201+ channel_name = %bridged_ctx. channel_name,
202+ pipe_name = %bridged_ctx. pipe_name,
203+ ?error,
204+ retry_delay_ms = reconnect_delay. as_millis( ) ,
205+ "DVC pipe proxy connection failed; retrying"
206+ ) ;
207+ std:: thread:: sleep ( reconnect_delay) ;
208+ reconnect_delay = reconnect_delay. saturating_mul ( 2 ) . min ( MAX_RECONNECT_DELAY ) ;
209+ }
210+ Ok ( NextWorkerState :: Abort ) => {
180211 debug ! (
181212 channel_name = %bridged_ctx. channel_name,
182213 pipe_name = %bridged_ctx. pipe_name,
183214 "Abort DVC proxy worker thread"
184215 ) ;
185216 break ;
186217 }
187- NextWorkerState :: Reconnect => {
218+ Ok ( NextWorkerState :: Reconnect ) => {
219+ reconnect_delay = INITIAL_RECONNECT_DELAY ;
188220 debug ! (
189221 channel_name = %bridged_ctx. channel_name,
190222 pipe_name = %bridged_ctx. pipe_name,
0 commit comments