1use std::{
2 fmt::{Debug, Display},
3 future::Future,
4 marker::PhantomData,
5 sync::Arc,
6 time::{Duration, Instant},
7};
8
9use anyhow::Context;
10use async_lock::RwLock;
11use derivative::Derivative;
12use espresso_types::{
13 NodeState, PubKey, Transaction, ValidatedState,
14 v0::traits::{EventConsumer as PersistenceEventConsumer, SequencerPersistence},
15};
16use futures::{
17 future::join_all,
18 stream::{BoxStream, Stream, StreamExt},
19};
20use hotshot::SystemContext;
21use hotshot_events_service::events_source::{EventConsumer, EventsStreamer};
22use hotshot_new_protocol::{
23 coordinator::Coordinator,
24 network::{Cliquenet, NetworkError},
25};
26use hotshot_orchestrator::client::OrchestratorClient;
27use hotshot_types::{
28 PeerConfig, ValidatorConfig,
29 consensus::ConsensusMetricsValue,
30 constants::EXTERNAL_EVENT_CHANNEL_SIZE,
31 data::{Leaf2, ViewNumber},
32 epoch_membership::EpochMembershipCoordinator,
33 message::UpgradeLock,
34 network::NetworkConfig,
35 new_protocol::CoordinatorEvent,
36 simple_certificate::CertificatePair,
37 storage_metrics::StorageMetricsValue,
38 traits::{
39 metrics::{Counter, Gauge, Histogram, Metrics},
40 network::ConnectedNetwork,
41 },
42};
43use parking_lot::Mutex;
44use request_response::RequestResponseConfig;
45use tokio::{
46 spawn,
47 sync::{mpsc::channel, watch},
48 task::JoinHandle,
49};
50use tracing::{Instrument, Level, info};
51use url::Url;
52use versions::NEW_PROTOCOL_VERSION;
53
54use crate::{
55 Node, SeqTypes, SequencerApiVersion,
56 catchup::ParallelStateCatchup,
57 consensus_handle::ConsensusHandle,
58 external_event_handler::ExternalEventHandler,
59 proposal_fetcher::ProposalFetcherConfig,
60 request_response::{
61 RequestResponseProtocol,
62 data_source::{DataSource, Storage as RequestResponseStorage},
63 network::Sender as RequestResponseSender,
64 recipient_source::RecipientSource,
65 },
66 startup_catchup::bootstrap_epoch_window,
67 state_signature::{self, StateSigner},
68};
69pub(crate) type ConsensusNode<N, P> = Node<N, P>;
70pub type Consensus<N, P> = hotshot::types::SystemContextHandle<SeqTypes, ConsensusNode<N, P>>;
71
72#[derive(Derivative, Clone)]
74#[derivative(Debug(bound = ""))]
75pub struct SequencerContext<N: ConnectedNetwork<PubKey>, P: SequencerPersistence> {
76 #[derivative(Debug = "ignore")]
78 consensus_handle: Arc<ConsensusHandle<SeqTypes, ConsensusNode<N, P>>>,
79
80 #[derivative(Debug = "ignore")]
82 #[allow(dead_code)]
83 pub request_response_protocol: RequestResponseProtocol<ConsensusNode<N, P>, N, P>,
84
85 state_signer: Arc<RwLock<StateSigner<SequencerApiVersion>>>,
87
88 #[derivative(Debug = "ignore")]
90 wait_for_orchestrator: Option<Arc<OrchestratorClient>>,
91
92 tasks: TaskList,
94
95 events_streamer: Arc<RwLock<EventsStreamer<SeqTypes>>>,
97
98 detached: bool,
99
100 node_state: NodeState,
101
102 network_config: NetworkConfig<SeqTypes>,
103
104 #[derivative(Debug = "ignore")]
105 validator_config: ValidatorConfig<SeqTypes>,
106}
107
108impl<N, P> SequencerContext<N, P>
109where
110 N: ConnectedNetwork<PubKey>,
111 P: SequencerPersistence,
112{
113 #[tracing::instrument(skip_all, fields(node_id = instance_state.node_id))]
114 #[allow(clippy::too_many_arguments)]
115 pub async fn init<F>(
116 network_config: NetworkConfig<SeqTypes>,
117 upgrade: versions::Upgrade,
118 validator_config: ValidatorConfig<SeqTypes>,
119 membership_coordinator: EpochMembershipCoordinator<SeqTypes>,
120 instance_state: NodeState,
121 storage: Option<RequestResponseStorage>,
122 state_catchup: ParallelStateCatchup,
123 persistence: Arc<P>,
124 network: Arc<N>,
125 coordinator_network: F,
126 state_relay_server: Option<Url>,
127 metrics: &dyn Metrics,
128 stake_table_capacity: usize,
129 event_consumer: impl PersistenceEventConsumer + 'static,
130 proposal_fetcher_cfg: ProposalFetcherConfig,
131 bootstrap_epoch_catchup_timeout: Duration,
132 ) -> anyhow::Result<Self>
133 where
134 F: AsyncFnOnce(UpgradeLock<SeqTypes>) -> Result<Cliquenet<SeqTypes>, NetworkError>,
135 {
136 let config = &network_config.config;
137 let pub_key = validator_config.public_key;
138 tracing::info!(%pub_key, "initializing consensus");
139
140 metrics
142 .create_gauge("node_index".into(), None)
143 .set(instance_state.node_id as usize);
144
145 instance_state.l1_client.spawn_tasks().await;
147
148 let (initializer, anchor_view) = persistence
150 .load_consensus_state(instance_state.clone(), upgrade)
151 .await?;
152
153 info!(target: "announce", ?initializer, "starting up sequencer context with initializer");
154
155 let stake_table = config.hotshot_stake_table();
156 let stake_table_commit = stake_table.commitment(stake_table_capacity)?;
157 let stake_table_epoch = None;
158 let should_vote =
159 state_signature::should_vote(&stake_table, &validator_config.state_public_key);
160
161 let epoch_height = initializer.epoch_height;
162
163 let initializer_for_coordinator = initializer.clone();
164
165 let event_streamer = Arc::new(RwLock::new(EventsStreamer::<SeqTypes>::new(
166 stake_table.0,
167 0,
168 )));
169 let consensus_metrics = ConsensusMetricsValue::new(metrics);
170
171 let handle = SystemContext::init(
172 validator_config.public_key,
173 validator_config.private_key.clone(),
174 validator_config.state_private_key.clone(),
175 instance_state.node_id,
176 config.clone(),
177 upgrade,
178 membership_coordinator.clone(),
179 network.clone(),
180 initializer,
181 consensus_metrics.clone(),
182 Arc::clone(&persistence),
183 StorageMetricsValue::new(metrics),
184 )
185 .await?
186 .0;
187
188 let mut coordinator_network =
189 coordinator_network(handle.hotshot.upgrade_lock.clone()).await?;
190
191 let max_configured_version = std::cmp::max(upgrade.base, upgrade.target);
198 if max_configured_version >= NEW_PROTOCOL_VERSION {
199 let current_epoch = bootstrap_epoch_window(
200 &membership_coordinator,
201 epoch_height,
202 bootstrap_epoch_catchup_timeout,
203 )
204 .await
205 .context("startup stake-table catchup failed")?;
206 tracing::info!(%current_epoch, "Startup catchup complete");
207
208 if let Err(err) =
212 coordinator_network.apply_epoch(current_epoch, &membership_coordinator)
213 {
214 tracing::warn!(%current_epoch, %err, "coordinator network apply_epoch failed at startup");
215 }
216 }
217
218 let locked_qc = persistence
221 .load_high_qc2()
222 .await
223 .context("loading persisted locked QC")?;
224
225 let coordinator = Coordinator::maker()
226 .membership_coordinator(membership_coordinator.clone())
227 .network(coordinator_network)
228 .initializer(&initializer_for_coordinator)
229 .upgrade_lock(handle.hotshot.upgrade_lock.clone())
230 .public_key(validator_config.public_key)
231 .private_key(validator_config.private_key.clone())
232 .state_private_key(validator_config.state_private_key.clone())
233 .stake_table_capacity(stake_table_capacity)
234 .timeout_duration(Duration::from_secs(10))
235 .storage(Arc::clone(&persistence))
236 .metrics(metrics)
237 .consensus_metrics(consensus_metrics)
238 .maybe_locked_qc(locked_qc)
239 .make();
240
241 let legacy_event_rx = handle.event_stream_known_impl().deactivate();
242 let hotshot_handle = Arc::new(RwLock::new(handle));
243
244 let consensus_handle = {
245 let handle = ConsensusHandle::new(
246 hotshot_handle.clone(),
247 coordinator,
248 epoch_height.into(),
249 legacy_event_rx,
250 EXTERNAL_EVENT_CHANNEL_SIZE,
251 metrics,
252 )
253 .await;
254 Arc::new(handle)
255 };
256
257 let mut state_signer = StateSigner::new(
258 validator_config.state_private_key.clone(),
259 validator_config.state_public_key.clone(),
260 stake_table_commit,
261 stake_table_epoch,
262 stake_table_capacity,
263 should_vote,
264 );
265 if let Some(url) = state_relay_server {
266 state_signer = state_signer.with_relay_server(url);
267 }
268
269 let (outbound_message_sender, outbound_message_receiver) = channel(20);
271 let (request_response_sender, request_response_receiver) = channel(20);
272
273 let request_response_config = RequestResponseConfig {
275 incoming_request_ttl: Duration::from_secs(40),
276 incoming_request_timeout: Duration::from_secs(5),
277 incoming_response_timeout: Duration::from_secs(5),
278 request_batch_size: 5,
279 request_batch_interval: Duration::from_secs(2),
280 max_incoming_requests: 10,
281 max_incoming_requests_per_key: 1,
282 max_incoming_responses: 200,
283 };
284
285 let request_response_protocol = RequestResponseProtocol::new(
287 request_response_config,
288 RequestResponseSender::new(outbound_message_sender),
289 request_response_receiver,
290 RecipientSource {
291 memberships: membership_coordinator,
292 consensus_handle: consensus_handle.clone(),
293 public_key: validator_config.public_key,
294 },
295 DataSource {
296 node_state: instance_state.clone(),
297 storage,
298 persistence: persistence.clone(),
299 consensus_handle: consensus_handle.clone(),
300 phantom: PhantomData,
301 },
302 validator_config.public_key,
303 validator_config.private_key.clone(),
304 );
305
306 state_catchup.add_provider(Arc::new(request_response_protocol.clone()));
310
311 let mut tasks = TaskList::default();
313 let external_event_handler = ExternalEventHandler::new(
314 &mut tasks,
315 request_response_sender,
316 outbound_message_receiver,
317 consensus_handle.clone(),
318 network,
319 pub_key,
320 )
321 .await
322 .with_context(|| "Failed to create external event handler")?;
323
324 Ok(Self::new(
325 consensus_handle,
326 persistence,
327 state_signer,
328 external_event_handler,
329 request_response_protocol,
330 event_streamer,
331 instance_state,
332 network_config,
333 validator_config,
334 event_consumer,
335 anchor_view,
336 proposal_fetcher_cfg,
337 metrics,
338 )
339 .with_task_list(tasks))
340 }
341
342 #[allow(clippy::too_many_arguments)]
344 fn new(
345 consensus_handle: Arc<ConsensusHandle<SeqTypes, ConsensusNode<N, P>>>,
346 persistence: Arc<P>,
347 state_signer: StateSigner<SequencerApiVersion>,
348 external_event_handler: ExternalEventHandler,
349 request_response_protocol: RequestResponseProtocol<ConsensusNode<N, P>, N, P>,
350 event_streamer: Arc<RwLock<EventsStreamer<SeqTypes>>>,
351 node_state: NodeState,
352 network_config: NetworkConfig<SeqTypes>,
353 validator_config: ValidatorConfig<SeqTypes>,
354 event_consumer: impl PersistenceEventConsumer + 'static,
355 anchor_view: Option<ViewNumber>,
356 proposal_fetcher_cfg: ProposalFetcherConfig,
357 metrics: &dyn Metrics,
358 ) -> Self {
359 let events = consensus_handle.event_stream();
360
361 let node_id = node_state.node_id;
362 let mut ctx = Self {
363 consensus_handle,
364 state_signer: Arc::new(RwLock::new(state_signer)),
365 request_response_protocol,
366 tasks: Default::default(),
367 detached: false,
368 wait_for_orchestrator: None,
369 events_streamer: event_streamer.clone(),
370 node_state,
371 network_config,
372 validator_config,
373 };
374
375 proposal_fetcher_cfg.spawn(
377 &mut ctx.tasks,
378 ctx.consensus_handle.clone(),
379 persistence.clone(),
380 metrics,
381 );
382
383 let event_consumer = Arc::new(event_consumer);
385
386 let (decide_tx, decide_rx) = watch::channel::<DecideSignal>(None);
389
390 ctx.spawn(
392 "decide processor",
393 process_decided_events_task(
394 persistence.clone(),
395 event_consumer.clone(),
396 decide_rx,
397 anchor_view,
398 DecideProcessorMetrics::new(metrics),
399 ),
400 );
401
402 ctx.spawn(
404 "event handler",
405 handle_events(
406 ctx.consensus_handle.clone(),
407 node_id,
408 events,
409 persistence,
410 ctx.state_signer.clone(),
411 external_event_handler,
412 Some(event_streamer.clone()),
413 event_consumer,
414 decide_tx,
415 ),
416 );
417
418 ctx
419 }
420
421 pub fn wait_for_orchestrator(mut self, client: OrchestratorClient) -> Self {
423 self.wait_for_orchestrator = Some(Arc::new(client));
424 self
425 }
426
427 pub(crate) fn with_task_list(mut self, tasks: TaskList) -> Self {
429 self.tasks.extend(tasks);
430 self
431 }
432
433 pub fn state_signer(&self) -> Arc<RwLock<StateSigner<SequencerApiVersion>>> {
435 self.state_signer.clone()
436 }
437
438 pub fn event_stream(&self) -> BoxStream<'static, CoordinatorEvent<SeqTypes>> {
440 self.consensus_handle.event_stream()
441 }
442
443 pub async fn submit_transaction(&self, tx: Transaction) -> anyhow::Result<()> {
444 self.consensus_handle.submit_transaction(tx).await
445 }
446
447 pub fn event_streamer(&self) -> Arc<RwLock<EventsStreamer<SeqTypes>>> {
449 self.events_streamer.clone()
450 }
451
452 pub fn consensus_handle(&self) -> Arc<ConsensusHandle<SeqTypes, ConsensusNode<N, P>>> {
454 self.consensus_handle.clone()
455 }
456
457 pub async fn upgrade_lock(&self) -> UpgradeLock<SeqTypes> {
458 self.consensus_handle.upgrade_lock().await
459 }
460
461 pub async fn shutdown_consensus(&self) {
462 self.consensus_handle.shut_down().await
463 }
464
465 pub async fn decided_leaf(&self) -> Leaf2<SeqTypes> {
466 self.consensus_handle.decided_leaf().await
467 }
468
469 pub async fn state(&self, view: ViewNumber) -> Option<Arc<ValidatedState>> {
470 self.consensus_handle.state(view).await
471 }
472
473 pub async fn decided_state(&self) -> Option<Arc<ValidatedState>> {
474 self.consensus_handle.decided_state().await
475 }
476
477 pub fn node_id(&self) -> u64 {
478 self.node_state.node_id
479 }
480
481 pub fn node_state(&self) -> NodeState {
482 self.node_state.clone()
483 }
484
485 pub async fn start_consensus(&self) {
487 if let Some(orchestrator_client) = &self.wait_for_orchestrator {
488 tracing::warn!("waiting for orchestrated start");
489 let peer_config = PeerConfig::to_bytes(&self.validator_config.public_config()).clone();
490 orchestrator_client
491 .wait_for_all_nodes_ready(peer_config)
492 .await;
493 } else {
494 tracing::info!("no orchestrator configured");
498 }
499 tracing::warn!("starting consensus");
500 self.consensus_handle.start_consensus().await;
501 }
502
503 pub fn spawn(&mut self, name: impl Display, task: impl Future<Output: Debug> + Send + 'static) {
508 self.tasks.spawn(name, task);
509 }
510
511 pub fn spawn_short_lived(
519 &mut self,
520 name: impl Display,
521 task: impl Future<Output: Debug> + Send + 'static,
522 ) {
523 self.tasks.spawn_short_lived(name, task);
524 }
525
526 pub async fn shut_down(&mut self) {
528 tracing::info!("shutting down SequencerContext");
529 self.consensus_handle.shut_down().await;
530 self.tasks.shut_down();
531 self.node_state.l1_client.shut_down_tasks().await;
532
533 self.detached = true;
536 }
537
538 pub async fn join(&mut self) {
543 self.tasks.join().await;
544 }
545
546 pub fn detach(&mut self) {
548 self.detached = true;
550 }
551
552 pub fn network_config(&self) -> NetworkConfig<SeqTypes> {
554 self.network_config.clone()
555 }
556}
557
558impl<N: ConnectedNetwork<PubKey>, P: SequencerPersistence> Drop for SequencerContext<N, P> {
559 fn drop(&mut self) {
560 if !self.detached {
561 let consensus_handle = self.consensus_handle.clone();
563 let tasks_clone = self.tasks.clone();
564 let node_state_clone = self.node_state.clone();
565
566 spawn(async move {
567 tracing::info!("shutting down SequencerContext");
568 consensus_handle.shut_down().await;
569 tasks_clone.shut_down();
570 node_state_clone.l1_client.shut_down_tasks().await;
571 });
572
573 self.detached = true;
575 }
576 }
577}
578
579type DecideSignal = Option<(ViewNumber, Option<Arc<CertificatePair<SeqTypes>>>)>;
582
583struct DecideProcessorMetrics {
586 last_decided: Arc<dyn Gauge>,
587 last_processed: Arc<dyn Gauge>,
588 backlog: Arc<dyn Gauge>,
589 duration: Arc<dyn Histogram>,
590 failures: Arc<dyn Counter>,
591}
592
593impl DecideProcessorMetrics {
594 fn new(metrics: &(impl Metrics + ?Sized)) -> Self {
595 let metrics = metrics.subgroup("decide_processor".into());
596 Self {
597 last_decided: metrics
598 .create_gauge("last_decided".into(), Some("view".into()))
599 .into(),
600 last_processed: metrics
601 .create_gauge("last_processed".into(), Some("view".into()))
602 .into(),
603 backlog: metrics
604 .create_gauge("backlog".into(), Some("view".into()))
605 .into(),
606 duration: metrics
607 .create_histogram("process_duration".into(), Some("seconds".into()))
608 .into(),
609 failures: metrics.create_counter("failures".into(), None).into(),
610 }
611 }
612}
613
614const LEGACY_SHUTDOWN_DECIDE_COUNT: u64 = 100;
620
621#[tracing::instrument(skip_all, fields(node_id))]
622#[allow(clippy::too_many_arguments)]
623async fn handle_events<N, P, C>(
624 consensus_handle: Arc<ConsensusHandle<SeqTypes, ConsensusNode<N, P>>>,
625 node_id: u64,
626 mut events: impl Stream<Item = CoordinatorEvent<SeqTypes>> + Unpin,
627 persistence: Arc<P>,
628 state_signer: Arc<RwLock<StateSigner<SequencerApiVersion>>>,
629 external_event_handler: ExternalEventHandler,
630 events_streamer: Option<Arc<RwLock<EventsStreamer<SeqTypes>>>>,
631 event_consumer: Arc<C>,
632 decide_tx: watch::Sender<DecideSignal>,
633) where
634 N: ConnectedNetwork<PubKey>,
635 P: SequencerPersistence,
636 C: PersistenceEventConsumer + 'static,
637{
638 let mut new_protocol_decides: u64 = 0;
639
640 while let Some(event) = events.next().await {
641 tracing::debug!(node_id, ?event, "consensus event");
642
643 match &event {
644 CoordinatorEvent::NewDecide { .. } => {
645 new_protocol_decides += 1;
646 if new_protocol_decides == LEGACY_SHUTDOWN_DECIDE_COUNT {
647 tracing::info!(
648 node_id,
649 "new protocol is live, shutting down legacy consensus and network"
650 );
651 let handle = consensus_handle.clone();
652 spawn(async move { handle.shut_down_legacy().await });
653 }
654 },
655 CoordinatorEvent::LegacyEvent(hotshot_event) => {
656 if let hotshot_types::event::EventType::ExternalMessageReceived { ref data, .. } =
657 hotshot_event.event
658 && let Err(err) = external_event_handler.handle_event(data).await
659 {
660 tracing::warn!(%err, "Failed to handle legacy external message");
661 }
662 consensus_handle.activate().await;
663 },
664 CoordinatorEvent::ExternalMessageReceived { data, .. } => {
665 if let Err(err) = external_event_handler.handle_event(data).await {
666 tracing::warn!("Failed to handle external message: {:?}", err);
667 }
668 },
669 CoordinatorEvent::BlockPayloadReconstructed { .. } => {
670 if let Err(err) = event_consumer.handle_event(&event).await {
674 tracing::warn!("failed to handle reconstructed payload: {err:#}");
675 }
676 },
677 _ => {},
678 }
679
680 let persistence_fut = async {
683 if let Some(signal) = persistence
684 .persist_event(&event, event_consumer.as_ref())
685 .await
686 {
687 decide_tx.send_modify(|current| match current {
690 Some((view, _)) if *view > signal.0 => {},
691 _ => *current = Some(signal),
692 });
693 }
694 };
695
696 let state_signer_fut = async {
697 state_signer
698 .write()
699 .await
700 .handle_event(&event, consensus_handle.as_ref())
701 .await;
702 };
703
704 let events_streamer_fut = async {
705 if let CoordinatorEvent::LegacyEvent(ref hotshot_event) = event
706 && let Some(events_streamer) = events_streamer.as_ref()
707 {
708 events_streamer
709 .write()
710 .await
711 .handle_event(hotshot_event.clone())
712 .await;
713 }
714 };
715
716 tokio::join!(persistence_fut, state_signer_fut, events_streamer_fut);
717 }
718}
719
720const PROCESS_RETRY_INTERVAL: Duration = Duration::from_secs(30);
721
722#[tracing::instrument(skip_all)]
726async fn process_decided_events_task<P, C>(
727 persistence: Arc<P>,
728 consumer: Arc<C>,
729 mut decide_rx: watch::Receiver<DecideSignal>,
730 anchor_view: Option<ViewNumber>,
731 metrics: DecideProcessorMetrics,
732) where
733 P: SequencerPersistence,
734 C: PersistenceEventConsumer + 'static,
735{
736 let mut last_processed = anchor_view.map(|v| v.u64()).unwrap_or(0);
739
740 if let Some(view) = anchor_view {
742 match persistence
743 .process_decided_events(view, None, consumer.as_ref())
744 .await
745 {
746 Ok(processed) => {
747 if let Some(v) = processed {
748 last_processed = last_processed.max(v.u64());
749 }
750 },
751 Err(err) => tracing::warn!(
752 "failed to process decided leaves on startup, chain may not be up to date: {err:#}"
753 ),
754 }
755 }
756
757 let mut latest: DecideSignal = None;
759
760 loop {
761 match tokio::time::timeout(PROCESS_RETRY_INTERVAL, decide_rx.changed()).await {
763 Ok(Ok(())) => latest = decide_rx.borrow_and_update().clone(),
764 Ok(Err(_)) => {
765 tracing::info!("decide signal channel closed, stopping decide processor");
766 return;
767 },
768 Err(_) => {}, }
770
771 let Some((view, deciding_qc)) = latest.clone() else {
772 continue;
773 };
774 let decided = view.u64();
775 metrics.last_decided.set(decided as usize);
776 metrics
777 .backlog
778 .set(decided.saturating_sub(last_processed) as usize);
779
780 let start = Instant::now();
781 let result = persistence
782 .process_decided_events(view, deciding_qc, consumer.as_ref())
783 .await;
784 metrics.duration.add_point(start.elapsed().as_secs_f64());
785
786 match result {
787 Ok(processed) => {
788 if let Some(v) = processed {
791 last_processed = last_processed.max(v.u64());
792 }
793 if let Some((view, _)) = latest.clone()
795 && last_processed >= view.u64()
796 {
797 latest = None;
798 }
799 metrics.last_processed.set(last_processed as usize);
800 metrics
801 .backlog
802 .set(decided.saturating_sub(last_processed) as usize);
803 },
804 Err(err) => {
805 metrics.failures.add(1);
807 tracing::warn!(?view, "deferred decide processing failed: {err:#}");
808 },
809 }
810 }
811}
812
813#[derive(Debug, Default, Clone)]
814#[allow(clippy::type_complexity)]
815pub(crate) struct TaskList(Arc<Mutex<Vec<(String, JoinHandle<()>)>>>);
816
817macro_rules! spawn_with_log_level {
818 ($this:expr, $lvl:expr, $name:expr, $task: expr) => {
819 let name = $name.to_string();
820 let task = {
821 let name = name.clone();
822 let span = tracing::span!($lvl, "background task", name);
823 spawn(
824 async move {
825 tracing::event!($lvl, "spawning background task");
826 let res = $task.await;
827 tracing::event!($lvl, ?res, "background task exited");
828 }
829 .instrument(span),
830 )
831 };
832 $this.0.lock().push((name, task));
833 };
834}
835
836impl TaskList {
837 pub fn spawn(&mut self, name: impl Display, task: impl Future<Output: Debug> + Send + 'static) {
842 spawn_with_log_level!(self, Level::INFO, name, task);
843 }
844
845 pub fn spawn_short_lived(
853 &mut self,
854 name: impl Display,
855 task: impl Future<Output: Debug> + Send + 'static,
856 ) {
857 spawn_with_log_level!(self, Level::DEBUG, name, task);
858 }
859
860 pub fn shut_down(&self) {
862 let tasks: Vec<(String, JoinHandle<()>)> = self.0.lock().drain(..).collect();
863 for (name, task) in tasks.into_iter().rev() {
864 tracing::info!(name, "cancelling background task");
865 task.abort();
866 }
867 }
868
869 pub async fn join(&mut self) {
871 let tasks: Vec<(String, JoinHandle<()>)> = self.0.lock().drain(..).collect();
872 join_all(tasks.into_iter().map(|(_, task)| task)).await;
873 }
874
875 pub fn extend(&mut self, tasks: TaskList) {
876 self.0.lock().extend(
877 tasks
878 .0
879 .lock()
880 .drain(..)
881 .collect::<Vec<(String, JoinHandle<()>)>>(),
882 );
883 }
884}
885
886impl Drop for TaskList {
887 fn drop(&mut self) {
888 self.shut_down()
889 }
890}