Skip to main content

casper_node/
reactor.rs

1#![allow(clippy::boxed_local)] // We use boxed locals to pass on event data unchanged.
2
3//! Reactor core.
4//!
5//! Any long running instance of the node application uses an event-dispatch pattern: Events are
6//! generated and stored on an event queue, then processed one-by-one. This process happens inside
7//! the reactor, which also exclusively holds the state of the application besides pending events:
8//!
9//! 1. The reactor pops a reactor event off the event queue (called a
10//!    [`Scheduler`](type.Scheduler.html)).
11//! 2. The event is dispatched by the reactor via [`Reactor::dispatch_event`]. Since the reactor
12//!    holds mutable state, it can grant any component that processes an event mutable, exclusive
13//!    access to its state.
14//! 3. Once the [(synchronous)](`crate::components::Component::handle_event`) event processing has
15//!    completed, the component returns an [`effect`](crate::effect).
16//! 4. The reactor spawns a task that executes these effects and possibly schedules more events.
17//! 5. go to 1.
18//!
19//! For descriptions of events and instructions on how to create effects, see the
20//! [`effect`](super::effect) module.
21//!
22//! # Reactors
23//!
24//! There is no single reactor, but rather a reactor for each application type, since it defines
25//! which components are used and how they are wired up. The reactor defines the state by being a
26//! `struct` of components, their initialization through [`Reactor::new`] and event dispatching to
27//! components via [`Reactor::dispatch_event`].
28//!
29//! With all these set up, a reactor can be executed using a [`Runner`], either in a step-wise
30//! manner using [`Runner::crank`] or indefinitely using [`Runner::run`].
31
32mod event_queue_metrics;
33pub(crate) mod main_reactor;
34mod queue_kind;
35
36use std::{
37    any,
38    collections::HashMap,
39    env,
40    fmt::{Debug, Display},
41    io::Write,
42    num::NonZeroU64,
43    str::FromStr,
44    sync::{atomic::Ordering, Arc},
45};
46
47use datasize::DataSize;
48use erased_serde::Serialize as ErasedSerialize;
49#[cfg(test)]
50use fake_instant::FakeClock;
51#[cfg(test)]
52use futures::future::BoxFuture;
53use futures::FutureExt;
54use once_cell::sync::Lazy;
55use prometheus::{self, Histogram, HistogramOpts, IntCounter, IntGauge, Registry};
56use quanta::{Clock, IntoNanoseconds};
57use serde::Serialize;
58use signal_hook::consts::signal::{SIGINT, SIGQUIT, SIGTERM};
59use stats_alloc::{Stats, INSTRUMENTED_SYSTEM};
60use tokio::time::{Duration, Instant};
61use tracing::{debug_span, error, info, instrument, trace, warn, Instrument, Span};
62
63#[cfg(test)]
64use crate::components::ComponentState;
65#[cfg(test)]
66use casper_types::testing::TestRng;
67use casper_types::{
68    Block, BlockHeader, Chainspec, ChainspecRawBytes, FinalitySignature, Transaction,
69};
70
71#[cfg(target_os = "linux")]
72use utils::rlimit::{Limit, OpenFiles, ResourceLimit};
73
74#[cfg(test)]
75use crate::testing::{network::NetworkedReactor, ConditionCheckReactor};
76use crate::{
77    components::{
78        block_accumulator,
79        fetcher::{self, FetchItem},
80        network::{blocklist::BlocklistJustification, Identity as NetworkIdentity},
81        transaction_acceptor,
82    },
83    effect::{
84        announcements::{ControlAnnouncement, PeerBehaviorAnnouncement, QueueDumpFormat},
85        incoming::NetResponse,
86        Effect, EffectBuilder, EffectExt, Effects,
87    },
88    failpoints::FailpointActivation,
89    types::{BlockExecutionResultsOrChunk, ExitCode, LegacyDeploy, NodeId, SyncLeap, TrieOrChunk},
90    unregister_metric,
91    utils::{self, SharedFlag, WeightedRoundRobin},
92    NodeRng, TERMINATION_REQUESTED,
93};
94use casper_storage::block_store::types::ApprovalsHashes;
95pub(crate) use queue_kind::QueueKind;
96
97/// Default threshold for when an event is considered slow.  Can be overridden by setting the env
98/// var `CL_EVENT_MAX_MICROSECS=<MICROSECONDS>`.
99const DEFAULT_DISPATCH_EVENT_THRESHOLD: Duration = Duration::from_secs(1);
100const DISPATCH_EVENT_THRESHOLD_ENV_VAR: &str = "CL_EVENT_MAX_MICROSECS";
101#[cfg(test)]
102const POLL_INTERVAL: Duration = Duration::from_millis(10);
103
104static DISPATCH_EVENT_THRESHOLD: Lazy<Duration> = Lazy::new(|| {
105    env::var(DISPATCH_EVENT_THRESHOLD_ENV_VAR)
106        .map(|threshold_str| {
107            let threshold_microsecs = u64::from_str(&threshold_str).unwrap_or_else(|error| {
108                panic!(
109                    "can't parse env var {}={} as a u64: {}",
110                    DISPATCH_EVENT_THRESHOLD_ENV_VAR, threshold_str, error
111                )
112            });
113            Duration::from_micros(threshold_microsecs)
114        })
115        .unwrap_or_else(|_| DEFAULT_DISPATCH_EVENT_THRESHOLD)
116});
117
118#[cfg(target_os = "linux")]
119/// The desired limit for open files.
120const TARGET_OPEN_FILES_LIMIT: Limit = 64_000;
121
122#[cfg(target_os = "linux")]
123/// Adjusts the maximum number of open file handles upwards towards the hard limit.
124fn adjust_open_files_limit() {
125    // Ensure we have reasonable ulimits.
126    match ResourceLimit::<OpenFiles>::get() {
127        Err(err) => {
128            warn!(%err, "could not retrieve open files limit");
129        }
130
131        Ok(current_limit) => {
132            if current_limit.current() < TARGET_OPEN_FILES_LIMIT {
133                let best_possible = if current_limit.max() < TARGET_OPEN_FILES_LIMIT {
134                    warn!(
135                        wanted = TARGET_OPEN_FILES_LIMIT,
136                        hard_limit = current_limit.max(),
137                        "settling for lower open files limit due to hard limit"
138                    );
139                    current_limit.max()
140                } else {
141                    TARGET_OPEN_FILES_LIMIT
142                };
143
144                let new_limit = ResourceLimit::<OpenFiles>::fixed(best_possible);
145                if let Err(err) = new_limit.set() {
146                    warn!(%err, current=current_limit.current(), target=best_possible, "did not succeed in raising open files limit")
147                } else {
148                    tracing::debug!(?new_limit, "successfully increased open files limit");
149                }
150            } else {
151                tracing::debug!(
152                    ?current_limit,
153                    "not changing open files limit, already sufficient"
154                );
155            }
156        }
157    }
158}
159
160#[cfg(not(target_os = "linux"))]
161/// File handle limit adjustment shim.
162fn adjust_open_files_limit() {
163    info!("not on linux, not adjusting open files limit");
164}
165
166/// Event scheduler
167///
168/// The scheduler is a combination of multiple event queues that are polled in a specific order. It
169/// is the central hook for any part of the program that schedules events directly.
170///
171/// Components rarely use this, but use a bound `EventQueueHandle` instead.
172///
173/// Schedule tuples contain an optional ancestor ID and the actual event. The ancestor ID indicates
174/// which potential previous event resulted in the event being created.
175pub(crate) type Scheduler<Ev> = WeightedRoundRobin<(Option<NonZeroU64>, Ev), QueueKind>;
176
177/// Event queue handle
178///
179/// The event queue handle is how almost all parts of the application interact with the reactor
180/// outside of the normal event loop. It gives different parts a chance to schedule messages that
181/// stem from things like external IO.
182#[derive(DataSize, Debug)]
183pub(crate) struct EventQueueHandle<REv>
184where
185    REv: 'static,
186{
187    /// A reference to the scheduler of the event queue.
188    scheduler: &'static Scheduler<REv>,
189    /// Flag indicating whether or not the reactor processing this event queue is shutting down.
190    is_shutting_down: SharedFlag,
191}
192
193// Implement `Clone` and `Copy` manually, as `derive` will make it depend on `R` and `Ev` otherwise.
194impl<REv> Clone for EventQueueHandle<REv> {
195    fn clone(&self) -> Self {
196        *self
197    }
198}
199impl<REv> Copy for EventQueueHandle<REv> {}
200
201impl<REv> EventQueueHandle<REv> {
202    /// Creates a new event queue handle.
203    pub(crate) fn new(scheduler: &'static Scheduler<REv>, is_shutting_down: SharedFlag) -> Self {
204        EventQueueHandle {
205            scheduler,
206            is_shutting_down,
207        }
208    }
209
210    /// Creates a new event queue handle that is not connected to a shutdown flag.
211    ///
212    /// This method is used in tests, where we are never disabling shutdown warnings anyway.
213    #[cfg(test)]
214    pub(crate) fn without_shutdown(scheduler: &'static Scheduler<REv>) -> Self {
215        EventQueueHandle::new(scheduler, SharedFlag::global_shared())
216    }
217
218    /// Schedule an event on a specific queue.
219    ///
220    /// The scheduled event will not have an ancestor.
221    pub(crate) async fn schedule<Ev>(self, event: Ev, queue_kind: QueueKind)
222    where
223        REv: From<Ev>,
224    {
225        self.schedule_with_ancestor(None, event, queue_kind).await;
226    }
227
228    /// Schedule an event on a specific queue.
229    pub(crate) async fn schedule_with_ancestor<Ev>(
230        self,
231        ancestor: Option<NonZeroU64>,
232        event: Ev,
233        queue_kind: QueueKind,
234    ) where
235        REv: From<Ev>,
236    {
237        self.scheduler
238            .push((ancestor, event.into()), queue_kind)
239            .await;
240    }
241
242    /// Returns number of events in each of the scheduler's queues.
243    pub(crate) fn event_queues_counts(&self) -> HashMap<QueueKind, usize> {
244        self.scheduler.event_queues_counts()
245    }
246
247    /// Returns whether the associated reactor is currently shutting down.
248    pub(crate) fn shutdown_flag(&self) -> SharedFlag {
249        self.is_shutting_down
250    }
251}
252
253/// Reactor core.
254///
255/// Any reactor should implement this trait and be executed by the `reactor::run` function.
256pub(crate) trait Reactor: Sized {
257    // Note: We've gone for the `Sized` bound here, since we return an instance in `new`. As an
258    // alternative, `new` could return a boxed instance instead, removing this requirement.
259
260    /// Event type associated with reactor.
261    ///
262    /// Defines what kind of event the reactor processes.
263    type Event: ReactorEvent + Display;
264
265    /// A configuration for the reactor
266    type Config;
267
268    /// The error type returned by the reactor.
269    type Error: Send + 'static;
270
271    /// Dispatches an event on the reactor.
272    ///
273    /// This function is typically only called by the reactor itself to dispatch an event. It is
274    /// safe to call regardless, but will cause the event to skip the queue and things like
275    /// accounting.
276    fn dispatch_event(
277        &mut self,
278        effect_builder: EffectBuilder<Self::Event>,
279        rng: &mut NodeRng,
280        event: Self::Event,
281    ) -> Effects<Self::Event>;
282
283    /// Creates a new instance of the reactor.
284    ///
285    /// This method creates the full state, which consists of all components, and returns a reactor
286    /// instance along with the effects that the components generated upon instantiation.
287    ///
288    /// If any instantiation fails, an error is returned.
289    fn new(
290        cfg: Self::Config,
291        chainspec: Arc<Chainspec>,
292        chainspec_raw_bytes: Arc<ChainspecRawBytes>,
293        network_identity: NetworkIdentity,
294        registry: &Registry,
295        event_queue: EventQueueHandle<Self::Event>,
296        rng: &mut NodeRng,
297    ) -> Result<(Self, Effects<Self::Event>), Self::Error>;
298
299    /// Instructs the reactor to update performance metrics, if any.
300    fn update_metrics(&mut self, _event_queue_handle: EventQueueHandle<Self::Event>) {}
301
302    /// Activate/deactivate a failpoint.
303    fn activate_failpoint(&mut self, _activation: &FailpointActivation) {
304        // Default is to ignore the failpoint. If failpoint support is enabled for a reactor, route
305        // the activation to the respective components here.
306    }
307
308    /// Returns the state of a named components.
309    ///
310    /// May return `None` if the component cannot be found, or if the reactor does not support
311    /// querying component states.
312    #[allow(dead_code)]
313    #[cfg(test)]
314    fn get_component_state(&self, _name: &str) -> Option<&ComponentState> {
315        None
316    }
317}
318
319/// A reactor event type.
320pub(crate) trait ReactorEvent: Send + Debug + From<ControlAnnouncement> + 'static {
321    /// Returns `true` if the event is a control announcement variant.
322    fn is_control(&self) -> bool;
323
324    /// Converts the event into a control announcement without copying.
325    ///
326    /// Note that this function must return `Some` if and only `is_control` returns `true`.
327    fn try_into_control(self) -> Option<ControlAnnouncement>;
328
329    /// Returns a cheap but human-readable description of the event.
330    fn description(&self) -> &'static str {
331        "anonymous event"
332    }
333}
334
335/// A drop-like trait for `async` compatible drop-and-wait.
336///
337/// Shuts down a type by explicitly freeing resources, but allowing to wait on cleanup to complete.
338#[cfg(test)]
339pub(crate) trait Finalize: Sized {
340    /// Runs cleanup code and waits for a shutdown to complete.
341    ///
342    /// This function must always be optional and a way to wait for all resources to be freed, not
343    /// mandatory for cleanup!
344    fn finalize(self) -> BoxFuture<'static, ()> {
345        async move {}.boxed()
346    }
347}
348
349/// Represents memory statistics in bytes.
350struct AllocatedMem {
351    /// Total allocated memory in bytes.
352    allocated: u64,
353    /// Total consumed memory in bytes.
354    consumed: u64,
355    /// Total system memory in bytes.
356    total: u64,
357}
358
359/// A runner for a reactor.
360///
361/// The runner manages a reactor's event queue and reactor itself and can run it either continuously
362/// or in a step-by-step manner.
363#[derive(Debug)]
364pub(crate) struct Runner<R>
365where
366    R: Reactor,
367{
368    /// The scheduler used for the reactor.
369    scheduler: &'static Scheduler<R::Event>,
370
371    /// The reactor instance itself.
372    reactor: R,
373
374    /// Counter for events, to aid tracing.
375    current_event_id: u64,
376
377    /// Timestamp of last reactor metrics update.
378    last_metrics: Instant,
379
380    /// Metrics for the runner.
381    metrics: RunnerMetrics,
382
383    /// Check if we need to update reactor metrics every this many events.
384    event_metrics_threshold: u64,
385
386    /// Only update reactor metrics if at least this much time has passed.
387    event_metrics_min_delay: Duration,
388
389    /// An accurate, possible TSC-supporting clock.
390    clock: Clock,
391
392    /// Flag indicating the reactor is being shut down.
393    is_shutting_down: SharedFlag,
394}
395
396/// Metric data for the Runner
397#[derive(Debug)]
398struct RunnerMetrics {
399    /// Total number of events processed.
400    events: IntCounter,
401    /// Histogram of how long it took to dispatch an event.
402    event_dispatch_duration: Histogram,
403    /// Total allocated RAM in bytes, as reported by stats_alloc.
404    allocated_ram_bytes: IntGauge,
405    /// Total consumed RAM in bytes, as reported by sys-info.
406    consumed_ram_bytes: IntGauge,
407    /// Total system RAM in bytes, as reported by sys-info.
408    total_ram_bytes: IntGauge,
409    /// Handle to the metrics registry, in case we need to unregister.
410    registry: Registry,
411}
412
413impl RunnerMetrics {
414    /// Create and register new runner metrics.
415    fn new(registry: &Registry) -> Result<Self, prometheus::Error> {
416        let events = IntCounter::new(
417            "runner_events",
418            "running total count of events handled by this reactor",
419        )?;
420
421        // Create an event dispatch histogram, putting extra emphasis on the area between 1-10 us.
422        let event_dispatch_duration = Histogram::with_opts(
423            HistogramOpts::new(
424                "event_dispatch_duration",
425                "time in nanoseconds to dispatch an event",
426            )
427            .buckets(vec![
428                100.0,
429                500.0,
430                1_000.0,
431                5_000.0,
432                10_000.0,
433                20_000.0,
434                50_000.0,
435                100_000.0,
436                200_000.0,
437                300_000.0,
438                400_000.0,
439                500_000.0,
440                600_000.0,
441                700_000.0,
442                800_000.0,
443                900_000.0,
444                1_000_000.0,
445                2_000_000.0,
446                5_000_000.0,
447            ]),
448        )?;
449
450        let allocated_ram_bytes =
451            IntGauge::new("allocated_ram_bytes", "total allocated ram in bytes")?;
452        let consumed_ram_bytes =
453            IntGauge::new("consumed_ram_bytes", "total consumed ram in bytes")?;
454        let total_ram_bytes = IntGauge::new("total_ram_bytes", "total system ram in bytes")?;
455
456        registry.register(Box::new(events.clone()))?;
457        registry.register(Box::new(event_dispatch_duration.clone()))?;
458        registry.register(Box::new(allocated_ram_bytes.clone()))?;
459        registry.register(Box::new(consumed_ram_bytes.clone()))?;
460        registry.register(Box::new(total_ram_bytes.clone()))?;
461
462        Ok(RunnerMetrics {
463            events,
464            event_dispatch_duration,
465            registry: registry.clone(),
466            allocated_ram_bytes,
467            consumed_ram_bytes,
468            total_ram_bytes,
469        })
470    }
471}
472
473impl Drop for RunnerMetrics {
474    fn drop(&mut self) {
475        unregister_metric!(self.registry, self.events);
476        unregister_metric!(self.registry, self.event_dispatch_duration);
477        unregister_metric!(self.registry, self.allocated_ram_bytes);
478        unregister_metric!(self.registry, self.consumed_ram_bytes);
479        unregister_metric!(self.registry, self.total_ram_bytes);
480    }
481}
482
483impl<R> Runner<R>
484where
485    R: Reactor,
486    R::Event: Serialize,
487    R::Error: From<prometheus::Error>,
488{
489    /// Creates a new runner from a given configuration, using existing metrics.
490    #[instrument(
491        "init",
492        level = "debug",
493        skip_all,
494        fields(node_id = %NodeId::from(&network_identity))
495    )]
496    pub(crate) async fn with_metrics(
497        cfg: R::Config,
498        chainspec: Arc<Chainspec>,
499        chainspec_raw_bytes: Arc<ChainspecRawBytes>,
500        network_identity: NetworkIdentity,
501        rng: &mut NodeRng,
502        registry: &Registry,
503    ) -> Result<Self, R::Error> {
504        adjust_open_files_limit();
505
506        let event_size = size_of::<R::Event>();
507
508        // Check if the event is of a reasonable size. This only emits a runtime warning at startup
509        // right now, since storage size of events is not an issue per se, but copying might be
510        // expensive if events get too large.
511        if event_size > 16 * size_of::<usize>() {
512            warn!(
513                %event_size, type_name = ?any::type_name::<R::Event>(),
514                "large event size, consider reducing it or boxing"
515            );
516        }
517
518        let event_queue_dump_threshold =
519            env::var("CL_EVENT_QUEUE_DUMP_THRESHOLD").map_or(None, |s| s.parse::<usize>().ok());
520
521        let scheduler = utils::leak(Scheduler::new(
522            QueueKind::weights(),
523            event_queue_dump_threshold,
524        ));
525        let is_shutting_down = SharedFlag::new();
526        let event_queue = EventQueueHandle::new(scheduler, is_shutting_down);
527        let (reactor, initial_effects) = R::new(
528            cfg,
529            chainspec,
530            chainspec_raw_bytes,
531            network_identity,
532            registry,
533            event_queue,
534            rng,
535        )?;
536
537        info!(
538            "Reactor: with_metrics has: {} initial_effects",
539            initial_effects.len()
540        );
541        // Run all effects from component instantiation.
542        process_effects(None, scheduler, initial_effects, QueueKind::Regular)
543            .instrument(debug_span!("process initial effects"))
544            .await;
545
546        info!("reactor main loop is ready");
547
548        Ok(Runner {
549            scheduler,
550            reactor,
551            current_event_id: 1,
552            metrics: RunnerMetrics::new(registry)?,
553            last_metrics: Instant::now(),
554            event_metrics_min_delay: Duration::from_secs(30),
555            event_metrics_threshold: 1000,
556            clock: Clock::new(),
557            is_shutting_down,
558        })
559    }
560
561    /// Processes a single event on the event queue.
562    ///
563    /// Returns `Some(exit_code)` if processing should stop.
564    #[instrument("dispatch", level = "debug", fields(a, ev = self.current_event_id), skip(self, rng))]
565    pub(crate) async fn crank(&mut self, rng: &mut NodeRng) -> Option<ExitCode> {
566        self.metrics.events.inc();
567
568        let event_queue = EventQueueHandle::new(self.scheduler, self.is_shutting_down);
569        let effect_builder = EffectBuilder::new(event_queue);
570
571        // Update metrics like memory usage and event queue sizes.
572        if self.current_event_id % self.event_metrics_threshold == 0 {
573            // We update metrics on the first very event as well to get a good baseline.
574            if self.last_metrics.elapsed() >= self.event_metrics_min_delay {
575                self.reactor.update_metrics(event_queue);
576
577                // Use a fresh timestamp. This skews the metrics collection interval a little bit,
578                // but ensures that if metrics collection time explodes, we are guaranteed a full
579                // `event_metrics_min_delay` of event processing.
580                self.last_metrics = Instant::now();
581            }
582
583            if let Some(AllocatedMem {
584                allocated,
585                consumed,
586                total,
587            }) = Self::get_allocated_memory()
588            {
589                trace!(%allocated, %total, "memory allocated");
590                self.metrics.allocated_ram_bytes.set(allocated as i64);
591                self.metrics.consumed_ram_bytes.set(consumed as i64);
592                self.metrics.total_ram_bytes.set(total as i64);
593            }
594        }
595
596        let ((ancestor, event), queue_kind) = self.scheduler.pop().await;
597        trace!(%event, %queue_kind, "current");
598        let event_desc = event.description();
599
600        // Create another span for tracing the processing of one event.
601        Span::current().record("ev", self.current_event_id);
602
603        // If we know the ancestor of an event, record it.
604        if let Some(ancestor) = ancestor {
605            Span::current().record("a", ancestor.get());
606        }
607
608        // Dispatch the event, then execute the resulting effect.
609        let start = self.clock.start();
610
611        let (effects, maybe_exit_code, queue_kind) = if event.is_control() {
612            // We've received a control event, which will _not_ be handled by the reactor.
613            match event.try_into_control() {
614                None => {
615                    // If `as_control().is_some()` is true, but `try_into_control` fails, the trait
616                    // is implemented incorrectly.
617                    error!(
618                        "event::as_control succeeded, but try_into_control failed. this is a bug"
619                    );
620
621                    // We ignore the event.
622                    (Effects::new(), None, QueueKind::Control)
623                }
624                Some(ControlAnnouncement::ShutdownDueToUserRequest) => (
625                    Effects::new(),
626                    Some(ExitCode::CleanExitDontRestart),
627                    QueueKind::Control,
628                ),
629                Some(ControlAnnouncement::ShutdownForUpgrade) => {
630                    (Effects::new(), Some(ExitCode::Success), QueueKind::Control)
631                }
632                Some(ControlAnnouncement::ShutdownAfterCatchingUp) => (
633                    Effects::new(),
634                    Some(ExitCode::CleanExitDontRestart),
635                    QueueKind::Control,
636                ),
637                Some(ControlAnnouncement::FatalError { file, line, msg }) => {
638                    error!(%file, %line, %msg, "fatal error via control announcement");
639                    (Effects::new(), Some(ExitCode::Abort), QueueKind::Control)
640                }
641                Some(ControlAnnouncement::QueueDumpRequest {
642                    dump_format,
643                    finished,
644                }) => {
645                    match dump_format {
646                        QueueDumpFormat::Serde(mut ser) => {
647                            self.scheduler
648                                .dump(move |queue_dump| {
649                                    if let Err(err) =
650                                        queue_dump.erased_serialize(&mut ser.as_serializer())
651                                    {
652                                        warn!(%err, "queue dump failed to serialize");
653                                    }
654                                })
655                                .await;
656                        }
657                        QueueDumpFormat::Debug(ref file) => {
658                            match file.try_clone() {
659                                Ok(mut local_file) => {
660                                    self.scheduler
661                                        .dump(move |queue_dump| {
662                                            write!(&mut local_file, "{:?}", queue_dump)
663                                                .and_then(|_| local_file.flush())
664                                                .map_err(|err| {
665                                                    warn!(
666                                                        ?err,
667                                                        "failed to write/flush queue dump using debug format"
668                                                    );
669                                                })
670                                                .ok();
671                                        })
672                                        .await;
673                                }
674                                Err(err) => warn!(
675                                    %err,
676                                    "could not create clone of temporary file for queue debug dump"
677                                ),
678                            };
679                        }
680                    }
681
682                    // Notify requester that we finished writing the queue dump.
683                    finished.respond(()).await;
684
685                    // Do nothing on queue dump otherwise.
686                    (Default::default(), None, QueueKind::Control)
687                }
688                Some(ControlAnnouncement::ActivateFailpoint { activation }) => {
689                    self.reactor.activate_failpoint(&activation);
690
691                    // No other effects, calling the method is all we had to do.
692                    (Effects::new(), None, QueueKind::Control)
693                }
694            }
695        } else {
696            (
697                self.reactor.dispatch_event(effect_builder, rng, event),
698                None,
699                queue_kind,
700            )
701        };
702
703        let end = self.clock.end();
704
705        // Warn if processing took a long time, record to histogram.
706        let delta = self.clock.delta(start, end);
707        if delta > *DISPATCH_EVENT_THRESHOLD {
708            warn!(%event_desc, ns = delta.into_nanos(), "event took very long to dispatch");
709        }
710        self.metrics
711            .event_dispatch_duration
712            .observe(delta.into_nanos() as f64);
713
714        // Run effects, with the current event ID as the ancestor for resulting set of events.
715        process_effects(
716            NonZeroU64::new(self.current_event_id),
717            self.scheduler,
718            effects,
719            queue_kind,
720        )
721        .in_current_span()
722        .await;
723
724        self.current_event_id += 1;
725
726        maybe_exit_code
727    }
728
729    /// Gets both the allocated and total memory from sys-info + jemalloc
730    fn get_allocated_memory() -> Option<AllocatedMem> {
731        let mem_info = match sys_info::mem_info() {
732            Ok(mem_info) => mem_info,
733            Err(error) => {
734                warn!(%error, "unable to get mem_info using sys-info");
735                return None;
736            }
737        };
738
739        // mem_info gives us kilobytes
740        let total = mem_info.total * 1024;
741        let consumed = total - (mem_info.avail * 1024);
742
743        let Stats {
744            allocations: _,
745            deallocations: _,
746            reallocations: _,
747            bytes_allocated,
748            bytes_deallocated,
749            bytes_reallocated: _,
750        } = INSTRUMENTED_SYSTEM.stats();
751
752        Some(AllocatedMem {
753            allocated: bytes_allocated.saturating_sub(bytes_deallocated) as u64,
754            consumed,
755            total,
756        })
757    }
758
759    /// Runs the reactor until `self.crank` returns `Some` or we get interrupted by a termination
760    /// signal.
761    pub(crate) async fn run(&mut self, rng: &mut NodeRng) -> ExitCode {
762        loop {
763            match TERMINATION_REQUESTED.load(Ordering::SeqCst) as i32 {
764                0 => {
765                    if let Some(exit_code) = self.crank(rng).await {
766                        self.is_shutting_down.set();
767                        break exit_code;
768                    }
769                }
770                SIGINT => {
771                    self.is_shutting_down.set();
772                    break ExitCode::SigInt;
773                }
774                SIGQUIT => {
775                    self.is_shutting_down.set();
776                    break ExitCode::SigQuit;
777                }
778                SIGTERM => {
779                    self.is_shutting_down.set();
780                    break ExitCode::SigTerm;
781                }
782                _ => error!("should be unreachable - bug in signal handler"),
783            }
784        }
785    }
786}
787
788#[cfg(test)]
789#[derive(Eq, PartialEq, Debug)]
790pub(crate) enum TryCrankOutcome {
791    NoEventsToProcess,
792    ProcessedAnEvent,
793    ShouldExit(ExitCode),
794    Exited,
795}
796
797#[cfg(test)]
798impl<R> Runner<R>
799where
800    R: Reactor,
801    R::Event: Serialize,
802    R::Error: From<prometheus::Error>,
803{
804    /// Creates a new runner from a given configuration.
805    ///
806    /// Creates a metrics registry that is only going to be used in this runner.
807    pub(crate) async fn new(
808        cfg: R::Config,
809        chainspec: Arc<Chainspec>,
810        chainspec_raw_bytes: Arc<ChainspecRawBytes>,
811        rng: &mut NodeRng,
812    ) -> Result<Self, R::Error> {
813        // Instantiate a new registry for metrics for this reactor.
814        let registry = Registry::new();
815        let network_identity = NetworkIdentity::with_generated_certs().unwrap();
816        Self::with_metrics(
817            cfg,
818            chainspec,
819            chainspec_raw_bytes,
820            network_identity,
821            rng,
822            &registry,
823        )
824        .await
825    }
826
827    /// Create an instance of an `EffectBuilder`.
828    #[cfg(test)]
829    pub(crate) fn effect_builder(&self) -> EffectBuilder<R::Event> {
830        let event_queue = EventQueueHandle::new(self.scheduler, self.is_shutting_down);
831        EffectBuilder::new(event_queue)
832    }
833
834    /// Inject (schedule then process) effects created via a call to `create_effects` which is
835    /// itself passed an instance of an `EffectBuilder`.
836    #[cfg(test)]
837    pub(crate) async fn process_injected_effects<F>(&mut self, create_effects: F)
838    where
839        F: FnOnce(EffectBuilder<R::Event>) -> Effects<R::Event>,
840    {
841        use tracing::{debug_span, Instrument};
842
843        let event_queue = EventQueueHandle::new(self.scheduler, self.is_shutting_down);
844        let effect_builder = EffectBuilder::new(event_queue);
845
846        let effects = create_effects(effect_builder);
847
848        process_effects(None, self.scheduler, effects, QueueKind::Regular)
849            .instrument(debug_span!(
850                "process injected effects",
851                ev = self.current_event_id
852            ))
853            .await
854    }
855
856    /// Processes a single event if there is one and we haven't previously handled an exit code.
857    pub(crate) async fn try_crank(&mut self, rng: &mut NodeRng) -> TryCrankOutcome {
858        if self.is_shutting_down.is_set() {
859            TryCrankOutcome::Exited
860        } else if self.scheduler.item_count() == 0 {
861            TryCrankOutcome::NoEventsToProcess
862        } else {
863            match self.crank(rng).await {
864                Some(exit_code) => {
865                    self.is_shutting_down.set();
866                    TryCrankOutcome::ShouldExit(exit_code)
867                }
868                None => TryCrankOutcome::ProcessedAnEvent,
869            }
870        }
871    }
872
873    /// Returns a reference to the reactor.
874    pub(crate) fn reactor(&self) -> &R {
875        &self.reactor
876    }
877
878    /// Returns a mutable reference to the reactor.
879    pub(crate) fn reactor_mut(&mut self) -> &mut R {
880        &mut self.reactor
881    }
882
883    /// Shuts down a reactor, sealing and draining the entire queue before returning it.
884    pub(crate) async fn drain_into_inner(self) -> R {
885        self.is_shutting_down.set();
886        self.scheduler.seal();
887        for (ancestor, event) in self.scheduler.drain_queues().await {
888            tracing::debug!(?ancestor, %event, "drained event");
889        }
890        self.reactor
891    }
892}
893
894#[cfg(test)]
895impl<R> Runner<ConditionCheckReactor<R>>
896where
897    R: Reactor + NetworkedReactor,
898    R::Event: Serialize,
899    R::Error: From<prometheus::Error>,
900{
901    /// Cranks the runner until `condition` is true or until `within` has elapsed.
902    ///
903    /// Returns `true` if `condition` has been met within the specified timeout.
904    ///
905    /// Panics if cranking causes the node to return an exit code.
906    pub(crate) async fn crank_until<F>(&mut self, rng: &mut TestRng, condition: F, within: Duration)
907    where
908        F: Fn(&R::Event) -> bool + Send + 'static,
909    {
910        self.reactor.set_condition_checker(Box::new(condition));
911
912        tokio::time::timeout(within, self.crank_and_check_indefinitely(rng))
913            .await
914            .unwrap_or_else(|_| {
915                panic!(
916                    "Runner::crank_until() timed out after {}s on node {}",
917                    within.as_secs_f64(),
918                    self.reactor.inner().node_id()
919                )
920            })
921    }
922
923    async fn crank_and_check_indefinitely(&mut self, rng: &mut TestRng) {
924        loop {
925            match self.try_crank(rng).await {
926                TryCrankOutcome::NoEventsToProcess => {
927                    FakeClock::advance_time(POLL_INTERVAL.as_millis() as u64);
928                    tokio::time::sleep(POLL_INTERVAL).await;
929                    continue;
930                }
931                TryCrankOutcome::ProcessedAnEvent => {}
932                TryCrankOutcome::ShouldExit(exit_code) => {
933                    panic!("should not exit: {:?}", exit_code)
934                }
935                TryCrankOutcome::Exited => unreachable!(),
936            }
937
938            if self.reactor.condition_result() {
939                info!("{} met condition", self.reactor.inner().node_id());
940                return;
941            }
942        }
943    }
944}
945
946/// Spawns tasks that will process the given effects.
947///
948/// Result events from processing the events will be scheduled with the given ancestor.
949async fn process_effects<Ev>(
950    ancestor: Option<NonZeroU64>,
951    scheduler: &'static Scheduler<Ev>,
952    effects: Effects<Ev>,
953    queue_kind: QueueKind,
954) where
955    Ev: Send + 'static,
956{
957    for effect in effects {
958        tokio::spawn(async move {
959            for event in effect.await {
960                scheduler.push((ancestor, event), queue_kind).await;
961            }
962        });
963    }
964}
965
966/// Converts a single effect into another by wrapping it.
967fn wrap_effect<Ev, REv, F>(wrap: F, effect: Effect<Ev>) -> Effect<REv>
968where
969    F: Fn(Ev) -> REv + Send + 'static,
970    Ev: Send + 'static,
971    REv: Send + 'static,
972{
973    // The double-boxing here is very unfortunate =(.
974    (async move {
975        let events = effect.await;
976        events.into_iter().map(wrap).collect()
977    })
978    .boxed()
979}
980
981/// Converts multiple effects into another by wrapping.
982pub(crate) fn wrap_effects<Ev, REv, F>(wrap: F, effects: Effects<Ev>) -> Effects<REv>
983where
984    F: Fn(Ev) -> REv + Send + 'static + Clone,
985    Ev: Send + 'static,
986    REv: Send + 'static,
987{
988    effects
989        .into_iter()
990        .map(move |effect| wrap_effect(wrap.clone(), effect))
991        .collect()
992}
993
994fn handle_fetch_response<R, I>(
995    reactor: &mut R,
996    effect_builder: EffectBuilder<<R as Reactor>::Event>,
997    rng: &mut NodeRng,
998    sender: NodeId,
999    serialized_item: &[u8],
1000) -> Effects<<R as Reactor>::Event>
1001where
1002    I: FetchItem,
1003    R: Reactor,
1004    <R as Reactor>::Event: From<fetcher::Event<I>> + From<PeerBehaviorAnnouncement>,
1005{
1006    match fetcher::Event::<I>::from_get_response_serialized_item(sender, serialized_item) {
1007        Some(fetcher_event) => {
1008            Reactor::dispatch_event(reactor, effect_builder, rng, fetcher_event.into())
1009        }
1010        None => effect_builder
1011            .announce_block_peer_with_justification(
1012                sender,
1013                BlocklistJustification::SentBadItem { tag: I::TAG },
1014            )
1015            .ignore(),
1016    }
1017}
1018
1019fn handle_get_response<R>(
1020    reactor: &mut R,
1021    effect_builder: EffectBuilder<<R as Reactor>::Event>,
1022    rng: &mut NodeRng,
1023    sender: NodeId,
1024    message: Box<NetResponse>,
1025) -> Effects<<R as Reactor>::Event>
1026where
1027    R: Reactor,
1028    <R as Reactor>::Event: From<transaction_acceptor::Event>
1029        + From<fetcher::Event<FinalitySignature>>
1030        + From<fetcher::Event<Block>>
1031        + From<fetcher::Event<BlockHeader>>
1032        + From<fetcher::Event<BlockExecutionResultsOrChunk>>
1033        + From<fetcher::Event<LegacyDeploy>>
1034        + From<fetcher::Event<Transaction>>
1035        + From<fetcher::Event<SyncLeap>>
1036        + From<fetcher::Event<TrieOrChunk>>
1037        + From<fetcher::Event<ApprovalsHashes>>
1038        + From<block_accumulator::Event>
1039        + From<PeerBehaviorAnnouncement>,
1040{
1041    match *message {
1042        NetResponse::Transaction(ref serialized_item) => handle_fetch_response::<R, Transaction>(
1043            reactor,
1044            effect_builder,
1045            rng,
1046            sender,
1047            serialized_item,
1048        ),
1049        NetResponse::LegacyDeploy(ref serialized_item) => handle_fetch_response::<R, LegacyDeploy>(
1050            reactor,
1051            effect_builder,
1052            rng,
1053            sender,
1054            serialized_item,
1055        ),
1056        NetResponse::Block(ref serialized_item) => {
1057            handle_fetch_response::<R, Block>(reactor, effect_builder, rng, sender, serialized_item)
1058        }
1059        NetResponse::BlockHeader(ref serialized_item) => handle_fetch_response::<R, BlockHeader>(
1060            reactor,
1061            effect_builder,
1062            rng,
1063            sender,
1064            serialized_item,
1065        ),
1066        NetResponse::FinalitySignature(ref serialized_item) => {
1067            handle_fetch_response::<R, FinalitySignature>(
1068                reactor,
1069                effect_builder,
1070                rng,
1071                sender,
1072                serialized_item,
1073            )
1074        }
1075        NetResponse::SyncLeap(ref serialized_item) => handle_fetch_response::<R, SyncLeap>(
1076            reactor,
1077            effect_builder,
1078            rng,
1079            sender,
1080            serialized_item,
1081        ),
1082        NetResponse::ApprovalsHashes(ref serialized_item) => {
1083            handle_fetch_response::<R, ApprovalsHashes>(
1084                reactor,
1085                effect_builder,
1086                rng,
1087                sender,
1088                serialized_item,
1089            )
1090        }
1091        NetResponse::BlockExecutionResults(ref serialized_item) => {
1092            handle_fetch_response::<R, BlockExecutionResultsOrChunk>(
1093                reactor,
1094                effect_builder,
1095                rng,
1096                sender,
1097                serialized_item,
1098            )
1099        }
1100    }
1101}