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::{HotShotInitializer, 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 = initializer.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 initializer: HotShotInitializer<SeqTypes>,
121 anchor_view: Option<ViewNumber>,
122 storage: Option<RequestResponseStorage>,
123 state_catchup: ParallelStateCatchup,
124 persistence: Arc<P>,
125 network: Arc<N>,
126 coordinator_network: F,
127 state_relay_server: Option<Url>,
128 metrics: &dyn Metrics,
129 stake_table_capacity: usize,
130 event_consumer: impl PersistenceEventConsumer + 'static,
131 proposal_fetcher_cfg: ProposalFetcherConfig,
132 bootstrap_epoch_catchup_timeout: Duration,
133 ) -> anyhow::Result<Self>
134 where
135 F: AsyncFnOnce(UpgradeLock<SeqTypes>) -> Result<Cliquenet<SeqTypes>, NetworkError>,
136 {
137 let config = &network_config.config;
138 let pub_key = validator_config.public_key;
139 tracing::info!(%pub_key, "initializing consensus");
140
141 let instance_state = initializer.instance_state().clone();
142
143 metrics
145 .create_gauge("node_index".into(), None)
146 .set(instance_state.node_id as usize);
147
148 instance_state.l1_client.spawn_tasks().await;
150
151 info!(target: "announce", ?initializer, "starting up sequencer context with initializer");
152
153 let stake_table = config.hotshot_stake_table();
154 let stake_table_commit = stake_table.commitment(stake_table_capacity)?;
155 let stake_table_epoch = None;
156 let should_vote =
157 state_signature::should_vote(&stake_table, &validator_config.state_public_key);
158
159 let epoch_height = initializer.epoch_height();
160
161 let initializer_for_coordinator = initializer.clone();
162
163 let event_streamer = Arc::new(RwLock::new(EventsStreamer::<SeqTypes>::new(
164 stake_table.0,
165 0,
166 )));
167 let consensus_metrics = ConsensusMetricsValue::new(metrics);
168
169 let handle = SystemContext::init(
170 validator_config.public_key,
171 validator_config.private_key.clone(),
172 validator_config.state_private_key.clone(),
173 instance_state.node_id,
174 config.clone(),
175 upgrade,
176 membership_coordinator.clone(),
177 network.clone(),
178 initializer,
179 consensus_metrics.clone(),
180 Arc::clone(&persistence),
181 StorageMetricsValue::new(metrics),
182 )
183 .await?
184 .0;
185
186 let mut coordinator_network =
187 coordinator_network(handle.hotshot.upgrade_lock.clone()).await?;
188
189 let max_configured_version = std::cmp::max(upgrade.base, upgrade.target);
196 if max_configured_version >= NEW_PROTOCOL_VERSION {
197 let current_epoch = bootstrap_epoch_window(
198 &membership_coordinator,
199 epoch_height,
200 bootstrap_epoch_catchup_timeout,
201 )
202 .await
203 .context("startup stake-table catchup failed")?;
204 tracing::info!(%current_epoch, "Startup catchup complete");
205
206 if let Err(err) =
210 coordinator_network.apply_epoch(current_epoch, &membership_coordinator)
211 {
212 tracing::warn!(%current_epoch, %err, "coordinator network apply_epoch failed at startup");
213 }
214 }
215
216 let locked_qc = persistence
219 .load_high_qc2()
220 .await
221 .context("loading persisted locked QC")?;
222
223 let coordinator = Coordinator::maker()
224 .membership_coordinator(membership_coordinator.clone())
225 .network(coordinator_network)
226 .initializer(&initializer_for_coordinator)
227 .upgrade_lock(handle.hotshot.upgrade_lock.clone())
228 .public_key(validator_config.public_key)
229 .private_key(validator_config.private_key.clone())
230 .state_private_key(validator_config.state_private_key.clone())
231 .stake_table_capacity(stake_table_capacity)
232 .timeout_duration(Duration::from_secs(10))
233 .storage(Arc::clone(&persistence))
234 .metrics(metrics)
235 .consensus_metrics(consensus_metrics)
236 .maybe_locked_qc(locked_qc)
237 .make();
238
239 let legacy_event_rx = handle.event_stream_known_impl().deactivate();
240 let hotshot_handle = Arc::new(RwLock::new(handle));
241
242 let consensus_handle = {
243 let handle = ConsensusHandle::new(
244 hotshot_handle.clone(),
245 coordinator,
246 epoch_height.into(),
247 legacy_event_rx,
248 EXTERNAL_EVENT_CHANNEL_SIZE,
249 metrics,
250 )
251 .await;
252 Arc::new(handle)
253 };
254
255 let mut state_signer = StateSigner::new(
256 validator_config.state_private_key.clone(),
257 validator_config.state_public_key.clone(),
258 stake_table_commit,
259 stake_table_epoch,
260 stake_table_capacity,
261 should_vote,
262 );
263 if let Some(url) = state_relay_server {
264 state_signer = state_signer.with_relay_server(url);
265 }
266
267 let (outbound_message_sender, outbound_message_receiver) = channel(20);
269 let (request_response_sender, request_response_receiver) = channel(20);
270
271 let request_response_config = RequestResponseConfig {
273 incoming_request_ttl: Duration::from_secs(40),
274 incoming_request_timeout: Duration::from_secs(5),
275 incoming_response_timeout: Duration::from_secs(5),
276 request_batch_size: 5,
277 request_batch_interval: Duration::from_secs(2),
278 max_incoming_requests: 10,
279 max_incoming_requests_per_key: 1,
280 max_incoming_responses: 200,
281 };
282
283 let request_response_protocol = RequestResponseProtocol::new(
285 request_response_config,
286 RequestResponseSender::new(outbound_message_sender),
287 request_response_receiver,
288 RecipientSource {
289 memberships: membership_coordinator,
290 consensus_handle: consensus_handle.clone(),
291 public_key: validator_config.public_key,
292 },
293 DataSource {
294 node_state: instance_state.clone(),
295 storage,
296 persistence: persistence.clone(),
297 consensus_handle: consensus_handle.clone(),
298 phantom: PhantomData,
299 },
300 validator_config.public_key,
301 validator_config.private_key.clone(),
302 );
303
304 state_catchup.add_provider(Arc::new(request_response_protocol.clone()));
308
309 let mut tasks = TaskList::default();
311 let external_event_handler = ExternalEventHandler::new(
312 &mut tasks,
313 request_response_sender,
314 outbound_message_receiver,
315 consensus_handle.clone(),
316 network,
317 pub_key,
318 )
319 .await
320 .with_context(|| "Failed to create external event handler")?;
321
322 Ok(Self::new(
323 consensus_handle,
324 persistence,
325 state_signer,
326 external_event_handler,
327 request_response_protocol,
328 event_streamer,
329 instance_state,
330 network_config,
331 validator_config,
332 event_consumer,
333 anchor_view,
334 proposal_fetcher_cfg,
335 metrics,
336 )
337 .with_task_list(tasks))
338 }
339
340 #[allow(clippy::too_many_arguments)]
342 fn new(
343 consensus_handle: Arc<ConsensusHandle<SeqTypes, ConsensusNode<N, P>>>,
344 persistence: Arc<P>,
345 state_signer: StateSigner<SequencerApiVersion>,
346 external_event_handler: ExternalEventHandler,
347 request_response_protocol: RequestResponseProtocol<ConsensusNode<N, P>, N, P>,
348 event_streamer: Arc<RwLock<EventsStreamer<SeqTypes>>>,
349 node_state: NodeState,
350 network_config: NetworkConfig<SeqTypes>,
351 validator_config: ValidatorConfig<SeqTypes>,
352 event_consumer: impl PersistenceEventConsumer + 'static,
353 anchor_view: Option<ViewNumber>,
354 proposal_fetcher_cfg: ProposalFetcherConfig,
355 metrics: &dyn Metrics,
356 ) -> Self {
357 let events = consensus_handle.event_stream();
358
359 let node_id = node_state.node_id;
360 let mut ctx = Self {
361 consensus_handle,
362 state_signer: Arc::new(RwLock::new(state_signer)),
363 request_response_protocol,
364 tasks: Default::default(),
365 detached: false,
366 wait_for_orchestrator: None,
367 events_streamer: event_streamer.clone(),
368 node_state,
369 network_config,
370 validator_config,
371 };
372
373 proposal_fetcher_cfg.spawn(
375 &mut ctx.tasks,
376 ctx.consensus_handle.clone(),
377 persistence.clone(),
378 metrics,
379 );
380
381 let event_consumer = Arc::new(event_consumer);
383
384 let (decide_tx, decide_rx) = watch::channel::<DecideSignal>(None);
387
388 ctx.spawn(
390 "decide processor",
391 process_decided_events_task(
392 persistence.clone(),
393 event_consumer.clone(),
394 decide_rx,
395 anchor_view,
396 DecideProcessorMetrics::new(metrics),
397 ),
398 );
399
400 ctx.spawn(
402 "event handler",
403 handle_events(
404 ctx.consensus_handle.clone(),
405 node_id,
406 events,
407 persistence,
408 ctx.state_signer.clone(),
409 external_event_handler,
410 Some(event_streamer.clone()),
411 event_consumer,
412 decide_tx,
413 ),
414 );
415
416 ctx
417 }
418
419 pub fn wait_for_orchestrator(mut self, client: OrchestratorClient) -> Self {
421 self.wait_for_orchestrator = Some(Arc::new(client));
422 self
423 }
424
425 pub(crate) fn with_task_list(mut self, tasks: TaskList) -> Self {
427 self.tasks.extend(tasks);
428 self
429 }
430
431 pub fn state_signer(&self) -> Arc<RwLock<StateSigner<SequencerApiVersion>>> {
433 self.state_signer.clone()
434 }
435
436 pub fn event_stream(&self) -> BoxStream<'static, CoordinatorEvent<SeqTypes>> {
438 self.consensus_handle.event_stream()
439 }
440
441 pub async fn submit_transaction(&self, tx: Transaction) -> anyhow::Result<()> {
442 self.consensus_handle.submit_transaction(tx).await
443 }
444
445 pub fn event_streamer(&self) -> Arc<RwLock<EventsStreamer<SeqTypes>>> {
447 self.events_streamer.clone()
448 }
449
450 pub fn consensus_handle(&self) -> Arc<ConsensusHandle<SeqTypes, ConsensusNode<N, P>>> {
452 self.consensus_handle.clone()
453 }
454
455 pub fn validator_config(&self) -> &ValidatorConfig<SeqTypes> {
456 &self.validator_config
457 }
458
459 pub async fn upgrade_lock(&self) -> UpgradeLock<SeqTypes> {
460 self.consensus_handle.upgrade_lock().await
461 }
462
463 pub async fn shutdown_consensus(&self) {
464 self.consensus_handle.shut_down().await
465 }
466
467 pub async fn decided_leaf(&self) -> Leaf2<SeqTypes> {
468 self.consensus_handle.decided_leaf().await
469 }
470
471 pub async fn state(&self, view: ViewNumber) -> Option<Arc<ValidatedState>> {
472 self.consensus_handle.state(view).await
473 }
474
475 pub async fn decided_state(&self) -> Option<Arc<ValidatedState>> {
476 self.consensus_handle.decided_state().await
477 }
478
479 pub fn node_id(&self) -> u64 {
480 self.node_state.node_id
481 }
482
483 pub fn node_state(&self) -> NodeState {
484 self.node_state.clone()
485 }
486
487 pub async fn start_consensus(&self) {
489 if let Some(orchestrator_client) = &self.wait_for_orchestrator {
490 tracing::warn!("waiting for orchestrated start");
491 let peer_config = PeerConfig::to_bytes(&self.validator_config.public_config()).clone();
492 orchestrator_client
493 .wait_for_all_nodes_ready(peer_config)
494 .await;
495 } else {
496 tracing::info!("no orchestrator configured");
500 }
501 tracing::warn!("starting consensus");
502 self.consensus_handle.start_consensus().await;
503 }
504
505 pub fn spawn(&mut self, name: impl Display, task: impl Future<Output: Debug> + Send + 'static) {
510 self.tasks.spawn(name, task);
511 }
512
513 pub fn spawn_short_lived(
521 &mut self,
522 name: impl Display,
523 task: impl Future<Output: Debug> + Send + 'static,
524 ) {
525 self.tasks.spawn_short_lived(name, task);
526 }
527
528 pub async fn shut_down(&mut self) {
530 tracing::info!("shutting down SequencerContext");
531 self.consensus_handle.shut_down().await;
532 self.tasks.shut_down();
533 self.node_state.l1_client.shut_down_tasks().await;
534
535 self.detached = true;
538 }
539
540 pub async fn join(&mut self) {
545 self.tasks.join().await;
546 }
547
548 pub fn detach(&mut self) {
550 self.detached = true;
552 }
553
554 pub fn network_config(&self) -> NetworkConfig<SeqTypes> {
556 self.network_config.clone()
557 }
558}
559
560impl<N: ConnectedNetwork<PubKey>, P: SequencerPersistence> Drop for SequencerContext<N, P> {
561 fn drop(&mut self) {
562 if !self.detached {
563 let consensus_handle = self.consensus_handle.clone();
565 let tasks_clone = self.tasks.clone();
566 let node_state_clone = self.node_state.clone();
567
568 spawn(async move {
569 tracing::info!("shutting down SequencerContext");
570 consensus_handle.shut_down().await;
571 tasks_clone.shut_down();
572 node_state_clone.l1_client.shut_down_tasks().await;
573 });
574
575 self.detached = true;
577 }
578 }
579}
580
581type DecideSignal = Option<(ViewNumber, Option<Arc<CertificatePair<SeqTypes>>>)>;
584
585struct DecideProcessorMetrics {
588 last_decided: Arc<dyn Gauge>,
589 last_processed: Arc<dyn Gauge>,
590 backlog: Arc<dyn Gauge>,
591 duration: Arc<dyn Histogram>,
592 failures: Arc<dyn Counter>,
593}
594
595impl DecideProcessorMetrics {
596 fn new(metrics: &(impl Metrics + ?Sized)) -> Self {
597 let metrics = metrics.subgroup("decide_processor".into());
598 Self {
599 last_decided: metrics
600 .create_gauge("last_decided".into(), Some("view".into()))
601 .into(),
602 last_processed: metrics
603 .create_gauge("last_processed".into(), Some("view".into()))
604 .into(),
605 backlog: metrics
606 .create_gauge("backlog".into(), Some("view".into()))
607 .into(),
608 duration: metrics
609 .create_histogram("process_duration".into(), Some("seconds".into()))
610 .into(),
611 failures: metrics.create_counter("failures".into(), None).into(),
612 }
613 }
614}
615
616const LEGACY_SHUTDOWN_DECIDE_COUNT: u64 = 100;
622
623#[tracing::instrument(skip_all, fields(node_id))]
624#[allow(clippy::too_many_arguments)]
625async fn handle_events<N, P, C>(
626 consensus_handle: Arc<ConsensusHandle<SeqTypes, ConsensusNode<N, P>>>,
627 node_id: u64,
628 mut events: impl Stream<Item = CoordinatorEvent<SeqTypes>> + Unpin,
629 persistence: Arc<P>,
630 state_signer: Arc<RwLock<StateSigner<SequencerApiVersion>>>,
631 external_event_handler: ExternalEventHandler,
632 events_streamer: Option<Arc<RwLock<EventsStreamer<SeqTypes>>>>,
633 event_consumer: Arc<C>,
634 decide_tx: watch::Sender<DecideSignal>,
635) where
636 N: ConnectedNetwork<PubKey>,
637 P: SequencerPersistence,
638 C: PersistenceEventConsumer + 'static,
639{
640 let mut new_protocol_decides: u64 = 0;
641
642 while let Some(event) = events.next().await {
643 tracing::debug!(node_id, ?event, "consensus event");
644
645 match &event {
646 CoordinatorEvent::NewDecide { .. } => {
647 new_protocol_decides += 1;
648 if new_protocol_decides == LEGACY_SHUTDOWN_DECIDE_COUNT {
649 tracing::info!(
650 node_id,
651 "new protocol is live, shutting down legacy consensus and network"
652 );
653 let handle = consensus_handle.clone();
654 spawn(async move { handle.shut_down_legacy().await });
655 }
656 },
657 CoordinatorEvent::LegacyEvent(hotshot_event) => {
658 if let hotshot_types::event::EventType::ExternalMessageReceived { ref data, .. } =
659 hotshot_event.event
660 && let Err(err) = external_event_handler.handle_event(data).await
661 {
662 tracing::warn!(%err, "Failed to handle legacy external message");
663 }
664 consensus_handle.activate().await;
665 },
666 CoordinatorEvent::ExternalMessageReceived { data, .. } => {
667 if let Err(err) = external_event_handler.handle_event(data).await {
668 tracing::warn!("Failed to handle external message: {:?}", err);
669 }
670 },
671 CoordinatorEvent::BlockPayloadReconstructed { .. } => {
672 if let Err(err) = event_consumer.handle_event(&event).await {
676 tracing::warn!("failed to handle reconstructed payload: {err:#}");
677 }
678 },
679 _ => {},
680 }
681
682 let persistence_fut = async {
685 if let Some(signal) = persistence
686 .persist_event(&event, event_consumer.as_ref())
687 .await
688 {
689 decide_tx.send_modify(|current| match current {
692 Some((view, _)) if *view > signal.0 => {},
693 _ => *current = Some(signal),
694 });
695 }
696 };
697
698 let state_signer_fut = async {
699 state_signer
700 .write()
701 .await
702 .handle_event(&event, consensus_handle.as_ref())
703 .await;
704 };
705
706 let events_streamer_fut = async {
707 if let CoordinatorEvent::LegacyEvent(ref hotshot_event) = event
708 && let Some(events_streamer) = events_streamer.as_ref()
709 {
710 events_streamer
711 .write()
712 .await
713 .handle_event(hotshot_event.clone())
714 .await;
715 }
716 };
717
718 tokio::join!(persistence_fut, state_signer_fut, events_streamer_fut);
719 }
720}
721
722const PROCESS_RETRY_INTERVAL: Duration = Duration::from_secs(30);
723
724#[tracing::instrument(skip_all)]
728async fn process_decided_events_task<P, C>(
729 persistence: Arc<P>,
730 consumer: Arc<C>,
731 mut decide_rx: watch::Receiver<DecideSignal>,
732 anchor_view: Option<ViewNumber>,
733 metrics: DecideProcessorMetrics,
734) where
735 P: SequencerPersistence,
736 C: PersistenceEventConsumer + 'static,
737{
738 let mut last_processed = anchor_view.map(|v| v.u64()).unwrap_or(0);
741
742 if let Some(view) = anchor_view {
744 match persistence
745 .process_decided_events(view, None, consumer.as_ref())
746 .await
747 {
748 Ok(processed) => {
749 if let Some(v) = processed {
750 last_processed = last_processed.max(v.u64());
751 }
752 },
753 Err(err) => tracing::warn!(
754 "failed to process decided leaves on startup, chain may not be up to date: {err:#}"
755 ),
756 }
757 }
758
759 let mut latest: DecideSignal = None;
761
762 loop {
763 match tokio::time::timeout(PROCESS_RETRY_INTERVAL, decide_rx.changed()).await {
765 Ok(Ok(())) => latest = decide_rx.borrow_and_update().clone(),
766 Ok(Err(_)) => {
767 tracing::info!("decide signal channel closed, stopping decide processor");
768 return;
769 },
770 Err(_) => {}, }
772
773 let Some((view, deciding_qc)) = latest.clone() else {
774 continue;
775 };
776 let decided = view.u64();
777 metrics.last_decided.set(decided as usize);
778 metrics
779 .backlog
780 .set(decided.saturating_sub(last_processed) as usize);
781
782 let start = Instant::now();
783 let result = persistence
784 .process_decided_events(view, deciding_qc, consumer.as_ref())
785 .await;
786 metrics.duration.add_point(start.elapsed().as_secs_f64());
787
788 match result {
789 Ok(processed) => {
790 if let Some(v) = processed {
793 last_processed = last_processed.max(v.u64());
794 }
795 if let Some((view, _)) = latest.clone()
797 && last_processed >= view.u64()
798 {
799 latest = None;
800 }
801 metrics.last_processed.set(last_processed as usize);
802 metrics
803 .backlog
804 .set(decided.saturating_sub(last_processed) as usize);
805 },
806 Err(err) => {
807 metrics.failures.add(1);
809 tracing::warn!(?view, "deferred decide processing failed: {err:#}");
810 },
811 }
812 }
813}
814
815#[derive(Debug, Default, Clone)]
816#[allow(clippy::type_complexity)]
817pub(crate) struct TaskList(Arc<Mutex<Vec<(String, JoinHandle<()>)>>>);
818
819macro_rules! spawn_with_log_level {
820 ($this:expr, $lvl:expr, $name:expr, $task: expr) => {
821 let name = $name.to_string();
822 let task = {
823 let name = name.clone();
824 let span = tracing::span!($lvl, "background task", name);
825 spawn(
826 async move {
827 tracing::event!($lvl, "spawning background task");
828 let res = $task.await;
829 tracing::event!($lvl, ?res, "background task exited");
830 }
831 .instrument(span),
832 )
833 };
834 $this.0.lock().push((name, task));
835 };
836}
837
838impl TaskList {
839 pub fn spawn(&mut self, name: impl Display, task: impl Future<Output: Debug> + Send + 'static) {
844 spawn_with_log_level!(self, Level::INFO, name, task);
845 }
846
847 pub fn spawn_short_lived(
855 &mut self,
856 name: impl Display,
857 task: impl Future<Output: Debug> + Send + 'static,
858 ) {
859 spawn_with_log_level!(self, Level::DEBUG, name, task);
860 }
861
862 pub fn shut_down(&self) {
864 let tasks: Vec<(String, JoinHandle<()>)> = self.0.lock().drain(..).collect();
865 for (name, task) in tasks.into_iter().rev() {
866 tracing::info!(name, "cancelling background task");
867 task.abort();
868 }
869 }
870
871 pub async fn join(&mut self) {
873 let tasks: Vec<(String, JoinHandle<()>)> = self.0.lock().drain(..).collect();
874 join_all(tasks.into_iter().map(|(_, task)| task)).await;
875 }
876
877 pub fn extend(&mut self, tasks: TaskList) {
878 self.0.lock().extend(
879 tasks
880 .0
881 .lock()
882 .drain(..)
883 .collect::<Vec<(String, JoinHandle<()>)>>(),
884 );
885 }
886}
887
888impl Drop for TaskList {
889 fn drop(&mut self) {
890 self.shut_down()
891 }
892}