1use std::{collections::BTreeMap, future::Future, sync::Arc};
5
6use custom_debug_derive::Debug;
7use futures::stream::{FuturesUnordered, StreamExt};
8use linera_base::{
9 crypto::ValidatorPublicKey,
10 data_types::{Blob, BlobContent, BlockHeight},
11 identifiers::{BlobId, ChainId},
12 time::Duration,
13};
14use linera_chain::types::ConfirmedBlockCertificate;
15use linera_storage::Clock as _;
16use rand::distributions::{Distribution, WeightedIndex};
17use tracing::{instrument, warn};
18
19use super::{
20 cache::{RequestsCache, SubsumingKey},
21 in_flight_tracker::{InFlightMatch, InFlightTracker},
22 node_info::NodeInfo,
23 request::{RequestKey, RequestResult},
24 scoring::ScoringWeights,
25};
26use crate::{
27 client::{
28 communicate_concurrently,
29 requests_scheduler::{in_flight_tracker::Subscribed, request::Cacheable},
30 ClockOf, RequestsSchedulerConfig,
31 },
32 environment::Environment,
33 node::{NodeError, ValidatorNode},
34 remote_node::RemoteNode,
35};
36
37#[cfg(with_metrics)]
38pub(crate) mod metrics {
39 use linera_base::prometheus_util::{
40 exponential_bucket_latencies, register_histogram_vec, register_int_counter,
41 register_int_counter_vec,
42 };
43 use prometheus::{HistogramVec, IntCounter, IntCounterVec};
44
45 linera_base::declare_metrics! {
46 pub(super) static VALIDATOR_RESPONSE_TIME: HistogramVec =
48 register_histogram_vec(
49 "requests_scheduler_response_time_ms",
50 "Response time for requests to validators in milliseconds",
51 &["validator", "address"],
52 exponential_bucket_latencies(10000.0), );
54
55 pub(super) static VALIDATOR_REQUEST_TOTAL: IntCounterVec =
57 register_int_counter_vec(
58 "requests_scheduler_request_total",
59 "Total number of requests made to each validator",
60 &["validator", "address"],
61 );
62
63 pub(super) static VALIDATOR_REQUEST_SUCCESS: IntCounterVec =
65 register_int_counter_vec(
66 "requests_scheduler_request_success",
67 "Number of successful requests to each validator",
68 &["validator", "address"],
69 );
70
71 pub(super) static REQUEST_CACHE_DEDUPLICATION: IntCounter =
73 register_int_counter(
74 "requests_scheduler_request_deduplication_total",
75 "Number of requests that were deduplicated by finding the result in the cache.",
76 );
77
78 pub static REQUEST_CACHE_HIT: IntCounter =
80 register_int_counter(
81 "requests_scheduler_request_cache_hit_total",
82 "Number of requests that were served from cache",
83 );
84 }
85}
86
87#[derive(Debug, Clone)]
118pub struct RequestsScheduler<Env: Environment> {
119 nodes: Arc<tokio::sync::RwLock<BTreeMap<ValidatorPublicKey, NodeInfo<Env>>>>,
122 weights: ScoringWeights,
124 alpha: f64,
126 max_expected_latency: f64,
128 retry_delay: Duration,
130 in_flight_tracker: InFlightTracker<RemoteNode<Env::ValidatorNode>>,
132 cache: RequestsCache<RequestKey, RequestResult>,
134 clock: ClockOf<Env>,
136}
137
138impl<Env: Environment> RequestsScheduler<Env> {
139 pub fn new(
141 nodes: impl IntoIterator<Item = RemoteNode<Env::ValidatorNode>>,
142 config: &RequestsSchedulerConfig,
143 clock: ClockOf<Env>,
144 ) -> Self {
145 Self::with_config(
146 nodes,
147 ScoringWeights::default(),
148 config.alpha,
149 config.max_accepted_latency_ms,
150 Duration::from_millis(config.cache_ttl_ms),
151 config.cache_max_size,
152 Duration::from_millis(config.max_request_ttl_ms),
153 Duration::from_millis(config.retry_delay_ms),
154 clock,
155 )
156 }
157
158 #[expect(clippy::too_many_arguments)]
171 pub fn with_config(
172 nodes: impl IntoIterator<Item = RemoteNode<Env::ValidatorNode>>,
173 weights: ScoringWeights,
174 alpha: f64,
175 max_expected_latency_ms: f64,
176 cache_ttl: Duration,
177 max_cache_size: usize,
178 max_request_ttl: Duration,
179 retry_delay: Duration,
180 clock: ClockOf<Env>,
181 ) -> Self {
182 assert!(alpha > 0.0 && alpha < 1.0, "Alpha must be in (0, 1) range");
183 Self {
184 nodes: Arc::new(tokio::sync::RwLock::new(
185 nodes
186 .into_iter()
187 .map(|node| {
188 (
189 node.public_key,
190 NodeInfo::with_config(node, weights, alpha, max_expected_latency_ms),
191 )
192 })
193 .collect(),
194 )),
195 weights,
196 alpha,
197 max_expected_latency: max_expected_latency_ms,
198 retry_delay,
199 in_flight_tracker: InFlightTracker::new(max_request_ttl),
200 cache: RequestsCache::new(cache_ttl, max_cache_size),
201 clock,
202 }
203 }
204
205 #[allow(unused)]
236 async fn with_best<R, F, Fut>(&self, key: RequestKey, operation: F) -> Result<R, NodeError>
237 where
238 R: Cacheable + Clone + Send + 'static,
239 F: Fn(RemoteNode<Env::ValidatorNode>) -> Fut,
240 Fut: Future<Output = Result<R, NodeError>> + 'static,
241 {
242 let peer = self
244 .select_best_peer()
245 .await
246 .ok_or_else(|| NodeError::WorkerError {
247 error: "No validators available".to_string(),
248 })?;
249 self.with_peer(key, peer, operation).await
250 }
251
252 async fn with_peer<R, F, Fut>(
271 &self,
272 key: RequestKey,
273 peer: RemoteNode<Env::ValidatorNode>,
274 operation: F,
275 ) -> Result<R, NodeError>
276 where
277 R: Cacheable + Clone + Send + 'static,
278 F: Fn(RemoteNode<Env::ValidatorNode>) -> Fut,
279 Fut: Future<Output = Result<R, NodeError>> + 'static,
280 {
281 self.add_peer(peer.clone()).await;
282 self.in_flight_tracker
283 .add_alternative_peer(&key, peer.clone())
284 .await;
285
286 let nodes = self.nodes.clone();
288 let clock = self.clock.clone();
289 self.deduplicated_request(key, peer, move |peer| {
290 let fut = operation(peer.clone());
291 let nodes = nodes.clone();
292 let clock = clock.clone();
293 async move { Self::track_request(nodes, peer, fut, &clock).await }
294 })
295 .await
296 }
297
298 #[instrument(level = "trace", skip_all)]
299 async fn download_blob(
300 &self,
301 peers: &[RemoteNode<Env::ValidatorNode>],
302 blob_id: BlobId,
303 hedge_delay: Duration,
304 ) -> Result<Option<Blob>, NodeError> {
305 let key = RequestKey::Blob(blob_id);
306 communicate_concurrently(
307 peers,
308 async move |peer| {
309 self.with_peer(key, peer, move |peer| async move {
310 peer.download_blob(blob_id).await
311 })
312 .await
313 },
314 hedge_delay,
315 &self.clock,
316 )
317 .await
318 .map_err(|errors| {
319 for (validator, error) in &errors {
320 warn!(
321 %validator,
322 %blob_id,
323 %error,
324 "failed to download blob from validator",
325 );
326 }
327 errors
328 .into_iter()
329 .last()
330 .map_or(NodeError::NoValidators, |(_, error)| error)
331 })
332 }
333
334 #[instrument(level = "trace", skip_all)]
338 pub async fn download_blobs(
339 &self,
340 peers: &[RemoteNode<Env::ValidatorNode>],
341 blob_ids: &[BlobId],
342 hedge_delay: Duration,
343 ) -> Result<Option<Vec<Blob>>, NodeError> {
344 let mut stream = blob_ids
345 .iter()
346 .map(|blob_id| self.download_blob(peers, *blob_id, hedge_delay))
347 .collect::<FuturesUnordered<_>>();
348
349 let mut blobs = Vec::new();
350 while let Some(maybe_blob) = stream.next().await {
351 blobs.push(maybe_blob?);
352 }
353 Ok(blobs.into_iter().collect::<Option<Vec<_>>>())
354 }
355
356 pub async fn download_certificates(
358 &self,
359 peer: &RemoteNode<Env::ValidatorNode>,
360 chain_id: ChainId,
361 start: BlockHeight,
362 limit: u64,
363 ) -> Result<Vec<ConfirmedBlockCertificate>, NodeError> {
364 let heights = (start.0..start.0 + limit)
365 .map(BlockHeight)
366 .collect::<Vec<_>>();
367 self.with_peer(
368 RequestKey::Certificates {
369 chain_id,
370 heights: heights.clone(),
371 },
372 peer.clone(),
373 move |peer| {
374 let heights = heights.clone();
375 async move {
376 Box::pin(peer.download_certificates_by_heights(chain_id, heights)).await
377 }
378 },
379 )
380 .await
381 }
382
383 pub async fn download_certificates_from_validators(
386 &self,
387 peers: &[RemoteNode<Env::ValidatorNode>],
388 chain_id: ChainId,
389 start: BlockHeight,
390 limit: u64,
391 hedge_delay: Duration,
392 ) -> Result<Vec<ConfirmedBlockCertificate>, NodeError> {
393 if peers.is_empty() {
394 return Err(NodeError::NoValidators);
395 }
396 let heights = (start.0..start.0 + limit)
397 .map(BlockHeight)
398 .collect::<Vec<_>>();
399 let key = RequestKey::Certificates {
400 chain_id,
401 heights: heights.clone(),
402 };
403 communicate_concurrently(
404 peers,
405 async move |peer| {
406 self.with_peer(key, peer, move |peer| {
407 let heights = heights.clone();
408 async move {
409 Box::pin(peer.download_certificates_by_heights(chain_id, heights)).await
410 }
411 })
412 .await
413 },
414 hedge_delay,
415 &self.clock,
416 )
417 .await
418 .map_err(|errors| {
419 for (validator, error) in &errors {
420 warn!(
421 %validator,
422 %chain_id,
423 %error,
424 "failed to download certificates from validator",
425 );
426 }
427 errors
428 .into_iter()
429 .last()
430 .map_or(NodeError::NoValidators, |(_, error)| error)
431 })
432 }
433
434 pub async fn download_certificates_by_heights(
436 &self,
437 peer: &RemoteNode<Env::ValidatorNode>,
438 chain_id: ChainId,
439 heights: Vec<BlockHeight>,
440 ) -> Result<Vec<ConfirmedBlockCertificate>, NodeError> {
441 self.with_peer(
442 RequestKey::Certificates {
443 chain_id,
444 heights: heights.clone(),
445 },
446 peer.clone(),
447 move |peer| {
448 let heights = heights.clone();
449 async move {
450 peer.download_certificates_by_heights(chain_id, heights)
451 .await
452 }
453 },
454 )
455 .await
456 }
457
458 pub async fn download_certificate_for_blob(
460 &self,
461 peer: &RemoteNode<Env::ValidatorNode>,
462 blob_id: BlobId,
463 ) -> Result<ConfirmedBlockCertificate, NodeError> {
464 self.with_peer(
465 RequestKey::CertificateForBlob(blob_id),
466 peer.clone(),
467 move |peer| async move { peer.download_certificate_for_blob(blob_id).await },
468 )
469 .await
470 }
471
472 pub async fn download_pending_blob(
474 &self,
475 peer: &RemoteNode<Env::ValidatorNode>,
476 chain_id: ChainId,
477 blob_id: BlobId,
478 ) -> Result<BlobContent, NodeError> {
479 self.with_peer(
480 RequestKey::PendingBlob { chain_id, blob_id },
481 peer.clone(),
482 move |peer| async move { peer.node.download_pending_blob(chain_id, blob_id).await },
483 )
484 .await
485 }
486
487 pub async fn get_alternative_peers(
492 &self,
493 key: &RequestKey,
494 ) -> Option<Vec<RemoteNode<Env::ValidatorNode>>> {
495 self.in_flight_tracker.get_alternative_peers(key).await
496 }
497
498 async fn track_request<T, Fut>(
512 nodes: Arc<tokio::sync::RwLock<BTreeMap<ValidatorPublicKey, NodeInfo<Env>>>>,
513 peer: RemoteNode<Env::ValidatorNode>,
514 operation: Fut,
515 clock: &ClockOf<Env>,
516 ) -> Result<T, NodeError>
517 where
518 Fut: Future<Output = Result<T, NodeError>> + 'static,
519 {
520 let start_time = clock.current_time();
521 let public_key = peer.public_key;
522
523 let result = operation.await;
525
526 let response_time_ms = clock.current_time().delta_since(start_time).as_micros() / 1000;
528 let is_success = result.is_ok();
529 {
530 let mut nodes_guard = nodes.write().await;
531 if let Some(info) = nodes_guard.get_mut(&public_key) {
532 info.update_metrics(is_success, response_time_ms);
533 let score = info.calculate_score().await;
534 tracing::trace!(
535 node = %public_key,
536 address = %info.node.node.address(),
537 success = %is_success,
538 response_time_ms = %response_time_ms,
539 score = %score,
540 total_requests = %info.total_requests(),
541 "Request completed"
542 );
543 }
544 }
545
546 #[cfg(with_metrics)]
548 {
549 let validator_name = public_key.to_string();
550 let address = peer.address();
551 metrics::VALIDATOR_RESPONSE_TIME
552 .with_label_values(&[&validator_name, &address])
553 .observe(response_time_ms as f64);
554 metrics::VALIDATOR_REQUEST_TOTAL
555 .with_label_values(&[&validator_name, &address])
556 .inc();
557 if is_success {
558 metrics::VALIDATOR_REQUEST_SUCCESS
559 .with_label_values(&[&validator_name, &address])
560 .inc();
561 }
562 }
563
564 result
565 }
566
567 async fn deduplicated_request<T, F, Fut>(
584 &self,
585 key: RequestKey,
586 peer: RemoteNode<Env::ValidatorNode>,
587 operation: F,
588 ) -> Result<T, NodeError>
589 where
590 T: Cacheable + Clone + Send + 'static,
591 F: Fn(RemoteNode<Env::ValidatorNode>) -> Fut,
592 Fut: Future<Output = Result<T, NodeError>> + 'static,
593 {
594 if let Some(result) = self.cache.get(&key).await {
596 return Ok(result);
597 }
598
599 if let Some(in_flight_match) = self
601 .in_flight_tracker
602 .try_subscribe(&key, self.clock.current_time())
603 {
604 match in_flight_match {
605 InFlightMatch::Exact(Subscribed(mut receiver)) => {
606 tracing::trace!(
607 ?key,
608 "deduplicating request (exact match) - joining existing in-flight request"
609 );
610 #[cfg(with_metrics)]
611 metrics::REQUEST_CACHE_DEDUPLICATION.inc();
612 match receiver.recv().await {
614 Ok(result) => match result.as_ref().clone() {
615 Ok(res) => match T::try_from(res) {
616 Ok(converted) => {
617 tracing::trace!(
618 ?key,
619 "received result from deduplicated in-flight request"
620 );
621 return Ok(converted);
622 }
623 Err(_) => {
624 tracing::warn!(
625 ?key,
626 "failed to convert result from deduplicated in-flight request, will execute independently"
627 );
628 }
629 },
630 Err(error) => {
631 tracing::trace!(
632 ?key,
633 %error,
634 "in-flight request failed",
635 );
636 }
638 },
639 Err(_) => {
640 tracing::trace!(?key, "in-flight request sender dropped");
641 }
643 }
644 }
645 InFlightMatch::Subsuming {
646 key: subsuming_key,
647 outcome: Subscribed(mut receiver),
648 } => {
649 tracing::trace!(
650 ?key,
651 subsumed_by = ?subsuming_key,
652 "deduplicating request (subsumption) - joining larger in-flight request"
653 );
654 #[cfg(with_metrics)]
655 metrics::REQUEST_CACHE_DEDUPLICATION.inc();
656 match receiver.recv().await {
658 Ok(result) => {
659 match result.as_ref() {
660 Ok(res) => {
661 if let Some(extracted) =
662 key.try_extract_result(&subsuming_key, res)
663 {
664 tracing::trace!(
665 ?key,
666 "extracted subset result from larger in-flight request"
667 );
668 match T::try_from(extracted) {
669 Ok(converted) => return Ok(converted),
670 Err(_) => {
671 tracing::trace!(
672 ?key,
673 "failed to convert extracted result, will execute independently"
674 );
675 }
676 }
677 } else {
678 tracing::trace!(
680 ?key,
681 "failed to extract from subsuming request, will execute independently"
682 );
683 }
684 }
685 Err(error) => {
686 tracing::trace!(
687 ?key,
688 ?error,
689 "subsuming in-flight request failed",
690 );
691 }
693 }
694 }
695 Err(_) => {
696 tracing::trace!(?key, "subsuming in-flight request sender dropped");
697 }
698 }
699 }
700 }
701 };
702
703 let in_flight_guard = self
708 .in_flight_tracker
709 .insert_new(key.clone(), self.clock.current_time());
710
711 self.in_flight_tracker
713 .remove_alternative_peer(&key, &peer)
714 .await;
715
716 tracing::trace!(?key, ?peer, "executing staggered parallel request");
719 let result = self
720 .try_staggered_parallel(&key, peer, &operation, self.retry_delay)
721 .await;
722
723 let result_for_broadcast: Result<RequestResult, NodeError> = result.clone().map(Into::into);
724 let shared_result = Arc::new(result_for_broadcast);
725
726 in_flight_guard.complete_and_broadcast(shared_result.clone());
728
729 if let Ok(success) = shared_result.as_ref() {
730 self.cache
731 .store(
732 key.clone(),
733 Arc::new(success.clone()),
734 self.clock.current_time(),
735 )
736 .await;
737 }
738 result
739 }
740
741 async fn try_staggered_parallel<T, F, Fut>(
756 &self,
757 key: &RequestKey,
758 first_peer: RemoteNode<Env::ValidatorNode>,
759 operation: &F,
760 staggered_delay: Duration,
761 ) -> Result<T, NodeError>
762 where
763 T: 'static,
764 F: Fn(RemoteNode<Env::ValidatorNode>) -> Fut,
765 Fut: Future<Output = Result<T, NodeError>> + 'static,
766 {
767 crate::client::hedged_fan_out(
770 first_peer,
771 || self.in_flight_tracker.pop_alternative_peer(key),
772 operation,
773 |started| {
774 let n = u32::try_from(started).unwrap_or(u32::MAX);
775 staggered_delay.saturating_mul(n)
776 },
777 &self.clock,
778 )
779 .await
780 .map_err(|errors| {
781 errors
782 .into_iter()
783 .next_back()
784 .unwrap_or(NodeError::UnexpectedMessage)
785 })
786 }
787
788 async fn peers_by_score(&self) -> Vec<(f64, RemoteNode<Env::ValidatorNode>)> {
797 let nodes = self.nodes.read().await;
798
799 let mut scored_nodes = Vec::new();
801 for info in nodes.values() {
802 let score = info.calculate_score().await;
803 scored_nodes.push((score, info.node.clone()));
804 }
805
806 scored_nodes.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
808
809 scored_nodes
810 }
811
812 async fn select_best_peer(&self) -> Option<RemoteNode<Env::ValidatorNode>> {
823 let scored_nodes = self.peers_by_score().await;
824
825 if scored_nodes.is_empty() {
826 return None;
827 }
828
829 let top_count = scored_nodes.len().min(3);
831 let top_nodes = &scored_nodes[..top_count];
832
833 let weights: Vec<f64> = top_nodes.iter().map(|(score, _)| score.max(0.01)).collect();
836
837 if let Ok(dist) = WeightedIndex::new(&weights) {
838 let mut rng = rand::thread_rng();
839 let index = dist.sample(&mut rng);
840 Some(top_nodes[index].1.clone())
841 } else {
842 tracing::warn!("failed to create weighted distribution, defaulting to best node");
844 Some(scored_nodes[0].1.clone())
845 }
846 }
847
848 async fn add_peer(&self, node: RemoteNode<Env::ValidatorNode>) {
850 let mut nodes = self.nodes.write().await;
851 let public_key = node.public_key;
852 nodes.entry(public_key).or_insert_with(|| {
853 NodeInfo::with_config(node, self.weights, self.alpha, self.max_expected_latency)
854 });
855 }
856}
857
858#[cfg(test)]
859mod tests {
860 use std::sync::{
861 atomic::{AtomicUsize, Ordering},
862 Arc,
863 };
864
865 use linera_base::{
866 crypto::{CryptoHash, InMemorySigner},
867 data_types::{BlockHeight, TimeDelta},
868 identifiers::ChainId,
869 time::Duration,
870 };
871 use linera_chain::types::ConfirmedBlockCertificate;
872 use linera_storage::TestClock;
873 use tokio::sync::oneshot;
874
875 use super::{super::request::RequestKey, *};
876 use crate::{
877 client::requests_scheduler::{MAX_REQUEST_TTL_MS, STAGGERED_DELAY_MS},
878 node::NodeError,
879 };
880
881 type TestEnvironment = crate::environment::Test;
882
883 fn create_test_manager(
888 in_flight_timeout: Duration,
889 cache_ttl: Duration,
890 ) -> Arc<RequestsScheduler<TestEnvironment>> {
891 let mut manager = RequestsScheduler::with_config(
892 vec![], ScoringWeights::default(),
894 0.1,
895 1000.0,
896 cache_ttl,
897 100,
898 in_flight_timeout,
899 Duration::from_millis(STAGGERED_DELAY_MS),
900 TestClock::new(),
901 );
902 manager.in_flight_tracker = InFlightTracker::new(in_flight_timeout);
904 Arc::new(manager)
905 }
906
907 fn test_key() -> RequestKey {
909 RequestKey::Certificates {
910 chain_id: ChainId(CryptoHash::test_hash("test")),
911 heights: vec![BlockHeight(0), BlockHeight(1)],
912 }
913 }
914
915 fn dummy_peer() -> RemoteNode<<TestEnvironment as Environment>::ValidatorNode> {
917 use crate::test_utils::{MemoryStorageBuilder, TestBuilder};
918
919 let mut builder = futures::executor::block_on(async {
921 TestBuilder::new(
922 MemoryStorageBuilder::default(),
923 1,
924 0,
925 linera_base::crypto::InMemorySigner::new(None),
926 )
927 .await
928 .unwrap()
929 });
930
931 let node = builder.node(0);
932 let public_key = node.name();
933 RemoteNode { public_key, node }
934 }
935
936 #[tokio::test]
937 async fn test_cache_hit_returns_cached_result() {
938 let manager = create_test_manager(Duration::from_secs(60), Duration::from_secs(60));
940 let key = test_key();
941 let peer = dummy_peer();
942
943 let execution_count = Arc::new(AtomicUsize::new(0));
945 let execution_count_clone = execution_count.clone();
946
947 let result1: Result<Vec<ConfirmedBlockCertificate>, NodeError> = manager
949 .deduplicated_request(key.clone(), peer.clone(), |_| {
950 let count = execution_count_clone.clone();
951 async move {
952 count.fetch_add(1, Ordering::SeqCst);
953 Ok(vec![])
954 }
955 })
956 .await;
957
958 assert!(result1.is_ok());
959 assert_eq!(execution_count.load(Ordering::SeqCst), 1);
960
961 let execution_count_clone2 = execution_count.clone();
963 let result2: Result<Vec<ConfirmedBlockCertificate>, NodeError> = manager
964 .deduplicated_request(key.clone(), peer.clone(), |_| {
965 let count = execution_count_clone2.clone();
966 async move {
967 count.fetch_add(1, Ordering::SeqCst);
968 Ok(vec![])
969 }
970 })
971 .await;
972
973 assert_eq!(result1, result2);
974 assert_eq!(execution_count.load(Ordering::SeqCst), 1);
976 }
977
978 #[tokio::test]
979 async fn test_in_flight_request_deduplication() {
980 let manager = create_test_manager(Duration::from_secs(60), Duration::from_secs(60));
981 let key = test_key();
982 let peer = dummy_peer();
983
984 let execution_count = Arc::new(AtomicUsize::new(0));
986
987 let (tx, rx) = oneshot::channel();
989 let rx = Arc::new(tokio::sync::Mutex::new(Some(rx)));
990
991 let manager_clone = Arc::clone(&manager);
993 let key_clone = key.clone();
994 let execution_count_clone = execution_count.clone();
995 let rx_clone = Arc::clone(&rx);
996 let peer_clone = peer.clone();
997 let first_request = tokio::spawn(async move {
998 manager_clone
999 .deduplicated_request(key_clone, peer_clone, |_| {
1000 let count = execution_count_clone.clone();
1001 let rx = Arc::clone(&rx_clone);
1002 async move {
1003 count.fetch_add(1, Ordering::SeqCst);
1004 if let Some(receiver) = rx.lock().await.take() {
1006 receiver.await.unwrap();
1007 }
1008 Ok(vec![])
1009 }
1010 })
1011 .await
1012 });
1013
1014 let execution_count_clone2 = execution_count.clone();
1016 let second_request = tokio::spawn(async move {
1017 manager
1018 .deduplicated_request(key, peer, |_| {
1019 let count = execution_count_clone2.clone();
1020 async move {
1021 count.fetch_add(1, Ordering::SeqCst);
1022 Ok(vec![])
1023 }
1024 })
1025 .await
1026 });
1027
1028 tx.send(()).unwrap();
1030
1031 let result1: Result<Vec<ConfirmedBlockCertificate>, NodeError> =
1033 first_request.await.unwrap();
1034 let result2: Result<Vec<ConfirmedBlockCertificate>, NodeError> =
1035 second_request.await.unwrap();
1036
1037 assert!(result1.is_ok());
1038 assert_eq!(result1, result2);
1039
1040 assert_eq!(execution_count.load(Ordering::SeqCst), 1);
1042 }
1043
1044 #[tokio::test]
1045 async fn test_multiple_subscribers_all_notified() {
1046 let manager = create_test_manager(Duration::from_secs(60), Duration::from_secs(60));
1047 let key = test_key();
1048 let peer = dummy_peer();
1049
1050 let execution_count = Arc::new(AtomicUsize::new(0));
1052
1053 let (tx, rx) = oneshot::channel();
1055 let rx = Arc::new(tokio::sync::Mutex::new(Some(rx)));
1056
1057 let manager_clone1 = Arc::clone(&manager);
1059 let key_clone1 = key.clone();
1060 let execution_count_clone = execution_count.clone();
1061 let rx_clone = Arc::clone(&rx);
1062 let peer_clone = peer.clone();
1063 let first_request = tokio::spawn(async move {
1064 manager_clone1
1065 .deduplicated_request(key_clone1, peer_clone, |_| {
1066 let count = execution_count_clone.clone();
1067 let rx = Arc::clone(&rx_clone);
1068 async move {
1069 count.fetch_add(1, Ordering::SeqCst);
1070 if let Some(receiver) = rx.lock().await.take() {
1071 receiver.await.unwrap();
1072 }
1073 Ok(vec![])
1074 }
1075 })
1076 .await
1077 });
1078
1079 let mut handles = vec![];
1081 for _ in 0..5 {
1082 let manager_clone = Arc::clone(&manager);
1083 let key_clone = key.clone();
1084 let execution_count_clone = execution_count.clone();
1085 let peer_clone = peer.clone();
1086 let handle = tokio::spawn(async move {
1087 manager_clone
1088 .deduplicated_request(key_clone, peer_clone, |_| {
1089 let count = execution_count_clone.clone();
1090 async move {
1091 count.fetch_add(1, Ordering::SeqCst);
1092 Ok(vec![])
1093 }
1094 })
1095 .await
1096 });
1097 handles.push(handle);
1098 }
1099
1100 tx.send(()).unwrap();
1102
1103 let result: Result<Vec<ConfirmedBlockCertificate>, NodeError> =
1105 first_request.await.unwrap();
1106 assert!(result.is_ok());
1107
1108 for handle in handles {
1110 assert_eq!(handle.await.unwrap(), result);
1111 }
1112
1113 assert_eq!(execution_count.load(Ordering::SeqCst), 1);
1115 }
1116
1117 #[tokio::test]
1118 async fn test_timeout_triggers_new_request() {
1119 let manager = create_test_manager(Duration::from_millis(50), Duration::from_secs(60));
1121
1122 let key = test_key();
1123 let peer = dummy_peer();
1124
1125 let execution_count = Arc::new(AtomicUsize::new(0));
1127 let started = Arc::new(tokio::sync::Notify::new());
1129
1130 let (tx, rx) = oneshot::channel();
1132 let rx = Arc::new(tokio::sync::Mutex::new(Some(rx)));
1133
1134 let manager_clone = Arc::clone(&manager);
1136 let key_clone = key.clone();
1137 let execution_count_clone = execution_count.clone();
1138 let started_clone = started.clone();
1139 let rx_clone = Arc::clone(&rx);
1140 let peer_clone = peer.clone();
1141 let first_request = tokio::spawn(async move {
1142 manager_clone
1143 .deduplicated_request(key_clone, peer_clone, |_| {
1144 let count = execution_count_clone.clone();
1145 let started = started_clone.clone();
1146 let rx = Arc::clone(&rx_clone);
1147 async move {
1148 count.fetch_add(1, Ordering::SeqCst);
1149 started.notify_one();
1150 if let Some(receiver) = rx.lock().await.take() {
1151 receiver.await.unwrap();
1152 }
1153 Ok(vec![])
1154 }
1155 })
1156 .await
1157 });
1158
1159 started.notified().await;
1162 manager
1163 .clock
1164 .add(TimeDelta::from_millis(MAX_REQUEST_TTL_MS + 1));
1165
1166 let execution_count_clone2 = execution_count.clone();
1168 let second_request = tokio::spawn(async move {
1169 manager
1170 .deduplicated_request(key, peer, |_| {
1171 let count = execution_count_clone2.clone();
1172 async move {
1173 count.fetch_add(1, Ordering::SeqCst);
1174 Ok(vec![])
1175 }
1176 })
1177 .await
1178 });
1179
1180 let result2: Result<Vec<ConfirmedBlockCertificate>, NodeError> =
1182 second_request.await.unwrap();
1183 assert!(result2.is_ok());
1184
1185 tx.send(()).unwrap();
1187 let result1: Result<Vec<ConfirmedBlockCertificate>, NodeError> =
1188 first_request.await.unwrap();
1189 assert!(result1.is_ok());
1190
1191 assert_eq!(execution_count.load(Ordering::SeqCst), 2);
1193 }
1194
1195 #[tokio::test]
1196 async fn test_alternative_peers_registered_and_cleared() {
1197 use linera_base::identifiers::BlobType;
1198
1199 use crate::test_utils::{MemoryStorageBuilder, TestBuilder};
1200
1201 let mut builder = TestBuilder::new(
1203 MemoryStorageBuilder::default(),
1204 3,
1205 0,
1206 InMemorySigner::new(None),
1207 )
1208 .await
1209 .unwrap();
1210 let nodes: Vec<_> = (0..3)
1211 .map(|i| {
1212 let node = builder.node(i);
1213 let public_key = node.name();
1214 RemoteNode { public_key, node }
1215 })
1216 .collect();
1217
1218 let manager = create_test_manager(Duration::from_secs(60), Duration::from_secs(60));
1219 let key = RequestKey::Blob(BlobId::new(
1220 CryptoHash::test_hash("test_blob"),
1221 BlobType::Data,
1222 ));
1223 let now = manager.clock.current_time();
1224
1225 let guard = manager.in_flight_tracker.insert_new(key.clone(), now);
1227 manager
1228 .in_flight_tracker
1229 .add_alternative_peer(&key, nodes[1].clone())
1230 .await;
1231 manager
1232 .in_flight_tracker
1233 .add_alternative_peer(&key, nodes[2].clone())
1234 .await;
1235 assert_eq!(
1236 manager
1237 .get_alternative_peers(&key)
1238 .await
1239 .map(|peers| peers.len()),
1240 Some(2),
1241 );
1242
1243 guard.complete_and_broadcast(Arc::new(Ok(RequestResult::Blob(None))));
1245 assert!(
1246 manager.get_alternative_peers(&key).await.is_none(),
1247 "Expected the in-flight entry to be removed after completion",
1248 );
1249 }
1250
1251 #[tokio::test]
1256 async fn test_owner_drop_wakes_subscribers() {
1257 use linera_base::identifiers::BlobType;
1258
1259 let manager = create_test_manager(Duration::from_secs(60), Duration::from_secs(60));
1260 let key = RequestKey::Blob(BlobId::new(
1261 CryptoHash::test_hash("test_blob"),
1262 BlobType::Data,
1263 ));
1264 let now = manager.clock.current_time();
1265
1266 let guard = manager.in_flight_tracker.insert_new(key.clone(), now);
1268 let Some(InFlightMatch::Exact(Subscribed(mut receiver))) =
1269 manager.in_flight_tracker.try_subscribe(&key, now)
1270 else {
1271 panic!("expected to subscribe to the in-flight request");
1272 };
1273
1274 drop(guard);
1276
1277 assert!(matches!(
1280 receiver.recv().await,
1281 Err(tokio::sync::broadcast::error::RecvError::Closed),
1282 ));
1283 assert!(manager.in_flight_tracker.try_subscribe(&key, now).is_none());
1284 }
1285
1286 #[tokio::test]
1290 async fn test_new_owner_adopts_existing_waiters() {
1291 use linera_base::identifiers::BlobType;
1292
1293 let manager = create_test_manager(Duration::from_secs(60), Duration::from_secs(60));
1294 let key = RequestKey::Blob(BlobId::new(
1295 CryptoHash::test_hash("test_blob"),
1296 BlobType::Data,
1297 ));
1298 let now = manager.clock.current_time();
1299
1300 let first_owner = manager.in_flight_tracker.insert_new(key.clone(), now);
1301 let Some(InFlightMatch::Exact(Subscribed(mut receiver))) =
1302 manager.in_flight_tracker.try_subscribe(&key, now)
1303 else {
1304 panic!("expected to subscribe to the in-flight request");
1305 };
1306
1307 let second_owner = manager.in_flight_tracker.insert_new(key.clone(), now);
1309
1310 assert_eq!(
1312 first_owner.complete_and_broadcast(Arc::new(Ok(RequestResult::Blob(None)))),
1313 0
1314 );
1315
1316 assert_eq!(
1318 second_owner.complete_and_broadcast(Arc::new(Ok(RequestResult::Blob(None)))),
1319 1
1320 );
1321 assert!(receiver.recv().await.is_ok());
1322 }
1323
1324 #[tokio::test]
1325 async fn test_staggered_parallel_retry_on_failure() {
1326 use crate::test_utils::{MemoryStorageBuilder, TestBuilder};
1327
1328 let mut builder = TestBuilder::new(
1330 MemoryStorageBuilder::default(),
1331 4,
1332 0,
1333 InMemorySigner::new(None),
1334 )
1335 .await
1336 .unwrap();
1337
1338 let nodes: Vec<_> = (0..4)
1340 .map(|i| {
1341 let node = builder.node(i);
1342 let public_key = node.name();
1343 RemoteNode { public_key, node }
1344 })
1345 .collect();
1346
1347 let staggered_delay = Duration::from_millis(100);
1348
1349 let node0_key = nodes[0].public_key;
1351 let node2_key = nodes[2].public_key;
1352
1353 let clock = TestClock::new();
1357 clock.set_sleep_callback(|_| true);
1358
1359 let manager: Arc<RequestsScheduler<TestEnvironment>> =
1361 Arc::new(RequestsScheduler::with_config(
1362 nodes.clone(),
1363 ScoringWeights::default(),
1364 0.1,
1365 1000.0,
1366 Duration::from_secs(60),
1367 100,
1368 Duration::from_millis(MAX_REQUEST_TTL_MS),
1369 staggered_delay,
1370 clock,
1371 ));
1372
1373 let key = test_key();
1374
1375 let call_order = Arc::new(tokio::sync::Mutex::new(Vec::new()));
1377 let call_order_clone = Arc::clone(&call_order);
1378
1379 let operation = |peer: RemoteNode<<TestEnvironment as Environment>::ValidatorNode>| {
1382 let order = Arc::clone(&call_order_clone);
1383 async move {
1384 order.lock().await.push(peer.public_key);
1385 if peer.public_key == node2_key {
1386 Ok(vec![])
1387 } else {
1388 Err(NodeError::UnexpectedMessage)
1389 }
1390 }
1391 };
1392
1393 let _guard = manager
1396 .in_flight_tracker
1397 .insert_new(key.clone(), manager.clock.current_time());
1398 for node in nodes.iter().skip(1).rev() {
1400 manager
1401 .in_flight_tracker
1402 .add_alternative_peer(&key, node.clone())
1403 .await;
1404 }
1405
1406 let result: Result<Vec<ConfirmedBlockCertificate>, NodeError> = manager
1408 .try_staggered_parallel(&key, nodes[0].clone(), &operation, staggered_delay)
1409 .await;
1410
1411 assert!(
1413 result.is_ok(),
1414 "Expected request to succeed with alternative peer"
1415 );
1416
1417 let order = call_order.lock().await;
1418 assert_eq!(
1419 order.first(),
1420 Some(&node0_key),
1421 "First peer tried should be node 0"
1422 );
1423 assert!(
1424 order.contains(&node2_key),
1425 "Retry should have reached the working peer (node 2)"
1426 );
1427 }
1428}