Skip to main content

veloren_network/
scheduler.rs

1use crate::{
2    api::{ConnectAddr, ListenAddr, NetworkConnectError, Participant},
3    channel::Protocols,
4    metrics::{NetworkMetrics, ProtocolInfo},
5    participant::{B2sPrioStatistic, BParticipant, S2bCreateChannel, S2bShutdownBparticipant},
6};
7use futures_util::StreamExt;
8use hashbrown::HashMap;
9use network_protocol::{Cid, Pid, ProtocolMetricCache, ProtocolMetrics};
10#[cfg(feature = "metrics")]
11use prometheus::Registry;
12use rand::RngExt;
13use std::{
14    sync::{
15        Arc,
16        atomic::{AtomicBool, AtomicU64, Ordering},
17    },
18    time::Duration,
19};
20use tokio::{
21    io,
22    sync::{Mutex, mpsc, oneshot},
23};
24use tokio_stream::wrappers::UnboundedReceiverStream;
25use tracing::*;
26
27// Naming of Channels `x2x`
28//  - a: api
29//  - s: scheduler
30//  - b: bparticipant
31//  - p: prios
32//  - r: protocol
33//  - w: wire
34//  - c: channel/handshake
35
36#[derive(Debug)]
37struct ParticipantInfo {
38    secret: u128,
39    #[expect(dead_code)]
40    s2b_create_channel_s: mpsc::UnboundedSender<S2bCreateChannel>,
41    s2b_shutdown_bparticipant_s: Option<oneshot::Sender<S2bShutdownBparticipant>>,
42}
43
44type A2sListen = (ListenAddr, oneshot::Sender<io::Result<()>>);
45pub(crate) type A2sConnect = (
46    ConnectAddr,
47    oneshot::Sender<Result<Participant, NetworkConnectError>>,
48);
49type A2sDisconnect = (Pid, S2bShutdownBparticipant);
50
51#[derive(Debug)]
52struct ControlChannels {
53    a2s_listen_r: mpsc::UnboundedReceiver<A2sListen>,
54    a2s_connect_r: mpsc::UnboundedReceiver<A2sConnect>,
55    a2s_scheduler_shutdown_r: oneshot::Receiver<()>,
56    a2s_disconnect_r: mpsc::UnboundedReceiver<A2sDisconnect>,
57    b2s_prio_statistic_r: mpsc::UnboundedReceiver<B2sPrioStatistic>,
58}
59
60#[derive(Debug, Clone)]
61struct ParticipantChannels {
62    s2a_connected_s: mpsc::UnboundedSender<Participant>,
63    a2s_disconnect_s: mpsc::UnboundedSender<A2sDisconnect>,
64    b2s_prio_statistic_s: mpsc::UnboundedSender<B2sPrioStatistic>,
65}
66
67#[derive(Debug)]
68pub struct Scheduler {
69    local_pid: Pid,
70    local_secret: u128,
71    closed: AtomicBool,
72    run_channels: Option<ControlChannels>,
73    participant_channels: Arc<Mutex<Option<ParticipantChannels>>>,
74    participants: Arc<Mutex<HashMap<Pid, ParticipantInfo>>>,
75    channel_ids: Arc<AtomicU64>,
76    channel_listener: Mutex<HashMap<ProtocolInfo, oneshot::Sender<()>>>,
77    metrics: Arc<NetworkMetrics>,
78    protocol_metrics: Arc<ProtocolMetrics>,
79    output_limit: usize,
80}
81
82impl Scheduler {
83    pub fn new(
84        local_pid: Pid,
85        #[cfg(feature = "metrics")] registry: Option<&Registry>,
86        output_limit: usize,
87    ) -> (
88        Self,
89        mpsc::UnboundedSender<A2sListen>,
90        mpsc::UnboundedSender<A2sConnect>,
91        mpsc::UnboundedReceiver<Participant>,
92        oneshot::Sender<()>,
93    ) {
94        let (a2s_listen_s, a2s_listen_r) = mpsc::unbounded_channel::<A2sListen>();
95        let (a2s_connect_s, a2s_connect_r) = mpsc::unbounded_channel::<A2sConnect>();
96        let (s2a_connected_s, s2a_connected_r) = mpsc::unbounded_channel::<Participant>();
97        let (a2s_scheduler_shutdown_s, a2s_scheduler_shutdown_r) = oneshot::channel::<()>();
98        let (a2s_disconnect_s, a2s_disconnect_r) = mpsc::unbounded_channel::<A2sDisconnect>();
99        let (b2s_prio_statistic_s, b2s_prio_statistic_r) =
100            mpsc::unbounded_channel::<B2sPrioStatistic>();
101
102        let run_channels = Some(ControlChannels {
103            a2s_listen_r,
104            a2s_connect_r,
105            a2s_scheduler_shutdown_r,
106            a2s_disconnect_r,
107            b2s_prio_statistic_r,
108        });
109
110        let participant_channels = ParticipantChannels {
111            s2a_connected_s,
112            a2s_disconnect_s,
113            b2s_prio_statistic_s,
114        };
115
116        let metrics = Arc::new(NetworkMetrics::new(&local_pid).unwrap());
117        let protocol_metrics = Arc::new(ProtocolMetrics::new().unwrap());
118
119        #[cfg(feature = "metrics")]
120        {
121            if let Some(registry) = registry {
122                metrics.register(registry).unwrap();
123                protocol_metrics.register(registry).unwrap();
124            }
125        }
126
127        let mut rng = rand::rng();
128        let local_secret: u128 = rng.random();
129
130        (
131            Self {
132                local_pid,
133                local_secret,
134                closed: AtomicBool::new(false),
135                run_channels,
136                participant_channels: Arc::new(Mutex::new(Some(participant_channels))),
137                participants: Arc::new(Mutex::new(HashMap::new())),
138                channel_ids: Arc::new(AtomicU64::new(0)),
139                channel_listener: Mutex::new(HashMap::new()),
140                metrics,
141                protocol_metrics,
142                output_limit,
143            },
144            a2s_listen_s,
145            a2s_connect_s,
146            s2a_connected_r,
147            a2s_scheduler_shutdown_s,
148        )
149    }
150
151    pub async fn run(mut self) {
152        let run_channels = self
153            .run_channels
154            .take()
155            .expect("run() can only be called once");
156
157        tokio::join!(
158            self.listen_mgr(run_channels.a2s_listen_r),
159            self.connect_mgr(run_channels.a2s_connect_r),
160            self.disconnect_mgr(run_channels.a2s_disconnect_r),
161            self.prio_adj_mgr(run_channels.b2s_prio_statistic_r),
162            self.scheduler_shutdown_mgr(run_channels.a2s_scheduler_shutdown_r),
163        );
164    }
165
166    async fn listen_mgr(&self, a2s_listen_r: mpsc::UnboundedReceiver<A2sListen>) {
167        trace!("Start listen_mgr");
168        let a2s_listen_r = UnboundedReceiverStream::new(a2s_listen_r);
169        a2s_listen_r
170            .for_each_concurrent(None, |(address, s2a_listen_result_s)| {
171                let address = address;
172                let cids = Arc::clone(&self.channel_ids);
173
174                #[cfg(feature = "metrics")]
175                let mcache = self.metrics.connect_requests_cache(&address);
176
177                debug!(?address, "Got request to open a channel_creator");
178                self.metrics.listen_request(&address);
179                let (s2s_stop_listening_s, s2s_stop_listening_r) = oneshot::channel::<()>();
180                let (c2s_protocol_s, mut c2s_protocol_r) = mpsc::unbounded_channel();
181                let metrics = Arc::clone(&self.protocol_metrics);
182
183                async move {
184                    self.channel_listener
185                        .lock()
186                        .await
187                        .insert(address.clone().into(), s2s_stop_listening_s);
188
189                    #[cfg(feature = "metrics")]
190                    mcache.inc();
191
192                    let res = match address {
193                        ListenAddr::Tcp(addr) => {
194                            Protocols::with_tcp_listen(
195                                addr,
196                                cids,
197                                metrics,
198                                s2s_stop_listening_r,
199                                c2s_protocol_s,
200                            )
201                            .await
202                        },
203                        #[cfg(feature = "quic")]
204                        ListenAddr::Quic(addr, ref server_config) => {
205                            Protocols::with_quic_listen(
206                                addr,
207                                server_config.clone(),
208                                cids,
209                                metrics,
210                                s2s_stop_listening_r,
211                                c2s_protocol_s,
212                            )
213                            .await
214                        },
215                        ListenAddr::Mpsc(addr) => {
216                            Protocols::with_mpsc_listen(
217                                addr,
218                                cids,
219                                metrics,
220                                s2s_stop_listening_r,
221                                c2s_protocol_s,
222                            )
223                            .await
224                        },
225                        _ => unimplemented!(),
226                    };
227                    let _ = s2a_listen_result_s.send(res);
228
229                    while let Some((prot, con_addr, cid)) = c2s_protocol_r.recv().await {
230                        self.init_protocol(prot, con_addr, cid, None, true).await;
231                    }
232                }
233            })
234            .await;
235        trace!("Stop listen_mgr");
236    }
237
238    async fn connect_mgr(&self, mut a2s_connect_r: mpsc::UnboundedReceiver<A2sConnect>) {
239        trace!("Start connect_mgr");
240        while let Some((addr, pid_sender)) = a2s_connect_r.recv().await {
241            let cid = self.channel_ids.fetch_add(1, Ordering::Relaxed);
242            let metrics =
243                ProtocolMetricCache::new(&cid.to_string(), Arc::clone(&self.protocol_metrics));
244            self.metrics.connect_request(&addr);
245            let protocol = match addr.clone() {
246                ConnectAddr::Tcp(addr) => Protocols::with_tcp_connect(addr, metrics).await,
247                #[cfg(feature = "quic")]
248                ConnectAddr::Quic(addr, ref config, name) => {
249                    Protocols::with_quic_connect(addr, config.clone(), name, metrics).await
250                },
251                ConnectAddr::Mpsc(addr) => Protocols::with_mpsc_connect(addr, metrics).await,
252                _ => unimplemented!(),
253            };
254            let protocol = match protocol {
255                Ok(p) => p,
256                Err(e) => {
257                    pid_sender.send(Err(e)).unwrap();
258                    continue;
259                },
260            };
261            self.init_protocol(protocol, addr, cid, Some(pid_sender), false)
262                .await;
263        }
264        trace!("Stop connect_mgr");
265    }
266
267    async fn disconnect_mgr(&self, a2s_disconnect_r: mpsc::UnboundedReceiver<A2sDisconnect>) {
268        trace!("Start disconnect_mgr");
269
270        let a2s_disconnect_r = UnboundedReceiverStream::new(a2s_disconnect_r);
271        a2s_disconnect_r
272            .for_each_concurrent(
273                None,
274                |(pid, (timeout_time, return_once_successful_shutdown))| {
275                    //Closing Participants is done the following way:
276                    // 1. We drop our senders and receivers
277                    // 2. we need to close BParticipant, this will drop its senderns and receivers
278                    // 3. Participant will try to access the BParticipant senders and receivers with
279                    // their next api action, it will fail and be closed then.
280                    let participants = Arc::clone(&self.participants);
281                    async move {
282                        trace!(?pid, "Got request to close participant");
283                        let pi = participants.lock().await.remove(&pid);
284                        trace!(?pid, "dropped participants lock");
285                        let r = if let Some(mut pi) = pi {
286                            let (finished_sender, finished_receiver) = oneshot::channel();
287                            // NOTE: If there's nothing to synchronize on (because the send failed)
288                            // we can assume everything relevant was shut down.
289                            let _ = pi
290                                .s2b_shutdown_bparticipant_s
291                                .take()
292                                .unwrap()
293                                .send((timeout_time, finished_sender));
294                            drop(pi);
295                            trace!(?pid, "dropped bparticipant, waiting for finish");
296                            // If await fails, already shut down, so send Ok(()).
297                            let e = finished_receiver.await.unwrap_or(Ok(()));
298                            trace!(?pid, "waiting completed");
299                            // can fail as api.rs has a timeout
300                            return_once_successful_shutdown.send(e)
301                        } else {
302                            debug!(?pid, "Looks like participant is already dropped");
303                            return_once_successful_shutdown.send(Ok(()))
304                        };
305                        if r.is_err() {
306                            trace!(?pid, "Closed participant with timeout");
307                        } else {
308                            trace!(?pid, "Closed participant");
309                        }
310                    }
311                },
312            )
313            .await;
314        trace!("Stop disconnect_mgr");
315    }
316
317    async fn prio_adj_mgr(
318        &self,
319        mut b2s_prio_statistic_r: mpsc::UnboundedReceiver<B2sPrioStatistic>,
320    ) {
321        trace!("Start prio_adj_mgr");
322        while let Some((_pid, _frame_cnt, _unused)) = b2s_prio_statistic_r.recv().await {
323
324            //TODO adjust prios in participants here!
325        }
326        trace!("Stop prio_adj_mgr");
327    }
328
329    async fn scheduler_shutdown_mgr(&self, a2s_scheduler_shutdown_r: oneshot::Receiver<()>) {
330        trace!("Start scheduler_shutdown_mgr");
331        if a2s_scheduler_shutdown_r.await.is_err() {
332            warn!("Schedule shutdown got triggered because a2s_scheduler_shutdown_r failed");
333        };
334        info!("Shutdown of scheduler requested");
335        self.closed.store(true, Ordering::SeqCst);
336        debug!("Shutting down all BParticipants gracefully");
337        let mut participants = self.participants.lock().await;
338        let waitings = participants
339            .drain()
340            .map(|(pid, mut pi)| {
341                trace!(?pid, "Shutting down BParticipants");
342                let (finished_sender, finished_receiver) = oneshot::channel();
343                pi.s2b_shutdown_bparticipant_s
344                    .take()
345                    .unwrap()
346                    .send((Duration::from_secs(120), finished_sender))
347                    .unwrap();
348                (pid, finished_receiver)
349            })
350            .collect::<Vec<_>>();
351        drop(participants);
352        debug!("Wait for partiticipants to be shut down");
353        for (pid, recv) in waitings {
354            if let Err(e) = recv.await {
355                error!(
356                    ?pid,
357                    ?e,
358                    "Failed to finish sending all remaining messages to participant when shutting \
359                     down"
360                );
361            };
362        }
363        debug!("shutting down protocol listeners");
364        for (addr, end_channel_sender) in self.channel_listener.lock().await.drain() {
365            trace!(?addr, "stopping listen on protocol");
366            if let Err(e) = end_channel_sender.send(()) {
367                warn!(?addr, ?e, "listener crashed/disconnected already");
368            }
369        }
370        debug!("Scheduler shut down gracefully");
371        //removing the possibility to create new participants, needed to close down
372        // some mgr:
373        self.participant_channels.lock().await.take();
374
375        trace!("Stop scheduler_shutdown_mgr");
376    }
377
378    async fn init_protocol(
379        &self,
380        mut protocol: Protocols,
381        con_addr: ConnectAddr, //address necessary to connect to the remote
382        cid: Cid,
383        s2a_return_pid_s: Option<oneshot::Sender<Result<Participant, NetworkConnectError>>>,
384        send_handshake: bool,
385    ) {
386        //channels are unknown till PID is known!
387        /* When A connects to a NETWORK, we, the listener answers with a Handshake.
388          Pro: - Its easier to debug, as someone who opens a port gets a magic number back!
389          Contra: - DOS possibility because we answer first
390                  - Speed, because otherwise the message can be send with the creation
391        */
392        let participant_channels = self.participant_channels.lock().await.clone().unwrap();
393        // spawn is needed here, e.g. for TCP connect it would mean that only 1
394        // participant can be in handshake phase ever! Someone could deadlock
395        // the whole server easily for new clients UDP doesnt work at all, as
396        // the UDP listening is done in another place.
397        let participants = Arc::clone(&self.participants);
398        let metrics = Arc::clone(&self.metrics);
399        let local_pid = self.local_pid;
400        let local_secret = self.local_secret;
401        let output_limit = self.output_limit;
402        // this is necessary for UDP to work at all and to remove code duplication
403        tokio::spawn(
404            async move {
405                trace!(?cid, "Open channel and be ready for Handshake");
406                use network_protocol::InitProtocol;
407                let init_result = protocol
408                    .initialize(send_handshake, local_pid, local_secret)
409                    .instrument(info_span!("handshake", ?cid))
410                    .await;
411                match init_result {
412                    Ok((pid, sid, secret)) => {
413                        trace!(
414                            ?cid,
415                            ?pid,
416                            "Detected that my channel is ready!, activating it :)"
417                        );
418                        let mut participants = participants.lock().await;
419                        if !participants.contains_key(&pid) {
420                            debug!(?cid, "New participant connected via a channel");
421                            let (
422                                bparticipant,
423                                a2b_open_stream_s,
424                                b2a_stream_opened_r,
425                                b2a_event_r,
426                                s2b_create_channel_s,
427                                s2b_shutdown_bparticipant_s,
428                                b2a_bandwidth_stats_r,
429                            ) = BParticipant::new(
430                                local_pid,
431                                pid,
432                                sid,
433                                Arc::clone(&metrics),
434                                output_limit,
435                            );
436
437                            let participant = Participant::new(
438                                local_pid,
439                                pid,
440                                a2b_open_stream_s,
441                                b2a_stream_opened_r,
442                                b2a_event_r,
443                                b2a_bandwidth_stats_r,
444                                participant_channels.a2s_disconnect_s,
445                            );
446
447                            #[cfg(feature = "metrics")]
448                            metrics.participants_connected_total.inc();
449                            participants.insert(pid, ParticipantInfo {
450                                secret,
451                                s2b_create_channel_s: s2b_create_channel_s.clone(),
452                                s2b_shutdown_bparticipant_s: Some(s2b_shutdown_bparticipant_s),
453                            });
454                            drop(participants);
455                            trace!("dropped participants lock");
456                            let p = pid;
457                            tokio::spawn(
458                                bparticipant
459                                    .run(participant_channels.b2s_prio_statistic_s)
460                                    .instrument(info_span!("remote", ?p)),
461                            );
462                            //create a new channel within BParticipant and wait for it to run
463                            let (b2s_create_channel_done_s, b2s_create_channel_done_r) =
464                                oneshot::channel();
465                            //From now on wire connects directly with bparticipant!
466                            s2b_create_channel_s
467                                .send((cid, sid, protocol, con_addr, b2s_create_channel_done_s))
468                                .unwrap();
469                            b2s_create_channel_done_r.await.unwrap();
470                            if let Some(pid_oneshot) = s2a_return_pid_s {
471                                // someone is waiting with `connect`, so give them their PID
472                                pid_oneshot.send(Ok(participant)).unwrap();
473                            } else {
474                                // no one is waiting on this Participant, return in to Network
475                                if participant_channels
476                                    .s2a_connected_s
477                                    .send(participant)
478                                    .is_err()
479                                {
480                                    warn!("seems like Network already got closed");
481                                };
482                            }
483                        } else {
484                            let pi = &participants[&pid];
485                            trace!(
486                                ?cid,
487                                "2nd+ channel of participant, going to compare security ids"
488                            );
489                            if pi.secret != secret {
490                                warn!(
491                                    ?cid,
492                                    ?pid,
493                                    ?secret,
494                                    "Detected incompatible Secret!, this is probably an attack!"
495                                );
496                                error!(?cid, "Just dropping here, TODO handle this correctly!");
497                                //TODO
498                                if let Some(pid_oneshot) = s2a_return_pid_s {
499                                    // someone is waiting with `connect`, so give them their Error
500                                    pid_oneshot
501                                        .send(Err(NetworkConnectError::InvalidSecret))
502                                        .unwrap();
503                                }
504                                return;
505                            }
506                            error!(
507                                ?cid,
508                                "Ufff i cant answer the pid_oneshot. as i need to create the SAME \
509                                 participant. maybe switch to ARC"
510                            );
511                        }
512                        //From now on this CHANNEL can receiver other frames!
513                        // move directly to participant!
514                    },
515                    Err(e) => {
516                        debug!(?cid, ?e, "Handshake from a new connection failed");
517                        #[cfg(feature = "metrics")]
518                        metrics.failed_handshakes_total.inc();
519                        if let Some(pid_oneshot) = s2a_return_pid_s {
520                            // someone is waiting with `connect`, so give them their Error
521                            trace!(?cid, "returning the Err to api who requested the connect");
522                            pid_oneshot
523                                .send(Err(NetworkConnectError::Handshake(e)))
524                                .unwrap();
525                        }
526                    },
527                }
528            }
529            .instrument(info_span!("")),
530        ); /*WORKAROUND FOR SPAN NOT TO GET LOST*/
531    }
532}